API函数包含:
1)标准库函数,可跨平台移植,如fopen、fwrite、fread、fseek、fclose,在头文件<stdio.h>中定义。
2)系统API函数(即特定平台的API函数),不能跨平台移植,如windows平台的CreateFile、WriteFile、ReadFile、DeleteFile、CloseHandle,在头文件<windows.h>中定义。Unix平台的open、write、read、lseek、close,在头文件<fcntl.h>、<unistd.h>中定义。
系统调用是操作系统向应用程序提供的访问内核的入口点。
本章主要讲解的是Unix系统文件读写操作的系统API。主要文件I/O函数有:open、read、write、lseek以及close,UNIX系统中的大多数文件I/O只需用到这5个函数。这些函数被称为不带缓冲的I/O,其中不带缓冲指的是每个read和write都调用内核中的一个系统调用。
上面所述的大多数文件,包括的类型有普通文件、目录文件、块设备、字符设备、命名管道、套接字、符号链接等。
1 文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。
当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。
当读或写一个文件时,使用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
文件描述符是一个非负整数,其变化范围是0~OPEN_MAX(允许每个进程最多打开的个数).按照惯例,UNIX系统shell使用文件描述符0(对应符号常量STDIN_FILENO)与进程的标准输入相关联,1(STDOUT_FILENO)与标准输出相关联,2(STDERR_FILENO)与标准出错输出相关联。
2 文件IO函数
1)open函数
头文件 | #include <fcntl.h> |
函数原型 | int open(const char *pathname, int oflag, …/*mode_t mode*/) |
参数 | pathname:打开或创建的文件名 oflag:文件访问模式(文件状态标志) 三选一:O_RDONLY、O_WRONLY、O_RDWR 可选:O_APPEND、O_CREAT、O_EXCL、O_TRUNC、O_NOCTTY、O_NONBLOCK 可选:O_DSYNC、O_SYNC、O_RSYNC mode:文件访问权限(该参数可选) 用户读、写、执行权限:S_IRUSR、S_IWUSR、S_IXUSR 组读、写、执行权限:S_IRGRP、S_IWGRP、S_IXGRP 其他读、写、执行权限:S_IROTH、S_IWOTH、S_IXOTH 仅当创建新文件时才使用该参数,用于指定文件的访问权限 |
返回 | 成功返回文件描述符;失败返回-1并设置全局变量errno |
功能 | 打开或创建一个文件 |
注意:
由open返回的文件描述符一定是最小的未用描述符数值,这点可被用来在标准输入、标准输出或标准出错输出上打开新的文件
头文件 | #include <fcntl.h> |
函数原型 | int creat(const char *pathname, mode_t mode) |
参数 | pathname:待创建的文件名 mode:文件访问权限 |
返回 | 成功返回文件描述符;失败返回-1并设置全局变量errno |
功能 | 以只写方式打开所创建的文件,等价于: open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode) |
2)close函数
头文件 | #include <unistd.h> |
函数原型 | int close(int fildes); |
参数 | fildes:文件描述符 |
返回 | 成功返回0;出错返回-1 |
功能 | 关闭一个打开的文件,即终止文件描述符fildes与其对应文件之间的关联。 关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所打开的文件。 |
3)lseek函数
头文件 | #include <unistd.h> #include <sys/types.h> |
函数原型 | off_t lseek(int fildes, off_t offset, int whence); |
参数 | fildes:文件描述符 offset:指定位置(offset可正可负,但不能使新的文件偏移量的值为负) whence:定义偏移值的用法 SEEK_SET:将文件偏移量设置为文件开始加offset个字节 SEEK_CUR:将文件偏移量设置为其当前值加offset个字节 SEEK_END:将文件偏移量设置为文件末尾加offset个字节 |
返回 | 成功返回新的文件偏移量;失败返回-1并设置全局变量errno |
功能 | 对文件描述符fildes的读写指针进行设置,即显式地为一个打开的文件设置偏移量 |
注意:
通常文件的当前偏移量应当是一个非负整数,但是,某些设备也可能允许负的偏移量。但对于普通文件,则其偏移量必须是非负值。
lseek返回-1的情况:文件描述符引用的是一个管道,FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE
4)write函数
头文件 | #include <unistd.h> |
函数原型 | ssize_t write(int fildes, const void *buf, size_t nbytes); |
参数 | fildes:文件描述符 buf:缓冲区 nbyte:字节数 |
返回 | 成功返回已写的字节数,出错则返回-1 其返回值通常与nbytes的值相同,否则表示出错。 |
功能 | 向打开的文件写数据,即把缓冲区buf的前nbytes个字节写入与文件描述符fildes关联的文件中。 |
5)read函数
头文件 | #include <unistd.h> |
函数原型 | ssize_t read(int fildes, void *buf, size_t nbytes); |
参数 | fildes:文件描述符 buf:缓冲区 bytes:字节数 |
返回 | 成功返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1 |
功能 | 从打开的文件中读数据,即从与文件描述符fildes相关联的文件里读入nbytes个字节的数据,并把它们放到数据区buf中。 |
3 文件共享
UNIX系统支持在不同进程间共享打开的文件。内核使用三种数据结构表示打开的文件,进程表项、文件表项、v节点结
进程表项:
每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表。每
个描述符各占一项,包含a)文件描述符标志(close_on_exec),b)指向一个文件表项的指针。
文件表项:
内核为所有打开文件维持一张文件表,每个文件表项包含:a)文件状态标志,b)当
前文件偏移量,c)指向该文件v节点表项的指针
V节点表:
每个打开文件(或设备)都有一个v节点结构。V节点包含了文件类型和对此文件
进行各种操作的函数的指针。
打开该文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个v节点表项。每个进程都有自己的文件表项的一个理由是:使每个进程都有它自己的对该文件的当前偏移量。
1)文件描述符标志:fd flags
如上图,每个文件描述符fd0,fd1等,各自都包含了文件描述符标志和文件表项指针。其中的文件描述符标志(file descriptors flags即close_on_exec)用于判断子进程在调用exec函数时是否需要关闭该标志对应的文件描述符。子进程调用exec函数时,加载执行另一个程序,此时子进程完全被新程序代替,并从新程序的main函数开始执行。
当文件描述符对于的文件描述符标志被设置时,执行exec时,该描述符被关闭;否则该描述符将始终处于被打开的状态。
当前只定义了一个文件描述符标志FDCLOEXEC
0: exec时不关闭已经打开的文件描述符
1: exec时关闭已经打开的文件描述符
/*通过fcntl函数设置文件描述符标志*/
int flags;
flags = fcntl(fd, F_GETFD, 0); /*先获取fd对应的文件描述符标志值,接着根据需要修改再设置。若直接执行F_SETFD命令进行设置时,会关闭以前设置的标志位*/
fcntl(fd, F_SETFD, flags|FD_CLOEXEC);/*修改fd的文件描述符标志值,并设置*/
2)文件状态标志:File status flags
文件状态标志 | 说明 |
O_RDONLY O_WRONLY O_RDWR O_APPEND O_NONBLOCK O_SYNC O_DSYNC O_RSYNC O_FSYNC O_ASYNC | 只读打开 只写打开 为读、写打开 每次写时追加 非阻塞模式 等待写完成(数据和属性) 等待写完成(仅数据) 同步读、写 等待写完成 异步I/O |
4 其他重要函数
1)dup函数,dup2函数
头文件 | #include <unistd.h> |
函数原型 | int dup(int filedes); |
参数 | filedes:现存的文件描述符,即一个已经打开的文件的文件描述符 |
返回 | 成功返回新的文件描述符,若出错则返回-1。dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。 |
功能 | 复制一个现存的文件描述符,返回的新的描述符与参数filedes共享同一个文件表项。 |
dup函数常用于输出的重定向。示例摘自http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=359433&page=1#pid2385375
#include <stdio.h>;
#include <unistd.h>;
#include <stdlib.h>;
#include <fcntl.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <string.h>;
#include <strings.h>;
int main()
{
int sfd = dup(STDOUT_FILENO), testfd;
printf("sfd = [%d]\n", sfd);
testfd = open("./temp",O_CREAT | O_RDWR | O_APPEND);
if (-1 == testfd)
{
printf("open file error.\n");
exit(1);
}
/* 重定向 */
if (-1 == dup2(testfd,STDOUT_FILENO) ) {
printf("can't redirect fd error\n");
exit(1);
}
/* 此时向stdout写入应该输出到文件 */
write(STDOUT_FILENO,"file\n",5);
/* 恢复stdout */
if (-1 != dup2(sfd,STDOUT_FILENO) ) {
printf("recover fd ok \n");
/* 恢复后,写入stdout应该向屏幕输出 */
write(STDOUT_FILENO,"stdout\n",7);
}
printf("gogogogogogo!\n");
close(testfd);
}
头文件 | #include <unistd.h> |
函数原型 | int dup2(int filedes, int filedes2); |
参数 | filedes:现存的文件描述符,即一个已经打开的文件的文件描述符 filedes2:指定新描述符的数值,如果filedes2已经打开,则先将其关闭。如若filedes等于filedes2,则dup2返回filedes2,而不关闭它。 |
返回 | 成功返回指定的新的文件描述符,若出错则返回-1 |
功能 | 复制一个现存的文件描述符,返回的指定的新描述符与参数filedes共享同一个文件表项。 |
fd = open("/dev/null", O_RDWR, 0);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if(fd > 2)
close(fd);
上述代码段经常用于守护进程中,使得守护进程重启时,0,1,2不会对应在标准输入、标准输出、标准错误输出上。
实现与dup2功能相同的函数:
int mydup2(int fd1, int fd2)
{
int fd;
int i = 0;
int fd_array[OPEN_MAX];
/*参数错误处理*/
if (fd1 < 0 || fd1 > OPEN_MAX || fd2 < 0 || fd2 > OPEN_MAX)
{
printf("The fd is not in the limits[%d~%d], fd1=%d, fd2=%d\n", 0,
OPEN_MAX, fd1, fd2);
return -1;
}
/*判断fd1是否为有效文件描述符*/
if ((fd = dup(fd1)) == -1)
{
printf("fd1 is the bad file destriptor, fd1=%d\n", fd1);
return -1;
}
else
{
close(fd);
}
/*判断fd1是否为有效文件描述符*/
if ((fd = dup(fd2)) == -1)
{
/*do nothing*/
}
else /*fd2为有效文件描述符*/
{
close(fd);
if (fd1 == fd2)
{
return fd2;/*fd2与fd1相等则直接返回fd2,且不关闭fd2*/
}
close(fd2);
}
/*利用dup生成与fd2相等的文件描述符*/
while ((fd = dup(fd1)) != fd2)
{
fd_array[i++] = fd;
}
for (i = i - 1; i >= 0; i--)
{
close(fd_array[i]);
}
return fd2;
}
2)fcntl函数
头文件 | #include <fcntl.h> |
函数原型 | int fcntl(int filedes, int cmd, …/*int arg*/) |
参数 | 第一个参数filedes:文件描述符 第二个参数cmd: 第三个参数:用于获得/设置记录锁时,该参数是指向一个结构的指针,其他情况下都是一个整数。 |
返回 | 若成功则依赖于cmd,若出错则返回-1 |
功能 | fcntl函数有5中功能: 1) 复制一个现有的描述符(cmd=F_DUPFD) 2) 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD) 3) 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL) 4) 获得/设置异步IO所有权(cmd=F_GETOWN或F_SETOWN) 5) 获得/设置记录锁(cmd=F_GETLK、F_SETLK、F_SETLKW) |
3)ioctl函数
头文件 | #include <unistd.h> /*System V*/ #include <sys/ioctl.h> /*BSD and Linux*/ #include <stropts.h> /*XSI STREAM*/ |
函数原型 | int ioctl(int filedes, int request, …) |
参数 | filedes:文件描述符 request:请求 |
返回 | 若出错则返回-1,若成功则返回其他值 |
功能 | ioctl函数是I/O操作的杂物箱,不能用本章中其他函数表示的I/O操作通常都能用ioctl表示。 |