Linux IO编程
Linux系统中每个运行的进程,具有与之关联的文件描述符,通过这些描述符可以访问打开的文件或者设备。当一个进程打开时,一般会有三个已经打开的文件描述符:
描述符 | 说明 |
0 | 代表标准输入 |
1 | 代表标准输出 |
2 | 代表标准错误 |
下表是与文件访问有关的系统调用
系统调用 | 说明 | ||
write |
将缓冲区buf的前n个字节写入到文件描述符fildes关联的文件中,返回实际写入的字节数,如果底层设备对数据块长度比较敏感,返回值可能小于n。如果出现错误,返回-1,可以通过全局变量errno访问错误代码。 函数原型: size_t write( int fildes, const void *buf, size_t nbytes ); 示例代码:
|
||
read |
从文件描述符fildes关联的文件中读取n字节数据,并把它们放入缓冲区中,返回实际读入的字节数。如果返回0表示未读入任何数据,已经到达文件结尾;如果返回-1表示出错。 函数原型: size_t read( int fildes, void *buf, size_t nbytes ); 示例代码:
|
||
open |
该函数用于创建一个新的文件描述符。该调用会返回一个文件描述符,其它进程即使打开同一个文件,也不会使用相同的描述符。如果两个进程同时写一个文件,那么写入的内容可能会相互覆盖(两个进程读写偏移量独立维护)。 函数原型:
open调用在成功时返回一个非负整数,作为文件描述符。失败时返回-1并设置全局变量errno。新文件描述符总是使用未用描述符的最小值。该特性被用于重定向:关闭标准输出后,再次调用open,则文件描述符1被重新使用,从而实现重定向。 任何运行中的程序能够打开的文件数量是有限制的,该限制在头文件limits.h中的OPEN_MAX定义,POSIX要求最少可以打开16个,在Linux中,该限制可以在运行时动态调整,因此OPEN_MAX是变量 |
||
close |
可以终止文件描述符与对应文件的关联,文件描述符被释放,可以重新使用。成功关闭返回0,否则返回-1 |
||
lseek |
可以对文件描述符的读写指针进行设置,即设置文件的读写位置。返回文件头到指针设置处的字节偏移量,失败时返回-1 函数原型:
|
||
fstat stat lstat |
返回与打开文件描述符关联的文件的状态信息 函数原型:
|
||
dup dup2 |
用于复制文件描述符,可以让多个文件描述符指向同一文件,从而允许在文件的不同位置进行读写。通过管道在多个进程间进行通信时,这些调用很有用 函数原型:
|
标准I/O库(stdio)及其头文件stdio.h为底层I/O系统提供通用的对外接口,现在这个库已经是ANSI C标准库的一部分。标准I/O库提供多种复杂的函数用于格式化输出、扫描输入,还负责设备缓冲的处理。
在标准I/O库中,操控文件的方式与系统调用类似,其中与底层文件描述符对应的是流(Stream),被实现为指向结构体FILE的指针。
在程序启动时,stdin、stdout、stderr这三个文件已经打开。
标准I/O库关于文件读写API的使用,参考Linux I/O编程
相关系统调用或函数:
系统调用 | 说明 | ||
chmod | 修改目录或者文件的模式(权限),是chmod命令的基础:
|
||
chown | 超级用户可以修改文件的所有者:
|
||
unlink link symlink |
unlink 可以用来删除文件,该调用减少文件的链接数,成功返回0否则返回-1,要求用户具有文件所属目录的写和执行权限。如果链接数为0且没有进程打开之,则文件就会被删除
|
||
mkdir rmdir |
分别用于创建和删除目录,rmdir只有在目录为空的时候才能删除
|
||
chdir | 修改当前目录:
|
||
函数 | 说明 | ||
getcwd | 获取当前工作目录,如果缓冲区长度不够,返回NULL,否则返回缓冲区
|
虽然可以将目录作为普通文件一样打开读写,但是这种方式不具有可移植性。Linux下用于目录访问的标准库函数定义在dirent.h头中,这些函数使用结构体DIR作为目录操作的基础,DIR*被称为目录流。这些函数中常用的包括:
函数 | 说明 | ||
opendir |
opendir打开一个目录并建立目录流,如果失败,返回NULL,在底层,目录流打开目录的文件描述符
|
||
readdir |
返回一个包含了下一个目录项信息的dirent指针,每次调用返回下一个,遇到目录结尾则返回NULL。如果在迭代期间,其他进程创建/删除了文件,那么readdir不能保证所有文件被列出。 dirent指针包含两个数据项: ino_t d_ino;/*文件Inode节点号*/char d_name[];/*文件的名字*/
|
||
telldir |
返回当前目录流迭代的位置,可以供后续seekdir调用进行重置
|
||
seekdir |
设置目录流指针的位置
|
||
closedir |
关闭目录流,并释放与之相关的资源
|
很多文件I/O函数在失败后会设置外部变量errno的值,以提示失败的原因,程序必须在函数报告出错后立即检查errno,因为它可能被下一个函数调用覆盖。常见的错误代码包括:
错误代码 | 说明 |
EPERM | 操作不允许 |
ENOENT | 文件或目录不存在 |
EINTR | 系统调用被中断 |
EIO | I/O错误 |
EBUSY | 设备或资源忙 |
EEXIST | 文件存在 |
EINVAL | 无效参数 |
EMFILE | 打开的文件过多 |
ENODEV | 设备不存在 |
EISDIR | 是一个目录 |
ENOTDIR | 不是一个目录 |
下面两个函数用于错误代码的处理:
函数 | 说明 | ||
strerror | 将整型错误代码转换为字符串表示:
|
||
perror | 将当前错误的字符串形式添加到缓冲区后面,在前缀后面添加冒号和空格:
|
该系统调用允许对底层的文件描述符进行更加细致的控制:
1 2 3 4 5 6 7 8 9 |
#include <fcntl.h> int fcntl(int fildes, int cmd); int fcntl(int fildes, int cmd, long arg); //cmd表示需要执行的动作: //F_DUPFD 复制并返回一个新的文件描述符 //F_GETFD 返回fcntl.h中定义的文件描述符标记 //F_SETFD 设置文件描述符标记 //F_GETFL 获取文件状态标记、访问模式 |
内存映射。该函数用于创建一段供多个程序共享的内存,其中一个程序对其的修改,另外一个程序会立即看到。
该函数创建了一个指向了一段内存区域的指针,该内存区域与文件描述符指向的文件的内容关联:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <sys/mman.h> void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); //addr 起始内存地址,如果设置为0,则自动分配 //len 共享内存的长度 //prot设置共享内存的访问权限,以下位或: //PROT_READ 该内存段可读 //PROT_WRITE 该内存段可写 //PROT_EXEC 该内存段可执行 //PROT_NONE 该内存段不能被访问 //flags控制程序对内存段的改变造成的影响: //MAP_PRIVATE 内存段是私有的,修改是本地的,仅对当前进程有效 //MAP_SHARED 对内存段的修改被保留到磁盘 //MAP_FIXED 该段必须位于addr指定的地址 //fildes 共享内存关联的文件描述符 //off 共享内存访问文件内容的偏移值 |
使用msync函数可以把内存段中的部分或者全部写回到被映射的文件中,或者从文件中读出:
1 2 3 4 5 6 7 8 |
#include <sys/mman.h> int msync(void *addr, size_t len, int flags); //addr 起始地址 //len 长度 //flags 标记位: //MS_ASYNC 执行异步写 //MS_SYNC 执行同步写 //MS_INVALIDATE 把数据读回到内存段 |
使用munmap函数可以释放内存段:
1 2 |
#include <sys/mman.h> int munmap(void *addr, size_t len); |
该系统调用在Linux下不仅仅可以用于网络I/O,普通文件I/O也被支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <sys/types.h> #include <sys/time.h> #include <stdio.h> #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <stdlib.h> int main() { char buffer[128]; int result, nread; fd_set inputs, testfds; struct timeval timeout; FD_ZERO( &inputs ); //初始化为空白文件描述符集 FD_SET( 0, &inputs ); //设置标准输入 while ( 1 ) { testfds = inputs; timeout.tv_sec = 2; timeout.tv_usec = 500000; //等待标准输入上具有数据可读,超时2.5秒 result = select( FD_SETSIZE, &testfds, ( fd_set * ) NULL, ( fd_set * ) NULL, &timeout ); switch ( result ) { case 0 : printf( "超时\n" ); break; case -1 : perror( "出错" ); exit( 1 ); default : if ( FD_ISSET( 0, &testfds ) ) { //标准输入已经就绪,可以读取 } break; } } } |
Leave a Reply