系统编程--文件IO
1.文件描述符
文件描述符是一个非负整数,当打开一个现有文件或创建一个新文件时候,内核向进程返回一个文件描述符,新打开文件返回文件描述符表中未使用的最小文件描述符。Unix系统shell使用文件描述符0与进程的标准输入相关联,文件描述符1与进程的标准输出相关联,文件描述符2与进程的标准出错相关联,在POSIX标准中,幻数0、1、2应当替换为符号常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO
Linux最大打开文件描述符数 cat /proc/sys/fs/file-max 或者 ulimit -n
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
#include <fcnt1.h> int create(const char *pathname,mode_t mode); 此函数等效于 int open(pathname,O_WRONLY | O_CREAT | O_TRUNC,mode);
4.close函数
#include<fcnt1.h> int close (int filedes); //返回值若成功返回0不成功返回-1
#include<unistd.h> off_t lseek(int fileds,off_t offset,int whence); //若成功则返回新的文件偏移量,若出错则返回-1
#include <unistd.h> ssize_t read(int filedes,void *buf,size_t nbytes);
#include <unistd.h> ssize_t write(int filedes,const void *buf,size_t nbytes);
#include <unistd.h> #include <stdlib.h> int main(void) { char buf[10]; int n; n = read(STDIN_FILENO, buf, 10); if (n < 0) { perror("read STDIN_FILENO"); exit(1); } write(STDOUT_FILENO, buf, n); return 0; }
#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdlib.h> #define MSG_TRY "try again\n" int main(void) { char buf[10]; int fd, n; fd = open("/dev/tty", O_RDONLY|O_NONBLOCK); if(fd<0) { perror("open /dev/tty"); exit(1); } tryagain: n = read(fd, buf, 10); if (n < 0) { if (errno == EAGAIN) { sleep(1); write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); goto tryagain; } perror("read /dev/tty"); exit(1); } write(STDOUT_FILENO, buf, n); close(fd); return 0; }
#include <unistd.h> int dup(int filedes); int dup2(int filedes,int filedes2); //filedes2参数指定新描述符的数值。如果filedes2已经打开,则先关闭。
dup(filedes);等效于fcntl(filedes, F_DUPFD, 0);
dup2(filedes, filedes2);等效于
close(filedes2);
fcntl(filedes, F_DUPFD, filedes2);
区别
1).dup2是一个原子操作,而close及fcntl则包括两个函数调用,有可能在close和fcntl之间插入执行信号捕获函数
2).dup2和fcntl有某些不同errorno
9.sync,fsync ,fdatasync函数
为了解决延迟写的问题,保证磁盘上实际文件系统和缓冲区高速缓存中内容的一致性。
#include <unistd.h> int fsync(int filedes); // 只对由文件描述符filedes指定的单一文件其作用,并且等待写磁盘操作结束 int fdatesync(int filedes); //功能和fsync差不多,但它还同步更新文件的属性 void sync(void); //将所有修改过的快缓冲区排入写队列,然后返回,它并不等待实际写磁盘操作结束
10.fcntl函数
可以改变已打开的文件性质
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);
第三个参数总是一个整数,与上面所示函数原型中的注释部分相对应。但是在作为记录锁用时,第三个参数则是指向一个结构的指针。
fcntl函数有5种功能:
1).复制一个现有的描述符(cmd=F_DUPFD).
2).获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
3).获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
4).获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
5).获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <stdlib.h> #define MSG_TRY "try again\n" int main(void) { char buf[10]; int n; int flags; flags = fcntl(STDIN_FILENO, F_GETFL); flags |= O_NONBLOCK; if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) { perror("fcntl"); exit(1); } tryagain: n = read(STDIN_FILENO, buf, 10); if (n < 0) { if (errno == EAGAIN) { sleep(1); write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); goto tryagain; } perror("read stdin"); exit(1); } write(STDOUT_FILENO, buf, n); return 0; }
11.ioctl函数
ioctl用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是不能用read/write读写的,称为Out-of-band数据。也就是说,read/write读写的数据是in-band数据,是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数据。例如,在串口线上收发数据通过read/write操作,而串口的波特率、校验位、停止位通过ioctl设置,A/D转换的结果通过read读取,而A/D转换的精度和工作频率通过ioctl设置。
#include <unistd.h> /*System V*/ #include <sys/ioctl.h> /*BSD and Linux*/ #include <stropts.h> /*XSI STREAMS*/ int ioctl(int filedes, int request, . . .);
以下程序使用TIOCGWINSZ命令获得终端设备的窗口大小。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> int main(void) { struct winsize size; if (isatty(STDOUT_FILENO) == 0) exit(1); if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)<0) { perror("ioctl TIOCGWINSZ error"); exit(1); } printf("%d rows, %d columns\n", size.ws_row, size.ws_col); return 0; }
12./dev/fd
比较新的unix/linux系统都提供名为/dev/fd的目录,其中有文件0、1、2等文件,打开这些文件,相当于复制这些文件描述符
fd=open("/dev/fd/0",mode);
等价于
fd=dup(0);
文件描述符fd和0将共享一个文件表记录项。
13.文件共享
文件共享是指不同进程间打开文件的共享。内核通过三种数据结构来表示打开的文件,
1).每个进程有个进程表项,表中是打开文件的描述符向量。每个向量包含了文件描述符标记和一个指向文件表项的指针;
2).内核为所有打开的文件维护一个文件表,每个文件表项包括了a,文件状态标记,如读、写、添加、非阻塞等b,当前的文件偏移量c,指向文件v-node表项的指针;
3).每个打开文件或设备都有一个v-node结构 ,包含文件类型和指向操作文件的函数的指针。
当两个以上独立进程打开同一个文件实现文件共享时,内核维护不同的文件表项,就是两个以上进程表项中的文件表项指针指向不同的文件表项,而不同的文件表项中的v-node指针指向同一个v-node而实现文件共享。由于每个进程有自己的打开文件表项,所以有自己的文件打开状态以及文件偏移量。
需要注意的是Linux没有将相关数据分为i节点和v节点,而是采用了一个独立于文件系统的i节点(通用i-node结构)和一个依赖于文件系统的i节点