1. 文件描述符
Linux系统将所有设备都当作文件来处理,而Linux用文件描述符来标识每个文件对象。
文件描述符是一个非负整数,用于唯一标识计算机操作系统中打开的文件。 它描述了数据资源,以及如何访问该资源。
下面这张图, 不同进程拥有自己独立的PCB,PCB是存放进程管理和控制信息数据的一个结构体,其中包含了管理文件的指针,每个进程都会默认打开三个文件,对应文件描述符为0,1,2.
标准输入(standard input)的文件描述符是 0(STDIN_FILENO),
标准输出(standard output)的文件描述符是 1(STDOUT_FILENO),
标准错误(standard error)的文件描述符是 2(STDERR_FILENO)。
POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、1、2。
可用文件I/O函数很多,包括:打开文件、读写文件等。大多数linux文件IO只需要用到5个函数:open、read、write、lseek、close。
2. 基本API
2.1 open
2.1.1 open函数原型以及简单使用实例
在man手册中可以看到open函数的定义
open函数有重载版本,是否有第三个参数,取决于第二个参数是否有O_CREAT。其他不做介绍,具体看man手册。
1. #include <sys/stat.h> 2. #include <sys/types.h> 3. #include <fcntl.h> 4. #include <stdio.h> 5. #include <stdlib.h> 6. #include <unistd.h> 7. 8. int main(int argc, char * argv[]) 9. { 10. int fd; 11. if(argc < 2) 12. { 13. printf("./app filename\n"); 14. exit(1); 15. } 16. 17. 18. fd = open(argv[1], O_CREAT, 0777); 19. printf("fd = %d\n", fd); 20. close(fd); 21. 22. return 0; 23. }
执行结果如下:
针对这段code,需要注意两个问题:
1) exit和return的区别
调用任何函数的return,都是返回这个值;调用main函数的return,程序结束
在任何函数调用exit都是导致程序结束
2) open函数的第三个参数,在code给新创建文件的权限是777,但是实际创建文件的权限是775
这是因为系统默认的umask设置值是2,在open第三个参数指定权限的时候需要用系统的umask值取反一下才可以。
2.1.2 最大打开文件数
1. #include <sys/stat.h> 2. #include <sys/types.h> 3. #include <fcntl.h> 4. #include <stdio.h> 5. #include <stdlib.h> 6. 7. int main(int argc, char * argv[]) 8. { 9. int fd; 10. char name[1024]; 11. int i = 0; 12. 13. while(1) 14. { 15. sprintf(name, "file%d", ++i); 16. fd = open(name, O_CREAT, 0777); 17. if(fd == -1) 18. { 19. exit(1); 20. } 21. printf("%d\n", i); 22. } 23. 24. 25. return 0; 26. }
执行后终端会输出fd为1-1021,因为还有3个文件描述符是默认打开的。同时可以看到终端下有file1-file1021个文件生成。
通过ulimit -a指令,可以看到最大打开文件数为1024.
可以通过ulimit -n修改最大打开文件数
2.2 read/write
2.2.1 read/write函数原型以及简单使用
1. #include <sys/stat.h> 2. #include <sys/types.h> 3. #include <fcntl.h> 4. #include <stdio.h> 5. #include <stdlib.h> 6. #include <unistd.h> 7. #include <string.h> 8. 9. int main(int argc, char * argv[]) 10. { 11. int fd; 12. char buf[1024] = "hello world"; 13. if(argc < 2) 14. { 15. printf("./app filename\n"); 16. exit(1); 17. } 18. 19. 20. fd = open(argv[1], O_CREAT, 0777); 21. printf("fd = %d\n", fd); 22. 23. write(fd, buf, strlen(buf)); 24. 25. close(fd); 26. 27. return 0; 28. }
2.2.2 实现文件拷贝code
1. #include <sys/stat.h> 2. #include <sys/types.h> 3. #include <fcntl.h> 4. #include <unistd.h> 5. #include <stdlib.h> 6. #include <string.h> 7. 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. { 18. printf("./mycp src dest\n"); 19. exit(1); 20. } 21. 22. fd_src = open(argv[1], O_RDONLY); 23. fd_dest = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0644); 24. 25. /* 26. *成功返回读到字节数 27. *读到文件末尾返回0 28. *读失败返回-1 29. */ 30. while(len == read(fd_src, buf, sizeof(buf)) > 0) 31. { 32. write(fd_dest, buf, len); 33. } 34. close(fd_src); 35. close(fd_dest); 36. 37. return 0; 38. }
2.3 lseek
每个打开的文件都记录着当前读写位置,打开文件时读写位置是0,表示文件开头,通常读写多少个字节就会将读写位置往后移多少个字节。但是有一个例外,如果以O_APPEND方
式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。lseek和标准I/O库的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。
lseek函数原型:
off_t lseek(int fd, off_t offset, int whence);
下面的代码展示lseek的两个作用(只是lseek附带的功能)
//用lseek拓展一个文件
//用lseek获取文件大小
1. #include <sys/stat.h> 2. #include <sys/types.h> 3. #include <fcntl.h> 4. #include <stdio.h> 5. #include <stdlib.h> 6. #include <errno.h> 7. #include <unistd.h> 8. #include <stdio.h> 9. 10. int main(void) 11. { 12. int fd = open("abc", O_RDWR); 13. if(fd < 0) 14. { 15. perror("open abc"); 16. exit(-1); 17. } 18. 19. lseek(fd, 0x1000, SEEK_SET); 20. 21. //拓展一个文件,一定要有一次写操作 22. write(fd, "a", 1); 23. close(fd); 24. 25. //用lseek获取文件大小 26. fd = open("hello", O_RDWR); 27. if(fd < 0) 28. { 29. perror("open hello"); 30. exit(-1); 31. } 32. printf("hello size = %d\n", lseek(fd, 0, SEEK_END)); 33. close(fd); 34. return 0; 35. }
2.4 fcntl
fcntl的作用:
fcntl函数改变一个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File Status Flag),而不必重新open文件。
fcntl函数原型
1. int fcntl(int fd, int cmd); 2. int fcntl(int fd, int cmd, long arg); 3. int fcntl(int fd, int cmd, struct flock *lock);
这个函数和open一样,也是用可变参数实现的,可变参数的类型和个数取决于前面的cmd参数。下面的例子使用F_GETFL和F_SETFL这两种fcntl命令改变STDIN_FILENO的属性,加上O_NONBLOCK选项。
1. #include <unistd.h> 2. #include <fcntl.h> 3. #include <errno.h>1.8节 ioctl 11 4. #include <string.h> 5. #include <stdlib.h> 6. #define MSG_TRY "try again\n" 7. int main(void) 8. { 9. char buf[10]; 10. int n; 11. int flags; 12. flags = fcntl(STDIN_FILENO, F_GETFL); 13. flags |= O_NONBLOCK; 14. if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) { 15. perror("fcntl"); 16. exit(1); 17. } 18. 19. tryagain: 20. n = read(STDIN_FILENO, buf, 10); 21. if (n < 0) 22. { 23. if (errno == EAGAIN) 24. { 25. sleep(1); 26. write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); 27. goto tryagain; 28. } 29. perror("read stdin"); 30. exit(1); 31. } 32. write(STDOUT_FILENO, buf, n); 33. return 0; 34. }
2.5 ioctl
设置和获取设备文件的物理特性时,用ioctl
这个API主要是在设备驱动的时候用,用的时候在具体看吧。。