欢迎光临
我们一直在努力

【Linux篇】数字世界的渡口 —— open、read、write 如何穿越用户与内核的边界

于此止?:个人主页

❄️个人专栏传送门:《Linux篇》 《QT百日筑基篇》 《算法篇》 

《C++与STL剖析篇》 《数据结构篇》 《Python基础篇》

⭐️纵有狂风拔地起,我亦乘风破万里⭐️

基础I/O操作


目录/索引

目录

基础I/O操作

目录/索引

一、理解文件:

1.狭义理解:

2.广义理解:

3.文件操作的归类认知。

4.系统的角度:

二、C语言标准库的文件操作:

三、系统文件的I/O

1.传标志位的方法:

2.接口的介绍

1.open:打开文件

文档解析:

参数的介绍:

pathname

flags:

mode:

返回值:

close: 关闭文件

文档解析:

返回值:

write:写

文档解析:

返回值:

read:读文件

文档解析:

返回值:

文本写入vs二进制写入

浅谈语言标准库为什么要进行封装系统接口?


一、理解文件:

我们在过去的认识文件是在磁盘中帮助我们储存一些我们的电脑已有的一些占用内存的东西。

我们对文件的理解还没有那么的深奥。现在我将输出四种文件的理解。


1.狭义理解:

-文件在磁盘中。

-磁盘是永久存储的介质, 因此文件在磁盘上存储是永久的。

-磁盘是外设(即输入输出设备)

-磁盘上的文件本质是对文件的所有操作, 都是对外设的输入/输出。

2.广义理解:

linux下一切接文件(我们使用的键盘,鼠标,磁盘,网卡……都是文件)。


3.文件操作的归类认知。

-对于0kb的文件是占用磁盘的资源的。

-文件时文件属性(元数据)和文件内容的集合(文件=内容+属性)

-所有的文件操作的本质都是对文件的内容和属性进行操作。

我们在操作文件时,都是进程帮我们打开文件,所以我们可以得出都是进程在对文件进行对应的操作。


4.系统的角度:

-对于文件的操作就是进程对文件进行操作。

-磁盘的管理者是操作系统。

-文件读写的本质不是通过c/c++的库函数进行操作的而是有对应的系统调用来进行我们对应的操作的。我们的标准库只是能让我们更加方便的对文件进行对应的操作。

所以在这里我们可以简单的猜想以下我们对应的库函数底层一定封装了对应的系统调用,来给我们更好的进行对应的使用。

我们都知道文件分为两种1. 被打开的文件。2.没被打开的文件。

我们被打开的文件属于内存级文件,我们没打开的文件属于磁盘级文件。

我们说到了对应的标准库,那么现在我们就来回顾我们的c语言的文件操作。


二、C语言标准库的文件操作:

推荐链接:

文件操作与使用(文件函数的使用)-CSDN博客


三、系统文件的I/O

我们的fopen(c)和我们的ifstream(c++)的等流式,都是我们对应语言层的概念, 他们都会去调用我们对应的系统的函数来实现我们对应的I/O操作。在我们介绍我们对应的文件之前我们先前谈以下传标志位的方法:


1.传标志位的方法:

传标记为是我们打开文件的一个特别重要的思想,我们可以将文件以xxx的形式进行对应的操作。

这里我们就模拟一个传标记位的方法:

代码的讲解:

我们先设置3个标记为分别是我们的0001为1号标记位。0010为我们的2号标记位。0100为我们的三号标记位。这里我对应的flag传入对应的0001那么我们对应的flag&ONE即0001 &0001 =0001。

所以我们会执行对应printf。

当然这不是最重要的,最重要的是我们如果我们传入ONE|TWE那么重要的就来了,我们0001 | 0010 = 0011 ,我们传入对应的flag的活就能行 0011 &0001(ONE) = 0010.可以执行。

