系统调用
系统调用进入内核
库函数fputc()和系统调用write性能比较
问题抛出:向文件中每次写入一个字节的数据,使用库函数fputc()快还是系统调用write()快?
从上图看,库函数使用系统调用向磁盘文件写入数据,应该是系统调用快?
使用strace命令可以跟踪程序执行,查看调用的系统函数,测试结果为fputc()更快
原因:预读入和缓输出
操作系统内核有一个系统级缓冲,当这个系统级缓冲满了(默认4096byte),才向磁盘写入数据,因为磁盘写数据非常慢一次尽可能写入更多,减少访问磁盘次数,可以提高效率。这就是缓输出,预读入也是同样机制。
为何fputc()比write()快?原因是fgutc()内部也有一个缓冲区,当写满后(4096byte)才向操作系统发起系统调用,而直接write(),写一个字节,就发起一次系统调用,系统调用次数是使用fputc()的4096倍,频繁的内核态与用户态切换很耗时
文件描述符
PCB进程控制块:本质 结构体。成员:文件描述符表。key-value形式:key代表文件描述符,value代表一个指针,指向一个file结构体
struct file {
…
目录项指针
文件的偏移量;
文件的访问权限;
文件的打开标志;
文件内核缓冲区的首地址;
struct operations * f_op;
…
};
文件描述符: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 非阻塞状态。(默认为阻塞状态)
函数read() 返回值为-1: 并且 errno = EAGIN 或 EWOULDBLOCK,
说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。
常用文件IO函数
1. open函数
函数原型:int open(char *pathname, int flags) #include <unistd.h>
参数:
pathname: 欲打开的文件路径名
flags:文件打开方式: #include <fcntl.h>
O_RDONLY|O_WRONLY|O_RDWR O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
O_TRUNC表示将文件内容清空
O_NONBLOCK表示将文件设置为非阻塞状态
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
函数原型2: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
errno是一个系统变量,能够返回错误类型,使用strerror(errno)可以打印出具体类型
2. read 函数
函数原型:size_t read(int fd, void *buf, size_t count);
参数:
fd:文件描述符
buf:存数据的缓冲区
count:缓冲区大小
返回值:
0:读到文件末尾。
成功; > 0 读到的字节数。
失败: -1, 设置 errno
-1: 并且 errno = EAGIN 或 EWOULDBLOCK,
说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。
3. write 函数
函数原型:size_t write(int fd, const void *buf, size_t count);
参数:
fd:文件描述符
buf:待写出数据的缓冲区
count:数据大小
返回值:
成功; 写入的字节数。
失败: -1, 设置 errno
4. 错误相关函数
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");
5. fcntl函数
函数原型:int (int fd, int cmd, ...) 改变一个【已经打开】的文件的
访问控制属性。重点掌握两个参数的使用,F_GETFL 和 F_SETFL。
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd, F_SETFL, flgs);
获取文件状态: F_GETFL
设置文件状态: F_SETFL
6. lseek函数
函数原型:off_t lseek(int fd, off_t offset, int whence);
参数:
fd:文件描述符
offset: 偏移量
whence:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END
返回值:
成功:较起始位置偏移量
失败:-1 errno
应用场景:
1. 文件的“读”、“写”使用同一偏移位置。
2. 使用lseek获取文件大小
3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作(写入,读出)。
4. 使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);