1open/close函数
查看系统调用的命令:
man 2 functionName
主要查看函数名称,调用方法,描述,参数,返回值等等,先学习如何使用,在学习内部原理
文件描述符是一个整数,会一直伴随着linux各种系统调用
函数名称和功能:open 打开或者创建一个文件
头文件:
#include <sys/types.h>
#include <sys/stat.h>
(上面两个头文件可以用#include <unistd.h>代替)
#include <fcntl.h>
函数原型:
int open(const char* pathname, int flags );
int open(const char *pathname, int flags, mode_t mode);
参数:
*char pathname:**代表文件的目录;
flags: O_RDONLY 只读 O_WRONLY 只写 O_RDWR 可读可写
O_APPEND 追加 O_CREAT 创建新文件 O_EXCL 判断文件是否存在 O_TRUNC 将文件阶段为0,清空 O_NONBLOCK 非阻塞
头文件位于 #include <fcntl.h>中
创建文件时,指定文件访问权限。权限同时受到umask影响。结论为(umask默认创建文件权限的反码):
文件权限=mode&~umask
返回值:返回一个int,文件描述符,返回-1代表操作失败
#include <errno.h> errno 操作系统的全局变量???
#include <string.h> char* strerror(int errnum) 打印错误的具体信息
mode:
使用的前提是,参数2指定了O_CREAT。参数3取值取8进制数,用来描述文件的访问权限,rwx, 0664
最终创建得到的文件权限=mode&(~umask)
open函数常见错误:
1)打开文件不存在;
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char** argv)
{
int fd;
fd=open("./dict.cd",O_RDONLY);
printf("fd=%d\n",fd);
printf("errno=%d\n",errno);
printf("%s\n",strerror(errno));
close(fd);
return 0;
}
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ./open
fd=-1
errno=2
No such file or directory
2)以写方式打开只读文件(没有对应的权限);
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char** argv)
{
int fd;
fd=open("./m6.txt",O_WRONLY);
printf("fd=%d\n",fd);
printf("errno=%d\n",errno);
printf("%s\n",strerror(errno));
close(fd);
return 0;
}
#修改m6.txt文件的属性为只读
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ chmod 0444 m6.txt
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ls
dict.CP dict.txt m6.txt open open.c
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ls -l
总用量 24
-rw-r--r-- 1 daniel daniel 0 5月 10 04:24 dict.CP
-rw-rw-r-- 1 daniel daniel 0 5月 10 04:14 dict.txt
-r--r--r-- 1 daniel daniel 0 5月 10 05:39 m6.txt
-rwxrwxr-x 1 daniel daniel 16928 5月 10 04:44 open
-rw-rw-r-- 1 daniel daniel 302 5月 10 05:38 open.c
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ gcc open.c -o open2
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ./open2
fd=-1
errno=13
Permission denied
3)以只写方式打开目录(open只能打开普通文件);
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(int argc, char** argv)
{
int fd;
fd=open("./m5",O_WRONLY);
printf("fd=%d\n",fd);
printf("errno=%d\n",errno);
printf("%s\n",strerror(errno));
close(fd);
return 0;
}
#m5是一个目录文件
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ mkdir m5
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ gcc open.c -o open2
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ ./open2
fd=-1
errno=21
Is a directory
close函数原型:
int close(int fd)
功能:关闭文件描述符
参数:
fd 调用open函数得到的文件描述符;
返回值:
0代表成功 -1代表失败
错误处理函数:
与errno相关。
//打印错误的编号
printf("errno=%d\n",errno);
//打印错误的具体内容
printf("xxx error:%s\n",strerror(int errno));
void perror(const char *)
是errno和strerror的结合;头文件为#include<stdio.h>
2 read/write文件读写操作
*函数:ssize_t read(int fd, void buf, size_t count)
头文件:#include <unistd.h>
参数:fd 文件描述符 buf 缓冲区指针 count 需要读入的字节数
功能:从fd的文件描述符中读取指定count大小的字节,并且写入缓冲区,buf指针当中;当函数返回0,代表读到了文件结尾,当函数返回-1,代表发生错误;当count=0,或者提示错误,或者返回值为0;
返回值:成功,返回读入的字节数,错误,返回-1;0代表读到文件结尾;
当返回-1,且erron=EAGIN/EVOULDBLOCK时,说明不是read失败,而是以非阻塞的形式读取设备文件,而且设备没有输入
*函数:ssize_t write(int fd, const void buf, size_t count)
头文件:#include <unistd.h>
参数:fd 文件描述符 buf 待写出数据的缓冲区 count 数据大小
功能:从fd的文件描述符中读取指定count大小的字节,并且写入缓冲区,buf指针当中;当函数返回0,代表读到了文件结尾,当函数返回-1,代表发生错误;当count=0,或者提示错误,或者返回值为0;
返回值:成功,返回写入的字节数,错误,返回-1;0代表没有数据可写了
通过调用系统函数read()和write()实现shell里面的cp命令:
#include <unistd.h>
#include <fcntl.h>
//#include <stdio.h>
//从命令行接收参数,并且初始化堆栈
int main(int argc, char *argv[])
{
//创建一个1024字节的缓冲区
char buf[1024];
//用于接收读取字符的数量
int n=0;
//以只读的方式打开传入参数的第一个文件
int fd1=open(argv[1],O_RDONLY);
//以读写方式打开第二个文件,如果没有,需要创建,如果有,需要截断为0
int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0664);
//持续读取字符,直到返回值 n=0 标志读到文件尾部为止
while((n=read(fd1,buf,1024))!=0){
//将读入的字符写入文件
write(fd2,buf,n);
}
//关闭文件描述符
close(fd1);
close(fd2);
return 0;
}
3 系统调用和库函数的比较-缓冲区相关
使用系统调用函数,实现每次拷贝1个字符到另外的文件和使用库函数fputc//从表面看也是每次拷贝一个字符到另外的文件。比较两者的运行速度。
使用库函数实现的代码如下:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
FILE *fp, *fp_out;
int n;
fp=fopen(argv[1],"r");
if(fp==NULL){
perror("fopen error");
exit(1);
}
fp_out=fopen(argv[2],"w");
if(fp==NULL){
perror("fopen error");
exit(1);
}
while((n=fgetc(fp))!=EOF){
fputc(n,fp_out);
}
fclose(fp);
fclose(fp_out);
return 0;
}
使用系统调用函数实现的方法如下:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define N 1
//从命令行接收参数,并且初始化堆栈
int main(int argc, char *argv[])
{
//创建一个1024字节的缓冲区
char buf[N];
//用于接收读取字符的数量
int n=0;
//以只读的方式打开传入参数的第一个文件
int fd1=open(argv[1],O_RDONLY);
if (fd1==-1){
perror("open argv1 error");
exit(1);
}
//以读写方式打开第二个文件,如果没有,需要创建,如果有,需要截断为0
int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0664);
if (fd2==-1){
perror("open argv2 error");
exit(1);
}
//持续读取字符,直到返回值 n=0 标志读到文件尾部为止
while((n=read(fd1,buf,N))!=0){
//将读入的字符写入文件
write(fd2,buf,n);
}
//关闭文件描述符
close(fd1);
close(fd2);
return 0;
}
最后经过比较发现,方法2的耗时比方法1耗时长的多。
原因,使用strace指令可以查看程序运行时使用的系统调用
daniel@daniel-Vostro-5471:~/文档/OS/test/IO_test$ strace ./myCP2.out xajh.txt 1.txt
read(3, "\215\212\344\270\252\345\270\210\347\210\266\357\274\214\345\216\237\350\257\245\345\216\273\350\260\242\347\232\204\343\200\202"..., 4096) = 4096
write(4, " \r\n\n \350\277\231\346\227\266\344\273\244\347\213\220\345\206\262\345\267\262\345\260\206\346\201\222"..., 4096) = 4096
read(3, "\350\265\260\345\207\272\345\261\213\345\216\273\343\200\202 \r\n\n \347\233\210\347\233\210\345\276\252"..., 4096) = 4096
write(4, "\215\212\344\270\252\345\270\210\347\210\266\357\274\214\345\216\237\350\257\245\345\216\273\350\260\242\347\232\204\343\200\202"..., 4096) = 4096
经过查看得知,使用库函数C程序,底层也是调用了系统调用的read()和write()函数,但是每次操作的字节数是4096个字节;也就是说虽然fputc表面每次只写入1个字节,但每4096个字节调用1次读或者写函数。
而使用同样的命令查看系统调用C函数用到的系统调用,发现每次调用系统函数只读入或者写入一个字节;这便导致了运行速度的差异;
read(3, "\243", 1) = 1
write(4, "\243", 1) = 1
read(3, "\350", 1) = 1
write(4, "\350", 1) = 1
read(3, "\243", 1) = 1
write(4, "\243", 1) = 1
read(3, "\263", 1) = 1
write(4, "\263", 1) = 1
fopen>>>>>>4096字节>>>>>>4096字节>>>>>>4096字节>>>>>>>BINGO!
用户区 标准库函数 内核区 缓冲区 硬件(硬盘区)
open>>>>>>>>>>>>>>>>>>>>>1字节>>>>>>>>4096字节>>>>>>>BINGO!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mAecj6Wk-1642598841072)(/home/daniel/.config/Typora/typora-user-images/image-20210512224341331.png)]
使用库函数,将每次读写字节的缓冲区定义为1字节时,使得用户区和内核区交互过于频繁,而这个过程时相当耗时的。如果将read()和write()函数的字节数,修改回4096,两个程序的运行时间,应该是使用系统调用更快。
read和write函数被称为 Unbuffered I/O,指的是无用户缓冲区,但是不保证不使用内核缓冲区
预读入,缓输出机制
即使学了系统调用,能使用库函数的时候,还是尽量使用库函数。
因为库函数久经考验,效率相对较高,而对于系统调用来说,如果对于原理不深刻了解,可能会出现效率低下问题。
4 文件描述符
PCB 进程控制块 本质是一个结构体 与之相对的是TCB 线程控制块
struct task_struct{ 结构体成员 } 其中有个文件描述符表
成员中有一个指针,指针指向一个文件描述符表
文件描述符表,实际上是一个key-value表,键值对表,key是一个正整数,也就是我们见到的文件描述符,而value是一个指针,指向一个文件结构体,内部含有文件的各种信息;
主要包含文件描述符,文件读写位置和IO缓冲区3部分内容
struct file {
…
文件偏移量
文件访问权限
文件的打开标志
文件内核缓冲区的首地址
struct operations *f_op;
} 结构体中包含文件的描述信息;
查看方法:
(1)/usr/src/linux-headers-3.16.0-30/include/linux/fs.h
0 STDIN_FILENO 标准输入
1 STDOUT_FILENO 标准输出
2 STDERR_FILENO 标准错误
3
…
1023 …
单个进程最多打开1024个文件,其中0,1,2已经被标准输入,标准输出和标准错误所占用,只能使用剩余编号的最小值。
文件描述符相当于句柄。我们在程序中通过文件描述符,对对应的文件进行操作。
使用 ulimit -a 查看
5 阻塞和非阻塞
阻塞/非阻塞:
产生阻塞的场景:读设备文件和读网络文件(读常规文件没有阻塞概念)
/dev/tty --终端文件
读常规的文件是不会产生堵塞的,无论读多少字节,read一定会在有限的时间内返回。
1个产生堵塞的示例:
//block.c>>>>block.out
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd1;
char* buf[20];
int n=20;
//从标准输入中读入n个字符,键盘的输入储存在标准输入中
//即标准输入是一个由外部设备给定的输入
read(STDIN_FILENO,buf,n);
//将读到的字符写入标准输出中,标准输出是终端显示
write(STDOUT_FILENO,buf,n);
return 0;
}
执行程序之后,终端会一直等待用户输入字符,这便是一种阻塞。
当read函数,当返回-1,且erron=EAGIN/EVOULDBLOCK时,说明不是read失败,而是以非阻塞的形式读取设备文件,而且设备没有输入
阻塞和非阻塞不是read和write函数的特性,而是设备文件或者网络文件的属性;
//block2.c
//以非阻塞形式打开设备文件
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fd;
char buf[10];
int n;
//以只读和非阻塞方式打开设备输入文件
//原来设备文件是阻塞性质的,现在修改为非阻塞性质的
fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
perror("open /dev/tty");
exit(1);
}
while (1)
{
n = read(fd, buf, 10);
if (n < 0)
{
if (errno != EAGAIN){
perror("read /dev/tty");
exit(1);
}
//n=-1,errno=EAGAIN,以非阻塞形式读数据,但是设备没有输入
//每隔2s重新读取一次数据
else{
write(STDOUT_FILENO, "try agin\n", strlen("try again\n"));
sleep(2);
continue;
}
}
else{
//读取数据成功,将数据写到终端,关闭文件描述符
write(STDOUT_FILENO, buf, n);
close(fd);
break;
}
}
return 0;
}
**阻塞:**调用结果返回值,当前程序被挂起,直到结果返回;
**非阻塞:**如果不能立即得到结果,则调用者不会阻塞当前线程;所以非阻塞的状态下,需要不断进行轮询;
6 fcntl改文件属性
函数:int fcntl(int fd, int cmd, …)
文件操作杂货铺,有诸多功能
int flags=fcntl(fd,F_GETFL);
获取文件状态:F_GETFL
设置文件状态:F_SETFL
位图:是一串二进制数,使用0或者1代表某个功能是否打开
和stm32 的pin脚定义类似
将标准输入的属性修改成 非阻塞状态 的代码如下:
int flags=fcntl(STDIN_FILENO,F_GETFL);
flags|=O_NONBLOCK;
fcntl(STDIN_FILENO,F_GETFL,flags)
头文件:#include <unistd> #include <fcntl.h>
功能:用于改变文件的状态
参数:fd是对应的文件描述符,cmd是对应的指令,…是可选项,是由cmd决定的参数,通常情况下是整数;
F_GETFD (void)
Return (as the function result) the file descriptor flags; arg is ignored.
F_SETFD (int)
Set the file descriptor flags to the value specified by arg.