基本看完了《unix/linux编程实践教程》,实现了几个小项目,觉得很不过瘾,书中对很多system call的细节和原理都没讲。在尝试看linux内核分析的书时发现很多困难,所以拿起apue,继续打基础。读着读着,对apue上瘾了。这是题外话,就说这么多。
一.我对文件I/O的理解
文件I/O最基本的两个函数就是read和write,书中也叫做unbuffered I/O。刚看到这个"unbuffered",我就奇怪,操作系统不是对所有的输入输出都会做缓存吗(delayed write),为什么还会存在unbuffered?但接着我就明白了,这里的ubuffered,是指的是针对与read和write本身来说,他们是没有缓存机制,比如read(fd,temp,100),在读够100个字节后或者遇到文件EOF后就返回,非常单纯。而C库函数中的fread和fwrite,就是利用缓存技术来调用read和write,可以说是buffered I/O。
二.文件描述符
文件描述符是一个整型,可以理解为一个指向文件的指针(注:整型也可以理解为一个指针,并不是只有void*才可以被叫做指针)。需要注意的就是,一个进程在结束的时候会关闭所有打开的文件描述符,但是0,1,2除外。0,1,2,分别代表标准输入流,标准输出流和标准错误流。这3个文件描述符是由kernel打开和进行管理的,不需要我们来打开和关闭。
三.文件共享机制
比较重要的就是file table的理解。file table包括了file status flags,the current file offset,a pointer to the v-node table entry for the file.比如对文件temp,调用一次open后,有fd1指向temp,也就有了一个file table。再对temp调用一次open后,有fd2指向temp,再生成一个file table(可以用dup,dup2实现,但又有不同,见下文)。这时,排除其他因素,我们的系统中有一个temp文件,2个文件描述符:fd1,fd2,两个file table(表示同一个文件)。需要注意的是,如果对write操作加上了排他锁,就无法用O_WRONLY同时打开两个文件描述符了。但可以用O_RDONLY同时打开两个文件描述符,fd1,fd2可以同时读,而相应的,fd1指向的file table的current file offset自然可以不同于fd2指向的,这也就可以很容易地理解多个file table的作用了。
四.system call原理与分析
我喜欢用函数来总结所学的东西,感觉比较实在。
1.lseek(fd,5,SEEK_SET).修改current offset.返回新的offset.
2.open(file,O_RDWR).不赘述细节,详见man。先写一个问题,就是O_SYNC,我无法利用代码来感受这个option,如果您能帮忙,小弟我非常感激。值得注意的就是O_APPEND,这个很强大的东西,跟>>重定向的"添加到末尾"是一样的。特别注意,当open(file,O_RDWR | O_APPEND)后,write与read得到的待遇并不相同。每个被打开的文件都有一个current offset,可以用lseek对这个offset进行修改。先贴出代码:
int main() { int fd; char buf[100]="abcde"; //注:temp中只有一行aaaaabbbbbccccc. if( (fd=open("temp",O_RDWR|O_APPEND))<0 ) err_sys("error open!"); if(lseek(fd,5,SEEK_SET)==-1) err_sys("error lseek!"); if( read(fd,buf,5)<0 ) err_sys("error read!"); buf[5]='\0'; printf("%s\n",buf); //if(write(fd,buf,5)!=5) // err_sys("write error"); return 0; }
当利用lseek修改current offset为5时,再read,结果如下:
bbbbb
这说明read可以不受O_APPEND的影响,可以自由读取文件中任意位置的内容。将上述代码这样修改后:
int main() { int fd; char buf[100]="abcde"; //注:temp中只有一行aaaaabbbbbccccc. if( (fd=open("temp",O_RDWR|O_APPEND))<0 ) err_sys("error open!"); //if(lseek(fd,5,SEEK_SET)==-1) // err_sys("error lseek!"); //if( read(fd,buf,5)<0 ) // err_sys("error read!"); //buf[5]='\0'; //printf("%s\n",buf); if(write(fd,buf,5)!=5) err_sys("write error"); return 0; }
temp文件结果如下:
aaaaabbbbbccccc abcde
啊,lseek被O_APPEND屏蔽了?哈哈,不是被屏蔽了。原理是这样的,在每次write之前,系统都因为O_APPEND而将current offset设置为文件的末尾,这就说明了上述结果。
3.fcntl(fd,F_GETFL,0).修改文件属性。
4.dup(fd1). dup2(fd1,fd2).都返回复制后的文件描述符。与两次open得到两个文件描述符不同的是,dup2(fd1,fd2)得到的fd2与fd1共享一个file table,而前者是分别拥有一个file table。自然,用read,write在处理fd1,fd2时,就会对两者都起作用了。dup,dup2都是原子操作,而
close(fd2); fcntl(fd1,F_DUPFD,fd2);
可以实现dup2的功能,但会破坏其原子性。
五.总结
基础,原理,这都是需要掌握的。之前我有些激进了,想尽快去读linux内核,现在看来,还有很长的路要走。
六.我的一些笔记(有些摘自apue)
1.off_t 是一个long int
2.read,write这两个system call在使用时会改变当前的offset
3.read,write starts at the file's current offset.
4. size_t 是为了方便系统之间的移植而定义的,是unsigned int. ssize_t是signed size_t
5.the lseek fnction modifies only the current file offset in the file table entry . No I/O takes place.
6.Any operation that requires more that one function call cannot be atomic,as there is always the possibility that the kernel can temporarily suspend the process between the two function calls.
7.creat(filename,FILE_MODE).如果filename文件已经存在,将会删除filename的所有内容
8.delayed write:When we write data to a file ,the data is normally copied by the kernel into one of its buffers and queued for writing to disk at some later time.
9.cat不直接接受标准输入。 比如 more file1 | cat file2 的结果只会输出file2,而这样more file1 | cat - file2就会输出file1和file2了。注:-可以用/dev/fd/0替代,表示连结标准输入.
10.用read,write系统调用时,用户没有对输入输出做缓存,但是内核是会对所有输入输出做缓存的。可以这样说,在这个进程的user time没有进行缓存,但是system time在进行缓存
11.O_SYNC会一直起作用.而fsync和fdatasync是一次性的操作。
12.每次open系统调用后,都会生成一个新的file table entry.
13.注意write,read,printf等函数的'\0'问题
14.2>&1 是将2重定向到1,也就是将stderr重定向到stdout,就是让stderr也指向stdout。再抽象一些,2>&1,就是将文件描述符2重定向到文件描述符1所指向的文件,使得二者指向同一个文件。
参考资料:apue(unix环境高级编程)
如果您觉得我的文章对您有帮助,请您推荐一下,非常感谢!