0011 & 0010 =0001,满足可执行,但是直到我们对应的THREE 0011 & 0100 =0;不执行我们对应的代码。所以我们下面的同理。

这里我们输出结论:我们传入对应的标记为可以通过| 来执行不同的标记为所能执行的程序。

我们后续会使用标记位传很多的东西, 我们open会使用对应的方法来进行传对应的标记为, 我们后面system V共享内存中也会进行使用(shmget)        

#include <stdio.h>
#define ONE 0001
#define TWO 0010
#define THREE 0100

void func(int flags) {
if (flags & ONE)
printf("这个标记为式ONE\\n");
if (flags & TWO)
printf("这个标记为式TWO\\n");

if (flags & THREE)
printf("这个标记为式THREE\\n");

printf("\\n");
}
int main() {
func(ONE);
func(TWO);
func(ONE | TWO);
func(ONE | TWO | THREE);

return 0;
}


2.接口的介绍

1.open:打开文件

这与我们的c语言的函数是有区别的, 我们c语言的打开文件的名字(fopen)是相似的。

我查看对应的文档。

文档解析:

int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);

参数的介绍:
pathname

我们第一个参数pathname是一个字符串,代表我要打开的哪个文件。我们也可以只写对应的文件名这样表示我们在当前路径下的文件, 或者可以写一个绝对的路径。 我们只写文件名也是一个绝对路径,我们的进程会补齐我们当前的路径的。

我们可以查看一下:

ls /proc/进程 -l

我们能看到我们对应的进程有对应的pwd等我们创建文件的时候我们的进程会给我们们文件将路径补齐

flags:

这个参数和我们上面所用到的标记为的方法进行传参,但是对应的标记位官方文档已经给出了对应的标记位,我们只要在我们像怎么使用文件传入对应的标记位,即可。

这里的标记位过多我就不一一的进行列举了。我们挑出我们几个最常用的即可。

O_RDONLY:

只读, 我们传入这个表示我们的文件以只读的形式进行打开。

取自单词:read(读) only(仅仅只)

O_WRONLY:

只写,我们传入的时候我们的文件以只写的方向进行打开。

取自单词: write(写) only(仅仅只)

O_RDWR:

读写,传入文件,代表文件以读写的方式进行打开。

取自单词:read(读) write(写)

O_CREAT:

创建, 若文件不存在我们就新建他, 这里我们要注意要设置他的第三个参数。否则我们创建的文件他的权限会出现对应的小问题。

取自单词:creat(创造)

O_APPEND:

追加, 表示我们的文件的写入方式不为对应的覆盖,我们以追加的方式进行对应文件的写入。

取自单词:append(追加)

O_TRUNC:

清空:将我们的文件以清空的方式进行打开。

我们发现我们如果搭配我们的对用的上面的一些参数,标记位的形式进行传参像0_WRONLY | O_TRUNC我们不就是我们C语言fopn传的“w”选项吗。所有这里我们就能看到我们的c语言标准库就是对我们的os系统函数做了对应的封装。

因为这里我们没有介绍库函数的读写操作,所以这里我们暂时不写对应的代码。

mode:

这个参数是将我们的文件带上权限位如果没有这个参数我们的权限会显示乱码的。

我们这里做一个小的操作来看一下。

我们已下面的文件进行写操作没有的话我们就创造这个文件。

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

int main()
{
int fd = open("add.txt", O_WRONLY | O_CREAT);
return 0;
}

可以看到我当前的目录下是没有对应的add.txt这个文件的。我们编译运行一下。

可以看到我们对应的权限位是乱码的。我们加上对应的第三个参数呢?这里我们设置为0666

也就是我的rw rw rw。

int fd = open("add.txt", O_WRONLY | O_CREAT, 0666);

我们发现我们的权限果然改变了,但是我们不因该other权限是rw吗?

那么这里计就是我们对应的权限掩码的问题了权限掩码默认是0002。

