Linux系列
文章目录
前言
在计算机体系中,文件操作是不可或缺的一部分,随着我们不断的深入学习,我们对文件使用的需求会越来越高,在操作系统层面,文件的操作往往是通过系统调用接口来完成的,要想更深入的学习,我们必须熟练掌握文件操作。
本篇我们会以:C语言中的文件I/O操作------>系统调用接口-------->C语言文件操作和系统调用接口的关系-------->文件描述符及进程与文件的关系,来给大家展开介绍。
一、C语言文件操作的基础I/O
在学习C语言阶段相信大家都接触过,C语言的文件操作,这里我们先简单介绍几个函数,以便后续讲解。
我们先进行介绍后面再给出示例,这块已经掌握的可以跳过至1.3。
1.1 fopen()函数
头文件
#include<stdio.h>
参数
1.path:打开文件的路径(相对路径/绝对路径)
2.mode:打开文件的模式
返回值
函数成功返回一个指向文件的“指针”,失败返回空并设置错误码error
。
FILE *:文件指针类型,这个类型我们后面会详细介绍
打开模式
r :以只读的方式打开文件,每次从文件开头读取。
w :以写的方式打开文件,若文件不存在则创建一个新文件,若存在先将文件内容清空再从文件开始出写。
a :追加形式打开文件,不存在创建,存在从文件内容末尾处开始写
1.2 fwrite()和fread()函数
fwrite()和fread()参数
1、ptr:指向缓冲区指针,对于fwrite
就是将缓冲区数据写入到文件,对于fread
来说就是将文件数据读入到缓冲区。
2、size:要操作的每个数据项大小。
3、要操作数据项个数。
4、文件指针。
代码演示:
1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 char* str="abcdef\n";
6 FILE*fd=fopen("llll.txt","w");
7 int cnt=3;
8 while(cnt--)
9 {
10 fwrite(str,1,strlen(str),fd);
11 }
12 return 0;
13 }
可以看到当我们执行该程序,当前目录下就会创建一个新的文件。
当我们再次执行程序向文件写入,他会先清空文件,从头开始写入,如果你get不到,可以更改向文件写入内容,进行测试。
1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5
6 FILE*fd=fopen("llll.txt","r");
7 char buf[100];
8 size_t s= fread(buf,1,100,fd);
9 if(s>0)printf("%s\n",buf);
10 return 0;
11 }
其他的大家感兴趣自己尝试吧。
1.3 向标准流中操作
在C语言看来,像文件打印和向显示器打印是没有区别的,相信大家一定听过在一个C程序启动时,会默认打开三个标准输入输出流:stdin(键盘)、stdout(显示器)、stderr(显示器)
,后两者的区别下篇会详细介绍。下面我们来简单演示一下:
1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 char *str="abcdef\n";
6 fwrite(str,1,strlen(str),stdout);
7 fprintf(stdout,str);
8 return 0;
9 }
这样我们就实现了直接向标准输出流打印数据。
1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 char buf[100];
6 fread(buf,1,20,stdin);
7 printf("%s\n",buf);
8 return 0;
9 }
从标准输入读取20个字符并打印。
二、系统调用接口
2.1 open()函数
参数
1、pathname:要操作文件路径及文件名,默认为当前进程路径。
2、flags:类似于对文件操作方式,可以传递以下宏
O_RDONLY:只读模式
O_WRONLY:只写模式
O_CREAT: 若文件不存在则创建,需配合 mode 参数设置权限。
O_APPEND: 追加写入(避免覆盖原有内容)。
O_TRUNC: 若文件存在且为普通文件,将其长度截断为 0(清空)。
3、mode:文件权限(操作文件不存在,创建文件时使用)
返回值
返回文件的文件描述符(后面详细介绍)
2.2 write()和read()函数
参数
1、fd:文件描述符
2、buf:缓冲区,对于read
来说就是将从文件中读取的数据存入缓冲区,对于write
来说就是将缓冲区的数据写入文件。
3、count:操作数的个数。
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 char*str="abcdefff\n";
10 int fd=open("hhhh.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
11 write(fd,str,strlen(str));
12 return 0;
13 }
以只写的方式打开hhhh.txt
文件,如果文件不存在就在当前进程的目录下创建它,将权限设为0666
你可以查看新建文件权限并不为0666,这是因为存在默认权限掩码,可以通过umask()更该。
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 char buf[100];
10 int fd=open("hhhh.txt",O_RDONLY);
11 ssize_t s=read(fd,buf,10);
12 buf[10]='\0';
13 if(s<0)perror("read");
14 printf("%s\n",buf);
15 return 0;
16 }
使用系统调用接口以只读形式打开hhhh.txt
文件,将内容读取到buf
中,并将读取内容的最后一个数据设为\0
(系统级别的接口只将操作数当作字符,并不会自动添加\0
)。
可以看到系统调用接口可以达到,与C语言库函数相同的结果,那么系统调用能否直接向显示器输出,或直接从键盘读入呢?要回答这个问题我们要先理清两者之间的关系。
三、C语言函数与系统调用接口的关系
不论是C语言函数,还是系统调用接口最终目的都是在访问文件,我们知道文件是存储在磁盘中的,磁盘又属于外部设备,早在学习冯诺依曼体系结构时我们就知道了,对于外设的访问我们只能通过操作系统来进行,而操作系统不相信用户,所以只给用户提供了系统调用接口,所以访问外设的库函数,都是通过封装系统调用接口来完成的。那这就很简单了,其实c函数就是封装了系统调用接口。
这样就比较清晰了,那么返回值该如何理解呢,文件流还说的过去,整形是什么鬼,其实这里的FILE
是封装为一个结构体来呈现的:
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 char *str="123456ggggjj\n";
10 FILE*fd=fopen("hhhh.txt","w");
11 write(fd->_fileno,str,strlen(str));
12 return 0;
13 }
这里我们通过C语言函数打开文件,利用其返回类型得到文件描述符,通过系统调用向对于文件中写入数据,如果成功写入我们就可以验证FILE
结构体封装了文件描述符。
四、文件描述符
我们先将文件描述符打印出来再讲解:
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 int fd1=open("llll.txt",O_RDONLY);
10 int fd2=open("hhhh.txt",O_RDONLY);
11 printf("%d %d\n",fd1,fd2);
12 return 0;
13 }
同打印我们看到文件描述符为:3、4那么为什么是从3开始的呢?
在Linux中,进程默认情况下会有三个缺省打开的文件描述符,分别是标准输入0、标准输出1、标准错误2,而他们所对应的硬件设备分别是:键盘、显示器、显示器,到底是不是这样呢,我们使用程序测试一下:
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 int main()
8 {
9 char *str="hello linux\n";
10 write(1,str,strlen(str));
11 write(2,str,strlen(str));
12 return 0;
13 }
我们分别向文件描述符1和文件描述符2对应的文件打印了str
字符串
可以看到成功打印。
那么系统调用是如何使用文件描述符来找到对应的文件的呢?
五、操作系统对文件的管理
学过操作系统对进程的管理后,再理解这块是简单的,无非就是先描述,再组织。当一个文件被打开时,操做系统会在内存中创建用来描述文件的结构体对象,为了方便管理操作系统将他们链接(我们就说它使用双链表链接)起来,操作系统只需要完成对这个数据结构的管理就可以实现对文件的管理了,那么进程是如何找到并操作文件的呢?进程有对应的task_struct
结构体,结构体内部含有一个指针*file
这个指针指向一张表files_struct
,这个表中存在一个数组,数组里存储的是,file
对象的地址,而文件表述符其实就是数组下标;
有图有真相的,感兴趣的可以去看看内核代码。
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
这篇文章我写的贼难受,需要说的东西太多了,要是全都展示出来,应该有现在的三倍量,复习的时候结合自己记的笔记!!!这句话是给作者自己看的!!!