linux系统调用与文件I/O操作
linux系统调用
所谓系统调用是指操作系统提供给用户程序的一组“特殊”接口,用 户程序可以通过这组“特殊”接口来获得得操作系统内核提供的特殊 服务。
在linux中用户程序不能直接访部内核提供的服务。为了更好的保护内 核空间,将程序的运行空间分为内核空间和用户空间,他们运行在不 同的级上,在逻辑上是相互隔离的。
用户程序接口(API)
在linux中用户编程接口(API)遵循了在UNIX中最流行的应用编程界 面标准——POSIX标准。这些系统调用编程接口主要通过C库(libc) 实现的。
系统调用、API与系统命令之间的关系见图
文件I/O操作介绍
可用的文件I/O函数——打开文件、读文件、写文件等等。大多数 linux文件I/O只需用到5个函数:open、read、write、lseek以及 close。不带缓存指的是每read和write都调用内核中的一个系统调用 。这些不带缓存的I/O函数不是ANSIC的组成部分,但是POSIX组成部分 。
文件描述符
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一 个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程 返回一个文件描述符。当读、写一个文件时,用open或creat返回的文 件描述符标识该文件,将其作为参数传送给read或write。在POSIX.1应用程序中,整数0、1、2应被代换成符号常数 STDIN_FILEN0、STDOUT_FILENO和STDERR_FILENO。这些常数都定义在 头文件<unistd.h>中
文件描述符的范围是0~OPEN_MAX。早期的UNIX版本采用的上限值是19 (允许每个进程打开20个文件),现在很多系统则将其增加至63。
open函数
#include<sys/types.h>
#include<sys/stat.h>
#inlcude<fcntl.h>
int open(const char *pathname,int oflag,.../*,mode_t mode*/);
返回:若成功为文件描述符,若出错为-1
pathname是要打开或创建的文件的名字。
oflag参数可用来说明此函数的多个选择项。
对于open函数而言,仅当创建新文件时才使用第三个参数。
用下列一个或多个常数进行或运算构成oflag参数(这些常数定义在
<fcntl.h>头文件中):
O_RDONLY只读打开。
O_WRONLY只写打开。
O_RDWR读、写打开。
O_APPEND每次写时都加到文件的尾端。
O_CREAT若此文件不存在则创建它。使用此选择项时,需同时说明第三 个参数mode,用其说明该新文件的存取许可权位。
O_EXCL如果同时指定了O_CREAT,而文件已经存在,则出错,这可测试 一个文件是否存在,如果不存在则创建此文件成为一个原子操作。
O_TRUNC如果此文件存在,而且为只读或只写成功打开,则将其长度截 短为0。
O_NOCTTY如果pathname指的是终端设备,则不将此设备分配作为此进 程的控制终端。
O_NONBLOCK如果pathname指的是一个FIFO、一个块特殊文件或一个字 符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设 置非阻塞方式。
O_SYNC使每次write都等到物理I/O操作完成。
creat函数
可用creat函数创建一个新文件。
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int creat(const char *pathname,mode_t mode);
返回:若成功只为写打开的文件描述符,若出错为-1。注意,此函数 等效于:
open (pathname,O_WRONLY |O_CREAT| O_TRUNC,mode);
creat的一个不足之处是它以只写方式打开所创建的文件。
close函数
可用close函数关闭一个打开文件:
#include<unistd.h>
int close(int filedes);
返回:若成功为0,若出错为-1
当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序 都使用这一功能而不显式地用close关闭打开的文件。
如open.c
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#inlcude<stdlib.h>
#include<stdio.h>
int main(void){
int fd;
if(fd=open("/tmp/hello.c",O_CREAT|O_TRUNC|O_WRONLY,0600))<0){
perror("open:");
exit(1);
}else
printf("open fiale:hello.c%d/n",fd);
if(close(fd)<0){
perror("close:");
exit(1);
}
else
printf("Close hello.c/n");
exit(0);
}
lessk函数——文件定位
每个打开文件都有一个与其相关联的“当前文件偏移量”。它是一个 非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作 都从当前文件偏移理处开始,并使偏移理增加所计或写的字节数。按 系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该偏 移量被设置为0。可以调用lseek显式地定位一个打开文件。
#include<sys/types.h>
#include<unistd.h>
off_t lseek(it filesdes,off_t offset,int whence);
返回:若成功为新文件位移,若出错为-1。
对于参数offset的解释与参数whence的值有关。
若whence是SEEK_SET,则将该文件的位移量设置为距文件开始处 offset个字节。若whence是SEEK_CUR,则将该文件的位移量设置为其当前值加offset, offset可为正或负。若whence是SEEK_END,则将该文件的位移量设置为文件长度加offset,offset可为正或负。若lseek成功执行,则返回新的文件位移量,为此可以用下列方式确定 一个打开文件的当前位移量:
off_t currpos;
currpos=lseek(fd,0,SEEK_CUR);
read函数
用read函数从打开文件中读数据
#include<unistd.h>
ssize_t read(int feledes,void *buff,size_t nbytes);
返回:读到的字节数,若已到文件尾为0,若出错为-1。如read成功, 则返回读到的字节数。如已到达文件的尾端,则返回0。 有多种情况可使实际读到的字节数小于要求读字节数:
读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若 在到达文件尾端之间还有30个字节,而要求读100个字节,则read返回 30,下一次再调用read时,它将返回0(文件尾端)。当从终端设备读时,通常一次最多读一行。
当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字 节数。某些面向记录的设备,例如磁带,一次最多返回一个记录。读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加 实际读得的字节数。
write函数
用write函数向打开文件写数据。
#include<unistd.h>
ssize_t write(int filedes,const void *buff,size_t nbytes);
返回:若成功为已写的字节数,若出错为-1。其返回值通常与参数
nbytes的值不同,否则表示出错。write出错的一个常见原因是:磁盘 已写满,或者超过了对一个给定进程的文件长度限制。
对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文 件时,指定了O_APPEND选择项,则在每次写操作之前,将文件位移量 设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实 际写的字节数。见write.c
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define MAXSIZE
int main(void){
int i,fd,size,len;
char *buf="Hello! I'm writing to this file!";
char buf_r[10];
len=strlen(buf);
buf_r[10]='/0';
if((fd=open("/tmp/hello.c",O_CREAT | O_TRUNC | O_RDWR,0666)
<0){
perror("open:");
exit(1);
}else
printf("open file hello.c %d/n",fd);
if((size=write(fd,buf,len))<0){
perror("write:");
exit(1);
}else
printf("Write %s/n",buf);
lseek(fd,0,SEEK_SET);
if((size=read(fd,buf_r,10))<0){
perror("read:");
exit(1);
}else
printf("read form file:%s/n",buf_r);
if(close(fd)<0){
perror("close:");
exit(1);
}else
printf("Close hello.c/n");exit(0);
}
fcntl函数
fcntl函数可以改变已经打开文件的性质。
#include<sys/types.h>
#inlcude<unistd.h>
#inlcude<fcntl.h>
int fcntl(int filedes,int cmd,....);
返回:若成功则依赖于cmd,若出错为-1。
fcntl函数有五种功能:
(1)复制一个现存的描述符,新文件描述符作为函数值返回
(cmd=F_DUPFD).
(2)获得/设置文件描述符标记,对应于filedes的文件描述符标志作 为函数值返回。(cmd=F_GETFD或F_SETFD)。
(3)获得/设置文件状态标志,对应于filedes的文件状态标志作为函 数值返回。(cmd=F_GETEL或F_SETFL)。
(4)获得/设置异步I/O有权(cmd=F_GETOWN或F_SETOWN)。
(5)获得/设置记录锁(cmd=F_SETLK,F_SETLKW)。
文件状态的说明见附件。
F_SETFL将文件状态设置为第三个参数的值(取为整型值)。可以更改 的几个标志是:O_APPEND,O_NONBLOCK,O_SYNC和O_ASYNC.
F_GETOWN取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。
F_SETOWN设置接收SIGIO和SIGURG信号的进程ID或进程组ID。正的arg 指定一个进程组ID,负的arg表示等于arg绝对值的一个进程组ID。
用fcntl给文件加锁
当多个用户共同使用、操作一个文件的时候,linux通常采用的方未能 是给文件上锁,来避免共享资源产生竞争的状态。
文件锁包括建议锁和强制性锁。建议性锁要求上锁文件的进程都要检 测是否有锁存在,并尊重已有的锁。强制性锁由内核和系统执行的锁 。Fcntl不仅可以实施建议性锁而且还可以实施强制性锁。
fcntl函数格式
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
int fcntl(int filedes,int cmd,... struct flock flockptr);
struct flock 结构
struct flock{
short l_type; /*F_RDLCK,F_WRLCK, F_UNCLK */
off_t l_start; /*offset in bytes,relative to l_whence */
short l_whence; /*SEEK_SET,SEEK_CUR,SEEK_END */
off_t len; /*length,in bytes */
pid_t l_pid; /* returned with F_GETLK */
};
flock结构说明:所希望的锁类型:F_FDLCK(共享读锁)、F_WRLCK(独 占性写锁)或F_UNLCK(解锁一个区域)。要加锁或解锁的区域的起始地址,由l_start和l_whence两者决定。l_start是相对位移量(字节 ),l_whence则决定了相对位移量的起点。区域的长度,早l_len表示 。
关于加锁和解锁区域的说明还要注意下列几点:
(1)该区域可以在当前文件尾端处开始或越过其尾端处开始,但是不 能在文件起始位置之前开始或越过该起始位置。
(2)如若l_len为0 ,则表示锁的区域从其起点(由l_start和
l_whence决定)开始直至最大可能位置为止。也就是不管添写到该文 件中多少数据,它都处于锁的范围。
(3)为了锁整个文件,通常的方法是将l_start说明为0,l_whence说 明为SEEK_SET,l_len说明为0。
ioctl函数 还得查其它资料
ioctl函数是I/O操作的杂物箱。不能用本章中其他函数表示的I/O操作 通常都能用ioctl表示。终端I/O是ioctl的最大使用方面,主要用于设 备的I/O控制。
#include<unistd.h> /*SVR4*/
#include<sys/ioctl.h> /*4.3 +BSD * /
int ioctl(int filedes,int request,....);
返回:若出错则为-1,若成功则为其他值。
selelct实现I/O复用
I/O处理的五种模型
(1)阻塞I/O模型:若所调用的I/O函数没有完成相关的功能就会使进 程挂起,直到相关数据到达才会返回。如:终端、网络设备的访问。
(2)非阻塞模型:当请求的I/O操作不能完成时,则不让进程休眠, 而且返回一个错误。如:open,read,write访问。
(3)I/O多路转接型:如果请求的I/O操作阻塞,且他不是真正阴塞 I/O,而且让其中的一个函数等待,在这期间,I/O还能进行其他操作 。如:select函数。
(4)信号驱动I/O模型:在这种模型下,通过安装一个信号处理程序 ,系统可以自动捕获特定信号的到来,从而启动I/O。
(5)异步I/O模型:在这种模型下,当一个描述符已准备好,可以启 动I/O时,进程会通知内核。由内核进行后续处理,这种用法现在较少 。
关心第一种、第三种和第四种模型,用的比较多。
传向select的参数告诉内核:
(1)我们所关心的描述符。
(2)对于每个描述符我们所关心的条件(是否读一个给定的描述符? 是否想烈军属一个给定的描述符?是否关心一个描述符的异常条件? )。
(3)希望等待多长时间(可以永远等待,等待一个固定量时间,或完 全不等待)。
从select返回时,内核告诉我们:
(1)已准备好的描述符的数量。
(2)哪一个描述符已准备好读、写或异常条件。
#include<sys/types.h>/*fd_set daya type*/
#include<sys/time.h>/*struct timeval*/
#inlcude<unistd.h>/*function prototype might be here*/
int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set
*execptfds,struct timeval *timeout);
返回:准备就绪的描述符数,若超时则为0,若出错则为-1。
timeout值:
NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止;
具体值:struct timeval类型的指针,若等待为timeout时间还没有文 件描述符准备好,就立即返回;
0:从不等待,测试所有指定的描述符并立即返回;
先说明最后一个参数,它指定愿意等待的时间。
struct timeval{
long tv_sec; /*seconds*/
long tv_usec; /*and microseconds*/毫秒
};
select函数根据希望进行的文件操作对文件描述符进行分类处理,这 里,对文件描述符的处理主要设计4个宏函数:
FD_AERO(fd_set *set)清除一个文件描述符集;
FD_SET(int fd, fd_set *set)将一个文件描述符加入文件描述符集中 。
FD_CLR(int fd,fd_set *set)将一个文件描述符从文件描述符集中清 除。
FD_ISSET(int fd,fd_set *set)测试该集中的一个给定位是否有变化 ;
在使用select函数之间,首先使用FD_ZERO和FD_SET来初始化文件描述 符集,并使用select函数时,可循环使用FD_ISSET测试描述符集,在 执行完成对相关的文件描述符后,使用FD_CLR来清除描述符集。例子 见select.c
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/time.h>
int main(void){
int fds[2];
char buf[7];
int i,rc,maxfd;
fd_set inset1,inset2;
struct timeval tv;
if((fds[0]=open("hello1",O_RDWR| O_CREAT,0666))<0)
perror("open hello1");
if((fds[1]=open("hello2",O_RDWR|O_CREAT,0666))<0)
perror("open hello2");
if((rc=write(fds[0],"Hello!/n",7))
printf("rc=%d/n",rc);
lseek(fds[0],0,SEEK_SET);
maxfd=fds[0]>fds[1]?fds[0]:fds[1];
FD_ZERO(&inset1);
FD_SET(fds[0],&inset1);
FD_ZERO(&inset2);
FD_SET(fds[1],&inset2);;
tv.tv_sec=2;
tv.tv_usec=0;
while(FD_ISSET(fds[0],&inset1)||FD_ISSET(fds[1],&inset2)){
if(select(maxfd+1,&inset1,&inset2,NULL,&tv)<0)
perror("select");
else{
if(FD_ISSET(fds[0],&inset1)){
rc=read(fds[0],buf,7);
if(rc>0){
buf[rc]='/0';
printf("read:%s/n",buf);
}else
perror("read");
}
if(FD_ISSET(fds[1],&inset2)){
rc=write(fds[1],buf,7);
if(rc>0){
buf[rc]='/0';
printf("rc=%d,write:%s/n",rc,buf);
}else
perror("write");
sleep(10);
}
}
}
exit(0);
}
读取hello1文件的内容,写入到hello2文件中去。