Linux 开发学习 (二) taro Posted on Mar 18 2021 Linux 笔记 开发 # 系统调用 系统调用 内核提供的函数 库调用 程序库中的函数 ## open函数 manpage 第二卷,open函数如下,有两个版本的 ![](/api/file/getImage?fileId=6050d17bc9095f000d00000e) 返回一个文件描述符,理解为整数,出错返回-1 - pathname 文件路径 - flags 权限控制,只读,只写,读写。O_RDONLY, O_WRONLY, O_RDWR 第二个open 多了一个mode参数,用来指定文件的权限,数字设定法 文件权限 = mode & ~umask open常见错误: - 打开文件不存在 - 以写方式打开只读文件(权限问题) - 以只写方式打开目录 当open出错时,程序会自动设置errno,可以通过strerror(errno)来查看报错数字的含义 以打开不存在文件为例: ![](/api/file/getImage?fileId=6050d17bc9095f000d000007) 执行该代码,结果如下: ![](/api/file/getImage?fileId=6050d17bc9095f000d000006) open函数: ```c #include <unistd.h> int open(char *pathname, int flags) ``` 参数: - pathname: 欲打开的文件路径名 - flags:文件打开方式: `#include <fcntl.h>` O_RDONLY|O_WRONLY|O_RDWR O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK .... 返回值: - 成功: 打开文件所得到对应的 文件描述符(整数) - 失败: -1, 设置errno ```c int open(char *pathname, int flags, mode_t mode) 123 775 ``` 参数: - pathname: 欲打开的文件路径名 - flags:文件打开方式: O_RDONLY|O_WRONLY|O_RDWR|O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK .... - mode: 参数3使用的前提, 参2指定了 O_CREAT。 取值8进制数,用来描述文件的 访问权限。 rwx 0664 创建文件最终权限 = mode & ~umask 返回值: - 成功: 打开文件所得到对应的 文件描述符(整数) - 失败: -1, 设置errno close函数: ```c int close(int fd); ``` ## read函数 write 函数 read函数: ```c ssize_t read(int fd, void *buf, size_t count); ``` 参数: - fd:文件描述符 - buf:存数据的缓冲区 - count:缓冲区大小 返回值: - 0:读到文件末尾。 - 成功; > 0 读到的字节数。 - 失败: -1, 设置 errno - -1: 并且 errno = EAGIN 或 EWOULDBLOCK, 说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。 write函数: ```c ssize_t write(int fd, const void *buf, size_t count); ``` 参数: - fd:文件描述符 - buf:待写出数据的缓冲区 - count:数据大小 返回值: - 成功; 写入的字节数。 - 失败: -1, 设置 errno 用read和write实现一个copy函数: ![](/api/file/getImage?fileId=6050d17bc9095f000d000003) ![](/api/file/getImage?fileId=6050d17bc9095f000d000002) ![](/api/file/getImage?fileId=6050d17bc9095f000d00000a) ## 错误处理 可以在复制函数里加入错误检测: ```c if(fd1 == -1){ perror(“open argv[1] error”); exit(1); } ``` 错误处理函数: 与 errno 相关。 ```c printf("xxx error: %d\n", errno); char *strerror(int errnum); printf("xxx error: %s\n", strerror(errno)); void perror(const char *s); perror("open error"); ``` ## 系统调用和库函数比较—预读入缓输出 ![](/api/file/getImage?fileId=6050d17bc9095f000d000008) ![](/api/file/getImage?fileId=6050d17bc9095f000d00000c) 下面写两个文件拷贝函数,一个用read/write实现,一个用fputc/fgetc实现,比较速度 fputc/fgetc ![](/api/file/getImage?fileId=6050d17bc9095f000d000010) 修改read那边的缓冲区,一次拷贝一个字符,比上面慢很多 ![](/api/file/getImage?fileId=6050d17bc9095f000d00000d) ### 原因分析 read/write这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。 fgetc/fputc,有个缓冲区,4096,所以它并不是一个字节一个字节地写,内核和用户切换就比较少 预读入,缓输出机制。 所以系统函数并不是一定比库函数牛逼,能使用库函数的地方就使用库函数。 标准IO函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。 ## 文件描述符 ![](/api/file/getImage?fileId=6050d17bc9095f000d000005) 文件描述符是指向一个文件结构体的指针 **PCB进程控制块**:本质是结构体。 **成员**:文件描述符表。 **文件描述符**:0/1/2/3/4..../1023 表中可用的最小的。 - 0 - STDIN_FILENO - 1 - STDOUT_FILENO - 2 - STDERR_FILENO ## 阻塞和非阻塞 阻塞、非阻塞: 是设备文件、网络文件的属性。 产生阻塞的场景。 读设备文件。读网络文件。(读常规文件无阻塞概念。) /dev/tty -- 终端文件。 open("/dev/tty", O_RDWR|O_NONBLOCK) --- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态) 一个例子,从标准输入读,写到标准输出: ![](/api/file/getImage?fileId=6050d17bc9095f000d00000f) 执行程序,就会发现程序在阻塞等待输入 ![](/api/file/getImage?fileId=6050d17bc9095f000d00000b) 下面是一段更改非阻塞读取终端的代码: ```c 1. #include <unistd.h> 2. #include <fcntl.h> 3. #include <stdlib.h> 4. #include <stdio.h> 5. #include <errno.h> 6. #include <string.h> 7. 8. #define MSG_TRY "try again\n" 9. #define MSG_TIMEOUT "time out\n" 10. 11. int main(void) 12. { 13. char buf[10]; 14. int fd, n, i; 15. 16. fd = open("/dev/tty", O_RDONLY|O_NONBLOCK); 17. if(fd < 0){ 18. perror("open /dev/tty"); 19. exit(1); 20. } 21. printf("open /dev/tty ok... %d\n", fd); 22. 23. for (i = 0; i < 5; i++){ 24. n = read(fd, buf, 10); 25. if (n > 0) { //说明读到了东西 26. break; 27. } 28. if (errno != EAGAIN) { //EWOULDBLOCK 29. perror("read /dev/tty"); 30. exit(1); 31. } else { 32. write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); 33. sleep(2); 34. } 35. } 36. 37. if (i == 5) { 38. write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT)); 39. } else { 40. write(STDOUT_FILENO, buf, n); 41. } 42. 43. close(fd); 44. 45. return 0; 46. } ``` ![](/api/file/getImage?fileId=6050d17bc9095f000d000004) ## fcntl改文件属性 fcntl用来改变一个【已经打开】的文件的 访问控制属性 重点掌握两个参数的使用, F_GETFL,F_SETFL ```c int fcntl (int fd, int cmd, ...) ``` - fd 文件描述符 - cmd 命令,决定了后续参数个数 - int flgs = fcntl(fd, F_GETFL); flgs |= O_NONBLOCK fcntl(fd, F_SETFL, flgs); - 获取文件状态: F_GETFL - 设置文件状态: F_SETFL 终端文件默认是阻塞读的,这里用fcntl将其更改为非阻塞读 ![](/api/file/getImage?fileId=6050d17bc9095f000d000009) Linux 开发学习 (三) PHP-disable_functions-绕过手札