我们只要设置一下对应的权限掩码就可以了。

我们修改权限掩码的工具:

umask(在权限那里将过对应的使用这里不过多的涉及了)

umask(0);
int fd = open("add.txt", O_WRONLY | O_CREAT, 0666);

发现程序果然是rw rw rw

返回值:

成功返回我们的文件描述符(下一个话题重点来谈), 失败返回-1。


close: 关闭文件

作用: 用于关闭一个已经打开的文件

文档解析:

int close(int fd);我们只要传入一个文件描述符就可以了。

返回值:

成功返回0,失败返回-1。失败的情况基本只有一种:你关闭的文件别人正在进行使用。

int fd = open("add.txt", O_WRONLY | O_CREAT, 0666);
close(fd);


write:写

文档解析:

ssize_t write(int fd, const void *buf, size_t count);

这里第一个参数是像哪个文件写,传入对应的文件描述符, 写什么我们传入一个buf,写大小多少

count。

返回值:

成功返回对应的写的元素的个数, 失败返回-1,错误码被设置。

使用:

O_WRONLY | O_CREAT | O_TRUNC: 写 | 创造 |清空 不就是我们c语言的w选项吗。

所以我们模拟w选项来操作。我们写入"Hello file operation"。

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include<string.h>
#include<unistd.h>

int main()
{
umask(0);
int fd = open("add.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
char* buff ="Hello file operation";
write(fd, buff, strlen(buff));
close(fd);
return 0;
}

add.txt


read:读文件

文档解析:

ssize_t read(int fd, void *buf, size_t count); 我们对应的fd是像那个文件去读,读到哪个数组,我们读多少count。

返回值:

成功返回我们实际读到的字符数, 返回0 默认已经读到结尾, -1表示读取失败。

使用:

我们这里模拟“r”选项的模式, 我们读到的应该是我们刚才写的内容。

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include<string.h>
#include<unistd.h>

int main()
{
umask(0);
int fd = open("add.txt", O_RDONLY, 0666);
char buff [1024];
read(fd, buff, sizeof(buff));
printf("我们读到的数据是:%s\\n", buff);
close(fd);
return 0;
}

lseek:光标

我们能通过这个函数可以进行光标的易懂向我们的fseek一样。他们底层都是调用了这个函数。

⚠️ 重要:管道、FIFO、socket 不支持 lseek,调用直接失败

1. 移动到文件头部

lseek(fd, 0, SEEK_SET);

2. 获取当前文件偏移(不移动位置)

off_t cur = lseek(fd, 0, SEEK_CUR);

3. 跳到文件末尾

lseek(fd, 0, SEEK_END);


文本写入vs二进制写入

我们对应的文本写入主要是我们的字符串的写入, 而我们的二进制写入主要是我们的数字。我们知道我们如果写一个数字到文件的话就会乱码

这里我们写一个int来看看。

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
umask(0);
int fd = open("add.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
int a = 10;
write(fd, &a, sizeof(a));
close(fd);
return 0;
}

我们发现上面的果然是对应的乱码。

我们操作系统也是对应的乱码吗???

答案已经不重要了,系统不管心你的写入方式,我们上面的两种方式我们的系统都能进行读取。

二进制写入是我们语言层的概念。我们如果想写入数字不出现乱码,可以尝试一下将数字以字符串的方式写入。


浅谈语言标准库为什么要进行封装系统接口?

我们在说这个问题之前我们先看一张图:

我们之前在进程的时候查看过这张图。我们os上层就是封装的各种库。我们如果直接进行系统的调用就代表我们直接跨越两个阶级来进行系统的调用。

而我们要是有了对应的库那么只要跨越一个阶级就能进行对应方法的调用。

系统调用和库函数的关系一目了然。

赞(0)
未经允许不得转载:171主机测评 » 【Linux篇】数字世界的渡口 —— open、read、write 如何穿越用户与内核的边界
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址