本文章中提到的文件IO都是不带缓冲的IO,即每个read和write都调用内核中的一个系统调用。
文件描述符
文件描述符是一个非负数,每当打开一个现有的文件或者创建一个新文件时,内核都向进程返回一个文件描述符。文件描述符用以表示所有类的已打开文件,包括管道(pipe)、FIFO、socket、终端设备和普通股文件。特殊:文件描述符0、1、2分别代表标准输入、标准输出、标准错误,把其换为符号常量即为STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO。
一些IO函数
#include <fcntl.h>
#include <unistd.h>
int open(const char *path,int oflag,...); //成功返回最小未用的文件描述符,失败返回-1
//path是文件路径,oflag为函数可选参数,(O_RDWR,O_RDONLY,O_WRONLY,O_EXEC)四选一,还有
//O_APPEND 每次写时追加到文件末尾
//O_CREAT 当文件不存在时创建
//O_TRUNC 如果文件存在且为只读或读写成功打开,则将其长度截断为0
//O_NONBLOCK 如果path是一个FIFO,一个块文件或字符文件,则应该设置为非阻塞方式
int create(const char *path,mode_t mode);//成功返回只写文件描述符,失败返回-1
等效于 open(path,o_WRONLY|O_CREAT|O_TRUNC,mode);
int close(int fd);//成功返回0,失败返回-1
off_t lseek(int fd,off_t offset,int whence);//若成功返回新的文件偏移量,若失败返回-1
//whence三个可选量SEEK_SET(将文件偏移量设置为距离文件开始除offset个字节),SEEK_CUR(将文件偏移量
//设置为当前值加offset,offset可为正或负),SEEK_END(将文件偏移量设置为文件长度加offset)
//对于每一个打开的文件,系统都会记录其文件偏移量,文件偏移量是执行下一个read和write操作的文件起始位置
ssize_t read(int fd,void *buf,size_t nbytes);//成功返回读到的字节数,失败返回-1,若达到文件尾部//返回0
ssize_t write(int fd,const void *buf,size_t nbytes);//成功返回已写字节数,失败返回-1
一个例子,从标准输入读,写到标准输出。
#include "apue.h"
#define BUFFSIZE 4096
int main()
{
int n;
char buf[BUFFSIZE];
while((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
{
if(write(STDOUT_FILENO,buf,n)!=n)
{
err_sys("write error\n");
}
}
if(n<0) err_sys("read errror\n");
return 0;
}
内核文件数据结构
内核使用三种数据结构表示打开的文件。
(1)每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符各占一项,每个文件描述符相关联的是文件描述符标志和指向一个文件表项的指针。(进程级的文件描述符表)
(2)内核为所有打开的文件维持一张文件表,每个文件表项包含文件状态标志、当前文件偏移量、指向该文件v节点表项的指针。(系统级的打开文件表)
(3)每个打开文件(或设备)都有一个v节点结构,v节点包含文件类型和对文件进行各种操作函数的指针,对于大多数文件,v节点还包括了i节点,i节点包括文件所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针。(文件系统的i-node表)
如果两个独立的进程各自打开同一个文件,则如下图。打开该文件的每个进程都获得各自的一个文件表项,但对一个给定的文件只有一个v节点表项,所以每个进程都有他自己对该文件的当前偏移量。当dup函数和fork函数调用时,就会有多个文件描述符项指向同一个文件表项。
文件描述符标志和文件状态标志在作用范围方面区别:前者只用于一个进程一个描述符,后者应用于指向该给定文件表项的任何进程中的所有描述符。 下面这张图更加能够展现其联系。
dup和fcntl函数
调用dup函数可以复制一个现有的文件描述符,函数返回的新文件描述符一定是当前可用文件描述符中最小数值,并且新的文件描述符与原描述符指向同一个文件表项。
fcntl函数可以改变已经打开文件的属性。下面例子修改文件属性,添加O_APPEND标志
int flags;
flags=fcntl(fd,F_GETFL);
if(flags==-1)
printf("error get\n");
flags |= O_APPEND;
if(fcntl(fd,F_SETFL,flags)==-1)
printf("error set\n");
ioctl函数是IO操作杂物箱,使用的最多的是终端IO。
int dup(int fd);
int dup2(int fd,int fd2);//用fd2指定新描述符的值;
int fcntl(int fd,int cmd,...);
//有五种功能
(1)复制一个已有描述符 cmd=F_DUPFD
(2)获取/设置文件描述符标志 cmd=F_GETFD/F_SETFD
(3)获取/设置文件状态标志 cmd=F_GETFL/F_SETFL
(4)获取/设置异步IO所羽泉 cmd=F_DETOWN/F_SETOWN
(5)获取/设置记录锁 cmd=F_GETLK/F_SETLK
dp(fd);等效于 fcntl(fd,F_DUPFD,0);
int ioctl(int fd,int request);//失败返回-1,成功返回其他值
一个例子,实现cp file1 file2 功能(简略)
#include <sys/stat.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5 #define BUFSIZE 300
6
7
8 int main(int argc,char *argv[])
9 {
10 if(argc<3)
11 {
12 printf("argc not enough\n");
13 }
14 char *name=argv[1];
15 char *des=argv[2];
16 int fd=open(argv[1],O_RDONLY);
17 int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
18 if(fd==-1) printf("open source error\n");
19 char buf[BUFSIZE+1];
20 int len=read(fd,buf,BUFSIZE);
21 if(len==-1) printf("read error\n");
22 printf("%d\n",len);
23 buf[len]='\0';
24 int len2=write(fd2,buf,len+1);
25 if(len2==-1) printf("write error \n");
26
27 printf("%d\n",len2);
28 printf("ok\n");
29 close(fd);
30 close(fd2);
31 return 0;
32 }
参考 《APUE》、《TLPI》