系统调用
系统调用是由操作系统实现并提供给外部应用程序的编程接口API,是应用程序同系统之间数据交互的桥梁。
open函数
作用是打开文件,返回文件描述符
函数原型
常用参数
需要使用头文件<fcntl.h>
只读、只写、可读写、追加、创建、判断是否存在、截断(将文件原本的内容丢弃)、非阻塞
文件权限 = mode & ~umask
read&write函数
#include <unistd.h>
函数原型:
read函数
参数:fd 文件描述符 buf 存数据的缓冲区 count 缓冲区大小
返回值:成功——读到的字节数 失败—— -1,设置errno 0——读到了文件末尾
返回-1且errno = EAGIN 或 EWOULDBLOCK时,并非read失败,而是read在以非阻塞方式读一个设备/网络文件,且文件无数据。
write函数
参数:fd 文件描述符 buf 待写出数据的缓冲区 count 数据大小
返回值:成功——写入的字节数 失败—— -1,设置errno
使用系统调用read和write实现模拟cp操作:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#define N 1
int main(int argc, char *argv[]){
char buf[N];
int n = 0;
int fd1 = open(argv[1], O_RDONLY);
if(fd1 == -1) {
perror("open argv1 error");
exit(1);
}
int fd2 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0664);
if(fd2 == -1){
perror("open argv2 error");
exit(1);
}
while ((n = read(fd1, buf, 1024)) != 0){
if(n<0){
perror("read error");
break;
}
write(fd2, buf, n);
}
close(fd1);
close(fd2);
return 0;
}
使用库函数fputc&fgetc完成拷贝程序:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
FILE *fp,*fp_out;
int n;
fp = fopen("dict.txt","r");
if(NULL== fp)
{
perror("fopen dict.txt error");
exit(1);
}
fp_out = fopen("dict.cp","w");
if(fp_out == NULL)
{
perror("fopen dict.cp error");
exit(1);
}
while((n = fgetc(fp)) != EOF)
{
fputc(n,fp_out);
}
fclose(fp);
fclose(fp_out);
return 0;
}
两者时间比较,使用库函数快于系统调用。使用strace命令追踪程序执行时的系统调用,使用read/write为一个字节一个字节的读写,而使用fputc/fgetc一次读写4k。
预读入缓输出机制
缓输出:
黑线代表内核区kernal和用户区user的分隔,需要通过系统调用才能跨过该线。
耗时在于跨越用户和内核(用时<磁盘访问)
write会多次切换用户区和内核区,指定N为1时(左黑框),会一个字节一个字节的写入内核buf
标准IO函数fputc自带用户缓冲区(蓝色方框),满4K后再进入内核,因此切换少,速度快
read&write函数被称为Unbuffered I/O,因为其无用户级缓冲区,但是可以通过定义buf(左黑框)的大小N来提升写入的速度。
预读入:
从磁盘读取数据到内核空间,先缓冲一批到缓冲区,然后读入用户。
尽量使用库函数(速度),不方便使用缓输出机制(处理即时消息)的情况,使用系统调用。
文件描述符
PCB进程控制块:本质是结构体,描述进程消息
PCB中有一个指针指向文件描述符表,其中的每个元素均为指针,指向文件结构体struct file(文件信息),文件描述符即为该表中的下标(0~1023)。
file结构体:
阻塞和非阻塞
阻塞:发起调用完成某功能,若不具备条件则一直等待,直到满足条件完成
非阻塞:发起调用完成某功能,具备条件直接输出,不具备条件报错返回
产生阻塞(block)的场景:读设备文件,读网络文件(读常规文件无阻塞概念)
/dev/tty 终端文件
open("/dev/tty", O_RDWR|O_NONBLCK) --设置/dev/tty为非阻塞状态(默认为阻塞)
非阻塞配合循环使用,设置timeout值防止无限调用。
fcntl函数
改变一个已经打开的文件的访问控制属性(不需要重新打开文件)
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK
fcntl(STDIN_FILENO, fd, F_SETFL);
获取文件状态:F_GETFL
设置文件状态:F_SETFL
lseek函数
off_t lseek(int fd, off_t offset, int whence);
参数:1. 文件描述符 2. 偏移量 3. 起始偏移地址
返回值: 成功:较起始位置向后偏移量 失败:-1,errno
whence取值:SEET_SET(开头), SEEK_CUR(当前), SEEK_END(结尾)
应用:
1. 文件的”读“和”写“使用的是同一偏移,使用lseek来修改文件偏移量(读写位置)
2. 使用lseek获取文件大小
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
int main(int argc, char *argv[]){
int fd = open(argv[1], O_RDWR);
if(fd == -1){
perror("open error");
exit(1);
}
int length = lseek(fd, 0, SEEK_END);
printf("file size: %d", length);
return 0;
}
3. 使用lseek拓展文件大小,真正改变文件大小需要IO操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
int main(int argc, char *argv[]){
int fd = open(argv[1], O_RDWR);
if(fd == -1){
perror("open error");
exit(1);
}
int length = lseek(fd, 111, SEEK_END);
printf("file size: %d", length);
write(fd, "a", 1);
close(fd);
return 0;
}
使用truncate函数直接拓展文件: int ret = truncate("dict.cp", 250);
传入/传出参数
传入参数:1. 指针作为函数参数
2. 通常由const关键字修饰
3. 指针指向有效区域,在函数内部做读操作
传出参数:
1. 指针作为函数参数
2. 在函数调用前,指针指向的空间可以无意义但必需有效
3. 在函数内部做写操作
4. 在函数调用结束后充当函数返回值
传入传出参数:
1. 指针作为函数参数
2. 在函数调用前,指针指向的空间有实际意义
3. 在函数内部,先做读操作后做写操作
4. 在函数调用结束后充当函数返回值
eg.
char *strcpy(char *dest //传出, const char *src //传出);
char *strtok_r(char *str, const char *delim, char **savept //传入传出);