廊坊做网站公司排名,企业微信公众号平台官网,深圳网站建设培训,可以接单做网站的软件这里写目录标题 1. 系统文件I/O1.1. 接口介绍1.2. 库函数接口与系统接口的关系 2. 文件描述符fd2.1. 012文件描述符2.2. 文件描述符的分配规则2.3. 重定向2.4. 重定向系统调用2.5. 进程独立性 3. Linux下一切皆文件4. 缓冲区4.1. 缓冲区的理解4.2. 缓冲区的位置 5. 理… 这里写目录标题 1. 系统文件I/O1.1. 接口介绍1.2. 库函数接口与系统接口的关系 2. 文件描述符fd2.1. 012文件描述符2.2. 文件描述符的分配规则2.3. 重定向2.4. 重定向系统调用2.5. 进程独立性 3. Linux下一切皆文件4. 缓冲区4.1. 缓冲区的理解4.2. 缓冲区的位置 5. 理解文件系统5.1. 认识磁盘5.2. 文件管理5.3. 分组的管理 6. 软硬连接6.1. 软链接6.2. 硬链接 7. 动静态库7.1. 静态库7.2. 动态库7.3. 动静态库的加载 1. 系统文件I/O
1.1. 接口介绍
在Linux中操作文件我们可以使用C接口操作也可以用系统接口来进行文件访问。下面用一段代码介绍一下常见的访问文件的系统接口
#include stdio.h
#include unistd.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include assert.h#define FILE_NAME log.txtint main()
{//该进程权限掩码更改为0000umask(0);//打开一个文件fd读写不存在则创建追加权限0666int fd open(FILE_NAME, O_RDWR | O_CREAT | O_APPEND, 0666);if (fd 0){perror(open);return 1;}int cnt 5;char outBuffer[64];//向文件fd追加数据 while (cnt){//向outBuffer以特定格式写入数据 aaaasprintf(outBuffer, %s:%d\n, aaaa, cnt--);//向打开的文件fd写入write(fd, outBuffer, strlen(outBuffer));}//重置指针位置lseek(fd, 0, SEEK_SET);char buffer[1024];ssize_t num read(fd, buffer, sizeof(buffer) - 1);if (num 0) buffer[num] 0; // 0, \0, NULL - 0printf(%s, buffer);close(fd);return 0;
}运行结果
open
man手册查找open的介绍 参数 返回值 write
man手册查找write的介绍 参数如上图所示
返回值写入多少数据就返回多少。
read 1.2. 库函数接口与系统接口的关系
库函数接口与系统接口的关系可以认为是库函数接口对系统接口做了一层封装供用户使用。如下所示 2. 文件描述符fd
2.1. 012文件描述符
Linux进程默认情况下会有3个缺省打开的文件描述符分别是标准输入0 标准输出1 标准错误2. 0,1,2对应的物理设备一般是键盘显示器显示器
#include stdio.h
#include unistd.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include assert.h#define FILE_NAME(number) log.txt#numberint main()
{//打印stdin的文件编号printf(stdin-fd: %d\n, stdin-_fileno);//打印stdout的文件编号printf(stdout-fd: %d\n, stdout-_fileno);//打印stderr的文件编号printf(stderr-fd: %d\n, stderr-_fileno);//打开5个文件int fd0 open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);int fd1 open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);int fd2 open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);int fd3 open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);int fd4 open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);//打印打开文件的文件描述符printf(fd: %d\n, fd0);printf(fd: %d\n, fd1);printf(fd: %d\n, fd2);printf(fd: %d\n, fd3);printf(fd: %d\n, fd4);//关闭文件close(fd0);close(fd1);close(fd2);close(fd3);close(fd4);return 0;
}运行结果 一个进程可以打开很多文件当文件被打开了就需要被管理管理的本质就是先描述再组织。所以文件是这样被管理的。如下图所示 2.2. 文件描述符的分配规则 如上图所示默认打开的三个文件流如果我关闭了0那么新打开的文件就会从0开始打开。也就是说在files_struct数组当中找到当前没有被使用的最小的一个下标作为新的文件描述符。
2.3. 重定向
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{//关闭stdoutclose(1);//打开1个文件int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd 0){perror(open);return 1;}//打印相关文件描述符printf(open fd: %d\n, fd); // printf - stdout//刷新缓冲区fflush(stdout);//关闭文件close(fd);return 0;
}运行结果 原本是在文件描述符3打开的文件却占用了文件描述符1打开。这种类似重新定义方向的做法称为重定向。如下图所示 就是原本1的位置的file* 被 3位置的file* 直接覆盖了使得1位置的file* 改变了方向。
2.4. 重定向系统调用
输出重定向
操作系统提供了一个系统调用可以直接实现重定向。 其中我们常用就是dup2接口。对应的参数理解你可以认为是oldfd直接覆盖了newfd也就是oldfd重新指定了方向。
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
int main()
{//打开1个文件int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd 0){perror(open);return 1;}//调用系统重定向把fd重定向到stdin//也就是原本显示到显示器上的数据现在写入到fd中dup2(fd,1);//打印相关文件描述符printf(open fd: %d\n, fd); // printf - stdout //刷新缓冲区fflush(stdout);//关闭文件close(fd);return 0;
}运行结果 这种从显示器到文件的重定向叫做输出重定向。
输入重定向
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h int main()
{ //只读方式打开文件 int fd open(log.txt, O_RDONLY); //判断是否打开成功 if(fd 0) { perror(open); return 1; } //输入重定向也就是说stdin直接读取到fd的内容 dup2(fd, 0); char line[64]; //显示是否读取成功 while(1) { printf( ); if(fgets(line, sizeof(line), stdin) NULL) break; //stdin-0 printf(%s, line); } return 0;
} 这种从标准输入到文件的重定向叫做输入重定向。
追加重定向
在输入重定向的基础上将文件打开的模式增加追加就可以实现追加重定向。
#include stdio.h
#include string.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h int main()
{ umask(0); //只写方式不存在则创建追加打开文件 int fd open(log.txt, O_WRONLY | O_CREAT | O_APPEND, 0666); //判断是否打开成功 if(fd 0) { perror(open); return 1; } //输出重定向也就是说stdout直接输出到fd dup2(fd, 1); printf(hello world!\n); printf(hello world!\n); printf(hello world!\n); printf(hello world!\n); fflush(stdout); return 0;
} 运行结果 2.5. 进程独立性
子进程的重定向并不会影响父进程
有两个进程一个父进程一个子进程操作系统维护着两个task_struct结构体如上图所示。每个进程的PCB中都有一个struct files_struct*的指针files。它们各自指向的struct files_struct结构体中都有一个文件描述符表。两个文件描述符表中的内容在子进程刚创建时是一样的所以它们都指向相同的被打开的文件。当子进程将自己文件描述符表中下标为1的文件关闭以后并不影响父进程文件描述符表中下标为1的数组中的内容。
每个进程都会维护自己的文件描述符表所以多个进程就会存在多个文件描述符表但是这些表中的指针指向的被打开文件只有一套。
某个进程进行文件的打开与关闭操作时只需要修改自己的文件描述符表就可以不会对其他进程造成任何影响。
3. Linux下一切皆文件
在Linux下一切皆文件怎么理解呢先讲讲硬件如果把硬件看作文件又该如何理解呢来看下图 每一个硬件操作系统都会维护一个struct file类型的结构体硬件的各种信息都在这个结构体中并且还有对应读写函数指针(对硬件的操作主要就是读写)。每个硬件的具体读写函数的实现方式都在驱动层中使用到相应的硬件时操作系统会通过维护的结构体中的函数指针调用相应的读写函数。
站在操作系统的角度来看下层无论驱动层和硬件层中有什么在它看来都是struct file结构体都是通过维护这个结构体来控制各种硬件。站在操作系统的角度来看上层无论用户层以及系统调用有什么在它看来都是一个个进程都是一个个的task_struct结构体都是通过维护这个结构体来调度各个进程的。
真正的文件在操作系统中的体现也是结构体操作系统维护的同样是被打开文件的结构体而不是文件本身。
一切皆文件是指在操作系统中一切都是结构体。
4. 缓冲区
4.1. 缓冲区的理解
A在海南B在黑龙江。A想把自己用过的键盘给B用那么此时有2种方法可以实现。
A自己一路奔波把键盘给到B手上A选择一家快递公司把键盘寄给B
很显然在现实生活中绝大多数人都会选择2方案选择一家快递公司进行邮寄。那么同样的在计算机世界里道理也是如此。 当你把快递放到快递站的时候快递站并不会立马就给你寄送过去而是等到一定的货物量快递站才会一起寄送快递。这样A就可以在寄送快递这一段时间干自己的事不用说专门为了给B寄送快递而浪费了自己本来的时间。同样的计算机世界也是如此进程给文件发送数据CPU并不会说专门给文件去发送数据而其他进程就不管。当进程发送数据给文件时数据会存放到缓冲区然后等缓冲区刷新或者缓冲区满发送。
从上面的分析可以得出结论缓冲区的存在是为了给发送方节省时间。
既然缓冲区的存在是为了给CPU节省时间那么它的访问速度肯定是比文件要快的多的所以它只能是内存。所以说缓冲区本质上就是一段内存。
4.2. 缓冲区的位置
上面讲了缓冲区是一段内存那么这段内存是谁申请的它是属于谁的下面来看一段代码。 运行结果 首先可以看到父子进程运行同一段代码但是只有C接口的代码运行了两次系统接口的代码只运行了一次。那么就是说缓冲区并不在操作系统内核种。
在C语言中文件相关的接口都涉及到一个FILE*那么我们就可以猜测缓冲区就在其中。 在Linux源码中可以看到FILE这个结构体的成员很多都是在维护缓冲区。所以我们所说的缓冲区都是用户级的缓冲区都是在用户层面体现出来的。
5. 理解文件系统
5.1. 认识磁盘
如果大家对电脑有过了解都会知道电脑有个硬件设备叫磁盘今天我们就来浅浅的认识一下磁盘。
物理结构
磁盘是电脑的外设是一个机械结构相对于CPU而言它是比较慢的。下图是生活中常见的磁盘。 可以看到磁盘好似一个圆柱体其中这个圆柱体是一片一片摞起来的。
存储结构 磁头向磁盘中读写数据如上图中有三个盘片那么就有六个磁头给它编号从0到5。柱面从俯视图中来看一个盘面可以看做是多个同心圆每一个同心圆被叫做一个磁道一叠盘片中的相同磁道所组成的圆柱就这里的柱面从内到外给柱面编号从0到3。扇区在俯视图中以相同圆心角将盘片分为多个扇形每个扇形和每个磁道相交产生的区域就被叫做扇区。一个盘面上每个磁道所包含的扇区个数是相同的同样给每个扇区编号。
每个扇区的大小可以认为是512KB看似扇区的面积不同靠近圆心的较小但是密度大远离圆心的面积较大但是密度较小。
这样一来我们就可以定位任意一个扇区然后进行读写数据。比如0号磁头0号柱面0号扇区此时磁头就会摆动到0号柱面处当0号磁头对应的盘面中的0号磁道里的0号扇区旋转到磁头位置时就可以向磁盘中读写数据。
这种定位方法称为CHS定位法。
逻辑结构
上面的图是不是看起来就像一盘蚊香假设这个蚊香是可以任意拉伸的。那么我们将这个磁盘拉伸成一条直线然后将这条直线看成一个数组数组中的每一个元素就是扇区。 此时磁盘就被我们抽象成了上图所示的数组并且给每一个扇区进行编号。站在操作系统的角度操作系统访问这个数组就是在访问磁盘。
那么这个数组的下标是怎么和磁盘的CHS对应起来的呢 如上图所示可以根据给定的逻辑数组下标转换成CHS定位法定位到磁盘上具体的某个扇区。
5.2. 文件管理
操作系统看到的磁盘就是一个数组这个数组每个元素的大小是512K字节(一个扇区)同样我们也知道每次向磁盘中读写数据都很耗费时间。
为了提高效率磁头每次访问磁盘的基本单位是4KB(绝大多数情况下)。即使访问磁盘的一个bit磁头也是将包过这一个bit在内的周围4KB大小的数据加载到内存。
正因为磁头每次访问的是4KB大小的数据块所以内存也被划分成了多个4KB大小的空间每一个空间被叫做页框。
同样的磁盘中的文件尤其是可执行文件也被划分成了多个4KB大小的数据块每一个块被叫做页帧。
假设现在有一个500GB大小的磁盘操作系统如果统一管理的话成本会很高所以采用分治的思想来管理整个磁盘。 将500GB的磁盘分成4个区只需要管理好一个区其他三个区便可以复用这套方法。再将每个区分为多个组只需要管理好一个组其他剩下的组便可以复用这套方法从而管理好这个区。每个分区以及每个分组是多大要看具体情况。
这种思想有点像递归的思想所以我们要学习到重点就是如何管理好一个组。
5.3. 分组的管理 每个分组中又分为这6个区域。主要了解5个区域
inode Table存放了这个分组中所有的inode(已经使用的和没有使用的)每个分组中inode的个数是确定的。inode Bitmapinode位图该分组中有多少个inode这个位图就有多少个bit并且每一个比特位都与一个inode一一对应。每使用一个inode对应的位图就会被置1。Data blocks保存这该分组内所有文件的内容该块区又被分为多个数据块。Block Bitmap数据块位图该分组的Data blocks中有多少个数据块这个位图就有多少个bit并且每一个比特位都和一个数据块一一对应。每使用一个数据块对应的位图就会被置1。GDT描述表记录该分组中inode和数据块的使用率等宏观属性。
最主要的是inode这个编号实际上inode是一个结构体一个文件的所有属性都在inode中。当创建一个文件的时候就会在inode Table中申请一个未被使用的inode并且将对应的位图置1.
文件内容的存储
其中文件内容的存储就存放在Data blocks中里面有着许多的数据块并且带有相应的编号。其编号都放在Block中对应起来。
6. 软硬连接
6.1. 软链接
指令ln -s 要链接的文件名 链接文件名功能建立软链接 如上图所示使用红色框中的指令建立了软连接。
建立软连接后会有新的文件产生。链接文件和被链接文件的inode不相同表示链接文件是一个独立的新文件。
链接文件的删除可以用rm删除也可以用unlink删除效果是一样的。
软链接的作用
你可以把在Linux上的软链接类比Windows上的快捷方式。
在当前目录下运行要补全路径。但是只要我们创建要运行文件的软链接在当前目录下只需要运行软链接便可。 软链接与inode无关只与文件名有关 所以删掉链接文件并不会影响它本身软链接的指向只是指向一个路径。
6.2. 硬链接
指令ln 链接文件名 被链接文件名功能没有选项-s建立硬链接 inode相同意味着它俩是一个文件因为inode是一个文件时唯一标识而且一个文件只有一个。所以说硬链接创建的文件并不是一个独立的新文件它是被链接文件的别名。
硬连接的本质在指定路径下新增文件名和inode编号的映射关系
此时就不再是文件名和inode一一对应了而是一个inode对应多个文件名这是文件名都标识一个文件只是叫法不同。 上图中红色框中的数字表示文件的硬连接数也就是一个inode有几个文件名字和它映射。
硬链接的删除
rm删除即可。 从上图可以得知删除了硬链接但是与原来硬链接相同inode的文件依然存在只是数字减少了1。那么就可以猜测硬链接与链接文件的关系应该如下图所示 在在inode中有一个变量count它在进行引用计数。
每当一个文件名和这个inode建立映射关系的时候引用计数加1。也就是硬连接数每加1引用计数就加1。
所以在删除这个文件的时候使用unlink只是让硬链接数和引用计数减了1。只有引用计数减到0这个文件的inode的位图才会被清0这个文件才会被删除。
7. 动静态库
简单了解一下动静态库。
动态库库文件以.so为后缀(Windows中为.dll)静态库库文件以.a为后缀(Windows中为.lib)库的命名规则lib库名.后缀
所以见到一个库掐头去尾才是它的库名。使用gcc进行编译的时候默认是采用的动态链接如果要使用静态链接需要加上选项-static
7.1. 静态库
简单以一个小例子来了解下库是如何形成的。
库源码 头文件 将上诉代码接口给他人使用并且不想暴露源码此时我们就可以选择将它制作成库。
静态库的理解
对源码进行预处理编译汇编形成以.o为后缀的目标文件就差最后链接一步。 其实库的原理和上面类似只是将所有的.o为后缀的文件打包在了一起形成了一个库在使用的时候直接使用这个库就可以。你可以这么来理解就是你要给某个顾客打包外卖顾客吃这个外卖1是需要勺子2是需要筷子3是需要牙签。所以我们将这个外卖所需要的餐具全都打包在一起提供给顾客。
制作静态库
制作静态库需要用到 ar -rc 库名 所有后缀为.o的文件。
指令ar -rc libname.a [所有待打包.o]作用将所有待打包的.o文件制作成静态库。 现在进行如下步骤
将所用到的头文件全部放在myliba/include目录下。将静态库文件放在myliba/lib目录下。 此时的静态库已经完成就是蓝色的目录此时已经是一个静态库了。
使用静态库
接下来我们将从使用者的角度来看下静态库该如何使用。 如果我们要使用这个库有两种方法。 将该静态安装在系统文件中然后编译的时候用相应的指令明确是系统中的哪个库即可。在编译的时候用相应的指令明确库的路径指定库中的文件然后即可使用现在来介绍这种用法。 选项作用-I(大写i)指定头文件路径-L指定库文件路径-l(小写L)指定库(掐头去尾后的库名)
如上图所示这样一来一个第三方库就可以正常使用了。 7.2. 动态库
形成位置无关码
和制作静态库一样将所有.o文件打包在一起但不使用ar打包而是使用gcc来打包。 注意此时是用gcc生成的.o文件还加了一个选项-fPIC生成位置无关码。
位置无关码是什么简单来说就是相对位置。就好比数轴上的数字1总是在2的旁边。如果想找到1那么可以从0开始找也可以直接定位到2的位置2的旁边便是1。
形成库文件 使用gcc加-shared选项告诉gcc生成动态库而不是可执行程序如上图中红色框中所示。
选项作用-fPIC生成位置无关码-shared生成动态库 使用动态库
使用动态库有4种方法。
将头文件和库文件安装在系统默认搜索路径中永久
将头文件复制到/sur/include路径下将动态库文件安装在/usr/lib64路径下.并且在编译的时候使用-l选项告诉gcc使用的动态库名称。
将库文件路径放在环境变量里 信息都告诉gcc了怎么编译不成功呢
我们将头文件路径库文件路径库文件名告诉了gcc。
编译完成以后和gcc就没有关系了接下来的执行是操作系统的事情。
那么操作系统就必须得知道所使用的动态库在哪里。
所以接下来的任务就是告诉操作系统我的动态库在哪里。
在执行程序的时候操作系统会从环境变量LD_LIBRARY_PATH中读取动态库的路径。
将自己的动态库路径放入到环境变量中再执行刚刚生成的可执行程序发现可以成功执行了而且使用的是动态库中的函数接口。 这种做法并不能永久生效因为每次启动shell的时候它都会从配置文件中重新加载环境变量我们这里给LD_LIBRARY_PATH赋值只是暂时的。
将库文件路径放在配置文件中永久软链接到系统默认搜索路径中永久
7.3. 动静态库的加载
动态库的加载
gcc在编译的时候只是将库中的库函数生成了一个位置无关码(相对偏移量)放在了程序中在程序执行的时候需要操作系统先将整个库加载到内存中然后再根据位置无关码调用这个库函数。 进程被创建以后将对应的代码加载到内存中。进程虚拟地址空间的代码段通过页表映射到了内存中其中就包括生成的位置无关码。操作系统将指定的动态库也加载到了内存中由于占据内存空间所以给它分配了地址。当进程执行到调用库函数时操作系统根据位置无关码在动态库基地址的基础上偏移一定量的地址找到要调用的库函数并且调用。
静态库的加载 在gcc进行编译时编译器将要调用的库函数复制到了程序中形成了可执行程序。进程被创建后操作系统将复制了库函数的可执行程序加载到内存中去执行。
静态库的加载和操作系统没有关系它是编译器完成的就是将库函数源码复制一份到我们对源码中。
如果使用静态库同一个库函数被使用了十次编译器就会在对应位置复制十分使用100次就会复制一百次。如果使用动态库同一个库函数被使用十次就会有十个位置无关码使用100次就会有100个在执行的时候操作系统根据偏移量在内存中仅有的一个动态库中调用相应的函数。
所以使用静态库的程序都比使用静态库的程序要大所占用的内存多。