文件描述符
files_struct *file结构体记录在task_struct结构体中,*file指向文件描述符。
一个进程默认打开三个文件描述符
1. STDIN_FILENO 0 2. 3. STDOUT_FILENO 1 4. 5. STDERR_FILENO 2
新打开文件返回文件描述符表中未使用的最小文件描述符
一、文件打开与关闭
open函数打开文件,open原型如下:
1. int open(const char * pathname, int flags); 2. 3. int open(const char * pathname, int flags, mode_t mode);
- 只有用到O_CREAT参数的时候,才会使用mode参数
- 返回值:打开成功返回文件描述符,错误返回-1.
- flags必选项:以下三个常数中必须指定一个,且仅允许指定一个。
* O_RDONLY 只读打开
* O_WRONLY 只写打开
* O_RDWR 可读可写打开
以下可选项可以同时指定0个或多个,和必选项按位或起来作为flags参数。可选项有很多,这里只介绍一部分,其它选项可参考open(2)的Man Page:
* O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾
而不覆盖原来的内容。
* O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该
文件的访问权限。
* O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
* O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)为0字节。
* O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/O)
关闭文件用close函数,close函数原型如下:
int close (int fd);
close返回值,关闭成功返回0,错误返回-1.
二、文件读写
read函数从打开的设备或文件中读取数据
ssize_t read(int fd, void *buf, size_t count)
- 返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0.
- fd是要读取的文件的文件描述符,参数count是请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。
write函数向打开的设备或文件中写数据
ssize_t write(int fd, const void *buf, size_t count);
返回值:成功返回写入的字节数,出错返回-1并设置errno
下面使用文件IO来实现一个cp命令类似的功能
1. #include <stdio.h> 2. #include <sys/types.h> 3. #include <sys/stat.h> 4. #include <fcntl.h> 5. #include <stdlib.h> 6. #include <unistd.h> 7. #include <string.h> 8. 9. #define SIZE 8192 10. 11. int main(int argc, char * argv[]) 12. { 13. char buf[SIZE]; 14. int fd_src, fd_dest, len; 15. 16. if(argc < 3){ 17. printf("./mycp src dest.\n"); 18. exit(1); 19. } 20. 21. 22. fd_src = open(argv[1], O_RDONLY); 23. fd_dest = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0777); 24. /* 25. *成功返回读到的字节数 26. *读到文件末尾返回0 27. *读失败返回-1 28. */ 29. while((len = read(fd_src, buf, sizeof(buf))) >0 ) 30. write(fd_dest, buf, len); 31. 32. close(fd_src); 33. close(fd_dest); 34. 35. return 0; 36. }
执行结果如下:
可以看到拷贝的2.txt与1..txt文件一样
三、文件定位
lseek可移动当前文件读写位置,函数原型:
off_t lseek (int fd, off_t offset, int whence)
- lseek()函数的作用是,重置文件描述符fd关联打开的文件偏移量,把这个偏移量重置为参数offset,在重置时要根据指令whence来做,whence可包含的指令有三个
SEEK_SET 表从文件的起始位置偏移offset大小的字节
SEEK_CUR 表从当前位置加上offset大小的字节
SEEK_END 表从未见末尾加上offset大小的字节
- 返回值:
lseek返回值:当lseek成功完成时,lseek()返回的偏移量是距离文件开头的偏移量,大小是以自己为单位衡量。如果错误返回值为-1,并设置errno的值
例1:用lseek拓展一个文件,注意一定要有一次写操作
1. #include <sys/types.h> 2. #include <sys/stat.h> 3. #include <fcntl.h> 4. #include <unistd.h> 5. #include <stdio.h> 6. #include <errno.h> 7. #include <stdlib.h> 8. 9. int main(void) 10. { 11. int fd = open("filetest", O_RDWR); 12. if(fd < 0){ 13. perror("open filetest"); 14. exit(-1); 15. } 16. 17. //拓展一个文件,一定要有一次写操作 18. lseek(fd, 0x1000, SEEK_SET); 19. //write(fd, "a", 1); 20. close(fd); 21. 22. return 0; 23. }
注释后://write(fd, "a", 1);
去掉注释
例2:用lseek输出一个文件的大小
1. #include <sys/types.h> 2. #include <sys/stat.h> 3. #include <fcntl.h> 4. #include <unistd.h> 5. #include <stdio.h> 6. #include <errno.h> 7. #include <stdlib.h> 8. 9. int main(void) 10. { 11. int file_size; 12. int fd = open("abc", O_RDWR); 13. if(fd < 0){ 14. perror("open abc"); 15. exit(-1); 16. } 17. 18. fd = open("hello", O_RDWR); 19. if(fd < 0){ 20. perror("open hello"); 21. exit(-1); 22. } 23. 24. file_size = lseek(fd, 0, SEEK_END); 25. printf("hello size = %d.\n", file_size); 26. close (fd); 27. 28. return 0; 29. }
执行结果
四、设置/获取文件控制属性
fcntl函数原型如下:
1. #include <unistd.h> 2. 3. #include <fcntl.h> 4. 5. int fcntl(int fd, int cmd); 6. 7. int fcntl(int fd, int cmd, long arg); 8. 9. int fcntl(int fd, int cmd, struct flock *lock);
先看一个例程,这里重新打开STDIN_FILENO,并添加非阻塞的属性,当用户有输入时,直接在标准输出上显示用户的输入,实现类似cat的功能(只不过这里是非阻塞)
1. #include <unistd.h> 2. #include <sys/types.h> 3. #include <sys/stat.h> 4. #include <string.h> 5. #include <stdlib.h> 6. #include <errno.h> 7. #include <string.h> 8. #include <fcntl.h> 9. #include <error.h> 10. #include <stdio.h> 11. 12. #define MSG_TRY "try again\n" 13. 14. int main(void) 15. { 16. char buf[10]; 17. int fd, n; 18. fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); 19. if(fd < 0){ 20. perror("open /dev/tty"); 21. exit(1); 22. } 23. 24. tryagain: 25. n = read(fd, buf, 10); 26. if(n < 0){ 27. if(errno == EAGAIN){ 28. sleep(1); 29. write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); 30. goto tryagain; 31. } 32. perror("read /dev/tty"); 33. exit(1); 34. } 35. write(STDOUT_FILENO, buf, n); 36. close(fd); 37. 38. return 0; 39. }
为什么我们不直接对STDIN_FILENO做非阻塞read,而要重新open一遍/dev/tty呢?因为STDIN_FILENO在程序启动时已经被自动打开了,而我们需要在调用open时指定O_NONBLOCK标志。
这里用fcntl函数改变一个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File Status Flag),而不必重新open文件。
所以上面的代码可修改如下,用fcntl函数实现
1. #include <unistd.h> 2. #include <fcntl.h> 3. #include <errno.h> 4. #include <string.h> 5. #include <stdlib.h> 6. 7. #define MSG_TRY "try again\n" 8. 9. int main(void) 10. { 11. char buf[10]; 12. int n; 13. int flags; 14. 15. flags = fcntl(STDIN_FILENO, F_GETFL); 16. flags |= O_NONBLOCK; 17. 18. if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) { 19. perror("fcntl"); 20. exit(1); 21. } 22. 23. tryagain: 24. n = read(STDIN_FILENO, buf, 10); 25. if (n < 0) { 26. if (errno == EAGAIN) { 27. sleep(1); 28. write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); 29. goto tryagain; 30. } 31. 32. perror("read stdin"); 33. exit(1); 34. } 35. 36. write(STDOUT_FILENO, buf, n); 37. 38. return 0; 39. }