复习C文件IO相关操作
共识的原理:
1文件 = 内容 + 属性
2文件分为打开的文件和没打开的文件
3打开的文件:进程打开(本质是研究进程和文件的关系)
4没打开的文件:在磁盘上放着,我们关心的就是文件如何被分门别类的放置好--我们要能快速的进行增删查改
文件被打开,必须先被加载到内存,而且进程可以打开多个文件
如何管理这这些被打开的文件呢?,仍然是先描述再组织,每个打开的文件都是一个对象,结构体里面就是这些文件的属性
打开文件的接口
fopen不带文件路径的时候,默认在当前路径下(就是进程的当前路径)
返回的是一个文件句柄
w:写入之前都会把文件清空,只要打开就清空
写入操作
c程序在启动的时候,会打开三个标准输入输出流(文件)
三个文件流
stdin : 键盘文件 stdout :显示器文件 stderr:显示器文件
所以用fprintf(stdout,"1111"); 这种也是向显示器中打印
认识文件相关系统调用接口
文件其实是在磁盘上的,磁盘是外部设备,访问文件其实就是访问硬件!
相关系统调用
cpp#include <sys/types.h> #include<sys/stat.h> #include<fcntl.h> int open(const char *pathname,int flags); int open(const char *pathname,int flags,mode_t mode); pathname:要打开或创建的目标文件 flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算,构成flags。 参数: O_RDONLY:只读打开 0_WRONLY:只写打开 O_RDWR:读,写打开 这三个常量,必须指定一个且只能指定一个 oCREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 O_APPEND:追加写 返回值: 成功:新打开的文件描述符 失败:-1open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,
则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
在用系统调用创建文件的时候,要想要指定权限,用umask(0) 来清除本进程的掩码,然后再用第三个参数直接去设置权限
系统中经常用二进制标志位来传递参数
这两个系统调用传参就要用这种方法
关闭和写入
注意,在系统调用接口中没有FILE* 而是用一个int代替,这个就是file descriptor 文件描述符
函数就是系统调用的封装
跟我们之前task_struct 一样,操作系统内描述一个被打开的文件的信息就应该描述为struct file结构体,里面直接或者间接的包含如下的属性
1在磁盘的什么位置
2基本属性,权限,大小,读写位置,谁打开的....
3文件的内核缓冲区信息
4struct file* next
5引用计数(当引用计数为0的时候系统才会去回收这个对象)
在task_struct里面就有个struct files_struct *files;
所以open返回的是数组的下标!!想要找到这个文件,只要有这个下标,就能找到这个目标文件(豁然开朗吧)这个下标就叫做文件描述符
而且这个文件表中0 1 2 默认会被c语言的三个文件占用,就是打开三个标准输入输出流,这个不是c语言的特性,而是操作系统的特性
FILE 是C库自己封装的结构体
stdout->_fileno就是stdout的fd 其实就是1
认识文件描述符,理解重定向
文件描述符的分配问题?
从0下标开始,寻找最小的没有被使用的数组的位置,他的下标就是新文件的文件描述符
cpp#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> int main() { close(1); int fd = open("myfile", O_WRONLY|O_CREAT, 00644); if(fd < 0){ perror("open"); return 1; } printf("fd: %d\n", fd); fflush(stdout); close(fd); exit(0); }此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出 重定向。常见的重定向有:>, >>,
其实就是把一个文件关掉,文件描述符空出来,再打开要重定向的文件,就完成了重定向的操作
从定向的函数
dup(oldfd,newfd)
如果新打开的文件的文件描述符为fd ,那么就要用dup(fd,1)
为什么fd 是老的,1为新的?
应为dup函数是将下标为1数组内容改变为指向新建文件的struct file 指针,所以向1中写就是向文件中写,这种不用关闭1了,所以1的位置为新的文件描述符
重定向之后如果程序替换后,重定向还有用吗?
有用的,程序替换只是代码和数据替换,但是进程还是不变的,文件所做的操作时基于task_struct的,那么程序替换时不影响的
stdout 和 stderr的区别这两个都是在显示器上打印
文件描述符分别为1 和 2 ,但是都是指向显示器,但是可以将这两个重定向到不同的文件,相当于stderr这种错误信息可以重定向到我们想要放置错误信息的额文件里面
如何理解一切皆文件
对比fd和FILE,理解系统调用和库函数的关系
重新理解缓冲区:
在我们之前写的进度条可知,如果printf不带\n,sleep(1),发现是在sleep结束之后要打印的东西才被打印出来,其实printf的时候,这个字符串已经被加载到缓冲区当中了,\n强制刷新
1打印完成用close(1),发现c语言的接口,如果字符串不带\n打印不出来,而用操着系统的接口,字符串不带\n可以打印出来东西
操作系统内核会有缓冲区,而c语言也会给我们提供一个缓冲区,用c语言接口的时候会把数据写入到c提供的缓冲区中,c语言函数中最终再调用系统接口,把c语言缓冲区中的数据写入到操作系统的缓冲区中,所以在c语言函数调用系统接口之前就把显示器文件关掉(操作系统显示器文件的缓冲区就被关掉),就无法写入到操作系统stdout缓冲区中了
所以exit() 和 _exit()我们就可以深刻理解他们两个之间的区别了,_exit()看不到c语言提供的缓冲区
向显示器文件的刷新方案是行刷新,所以再printf执行完就会立刻遇到\n的时候,将数据进行刷新
目前我们认为,只要将数据刷新到了内核,数据就可以给到硬件了
2当在关闭close(1)之后再用fork()创建子进程,发现一旦重定向到文件中(就是向文件中打)
此时的刷新方式就变为了全缓冲了(刷新到显示器上是行刷新),现象是文件中c接口写入数据为两份,操作系统接口写入的数据只有一份
原因:子进程的代码时共享的,数据是拷贝一份,因为c语言的缓冲区是用户级别的,每个进程都有,所以父子进程结束的时候,会刷新缓冲区,双方都要向文件(操作系统缓冲区)中刷新数据,所以c接口的数据被写入了两份
源代码:
cpp#include <stdio.h> #include <string.h> #include <unistd.h> int main() { //printf("hello Linux"); //close(1); //return 0; const char *fstr = "hello fwrite\n"; const char *str = "hello write\n"; // C printf("hello printf\n"); // stdout -> 1 sleep(2); fprintf(stdout, "hello fprintf\n"); // stdout -> 1 sleep(2); fwrite(fstr, strlen(fstr), 1, stdout); // fread, stdout->1 sleep(2); // 操作提供的systemcall write(1, str, strlen(str)); // 1 sleep(5); //close(1); // ? fork(); return 0; }
1 缓冲区刷新问题
a 无缓冲---直接刷新
b 行缓冲---不刷新,直到碰到\n | 显示器
c 全缓冲---缓冲区满了才会刷新 | 普通文件写入
2 补充问题
a 进程退出的时候,也会刷新(c语言的)
为什么要有这个缓冲区?
1 解决用户的效率问题(它相当于菜鸟驿站)
2 配合语言格式化问题
b 缓冲区在哪里?
文件操作绕不开FILE(struct),EILE->fd FILE 里面还有对应打开文件的缓冲区字段和维护信息 ,像fflush(stdout) !!! 打开10个文件,就有10个语言层的缓冲区
c 这个FILE对象属于用户?还是属于操作系统?这个缓冲区是不是属于用户级的缓冲区?
语言都属于用户,缓冲区也是用户级缓冲区
结构图
模拟实现一下c的文件标准库:
命令行模式 vs +文件 : vim 打开多文件
其实就是自己实现一下stdio.h
封装_FILE 完成_fopen _fwrite _fclose
所以这些上层语言具有跨平台性,在系统调用层面,是没有各种类型的概念的,所有的东西都是字符串
对于缓冲区的完成,用一个数组区存储,用三个标志位代表立刻刷新,行刷新,全缓冲
缓冲区的意义:将数据刷新到操作系统是有时间 消耗的,缓冲区的存在能够提高效率
所以就说c语言效率高 ,其实就是有这些的设计,提高了效率
.h
cpp#ifndef __MYSTDIO_H__ #define __MYSTDIO_H__ #include <string.h> #define SIZE 1024 #define FLUSH_NOW 1 #define FLUSH_LINE 2 #define FLUSH_ALL 4 typedef struct IO_FILE{ int fileno; int flag; //char inbuffer[SIZE]; //int in_pos; char outbuffer[SIZE]; // 用一下这个 int out_pos; }_FILE; _FILE * _fopen(const char*filename, const char *flag); int _fwrite(_FILE *fp, const char *s, int len); void _fclose(_FILE *fp); #endif.c
cpp#include "Mystdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #define FILE_MODE 0666 // "w", "a", "r" _FILE * _fopen(const char*filename, const char *flag) { assert(filename); assert(flag); int f = 0; int fd = -1; if(strcmp(flag, "w") == 0) { f = (O_CREAT|O_WRONLY|O_TRUNC); fd = open(filename, f, FILE_MODE); } else if(strcmp(flag, "a") == 0) { f = (O_CREAT|O_WRONLY|O_APPEND); fd = open(filename, f, FILE_MODE); } else if(strcmp(flag, "r") == 0) { f = O_RDONLY; fd = open(filename, f); } else return NULL; if(fd == -1) return NULL; _FILE *fp = (_FILE*)malloc(sizeof(_FILE)); if(fp == NULL) return NULL; fp->fileno = fd; //fp->flag = FLUSH_LINE; fp->flag = FLUSH_ALL; fp->out_pos = 0; return fp; } // FILE中的缓冲区的意义是什么???? int _fwrite(_FILE *fp, const char *s, int len) { // "abcd\n" memcpy(&fp->outbuffer[fp->out_pos], s, len); // 没有做异常处理, 也不考虑局部问题 fp->out_pos += len; if(fp->flag&FLUSH_NOW) { write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos = 0; } else if(fp->flag&FLUSH_LINE) { if(fp->outbuffer[fp->out_pos-1] == '\n'){ // 不考虑其他情况 write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos = 0; } } else if(fp->flag & FLUSH_ALL) { if(fp->out_pos == SIZE){ write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos = 0; } } return len; } void _fflush(_FILE *fp) { if(fp->out_pos > 0){ write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos = 0; } } void _fclose(_FILE *fp) { if(fp == NULL) return; _fflush(fp); close(fp->fileno); free(fp); }
理解文件系统中inode的概念
文件 = 文件内容 + 文件属性 -> 磁盘上存储文件 = 存文件的内容 + 存文件的属性
文件的内容 : 数据块
文件的属性 : inode
linux中,文件的内容和属性是分开存储的
文件系统
1认识硬件----磁盘
在了解文件系统的内核结构之前,可以补充一点计算机组成原理:关于数据在磁盘上是如何存储的?(以机械硬盘为例!)
数据在计算机中以二进制形式存在**(0 和 1)**。机械硬盘利用磁性材料的特性,将这两种状态表示为盘片表面上微小区域的不同磁化方向。

主要组成部分:
盘片:通常由铝合金或玻璃制成,表面涂覆一层薄薄的磁性材料(如钴基合金)。数据就存储在这些磁性涂层上。一个硬盘内通常有多个盘片叠放在一起,固定在主轴上同步旋转。
读写磁头:每个盘片的表面(上下两面)都有一个独立的读写磁头。它们安装在磁头臂上,由音圈电机驱动,可以在盘片半径方向快速移动。
关键特性: 磁头在工作时并不接触盘片表面!它们依靠盘片高速旋转产生的空气动力学效应,悬浮在距离盘片表面仅几纳米(现代硬盘大约 0.5 微米或更小)的高度飞行。这个间隙比一粒灰尘还要小得多。
读操作: 磁头感应盘片下方磁性区域产生的微小磁场变化,将其转换为电信号(电流),进而识别为 0 或 1。写操作: 磁头线圈通过电流,产生一个精确的磁场,改变盘片下方磁性区域的磁化方向,从而写入 0 或 1。
主轴电机:驱动盘片高速旋转。常见的转速有 5400 RPM(每分钟转数)、7200 RPM、10000 RPM 甚至 15000 RPM(主要用于企业级硬盘)。转速越快,通常数据传输速度越快。
数据是如何组织和存储的?
磁道:盘片旋转时,磁头悬浮的位置在盘片上画出的一个圆形轨迹称为一个磁道。盘片上有成千上万个同心圆磁道。
扇区:磁道被进一步划分为固定大小的弧段,称为扇区。这是硬盘读写数据的最小物理单元。传统扇区大小是 512 字节,现代硬盘(高级格式化硬盘)通常使用 4096 字节(4K)的扇区以提高效率和纠错能力。每个扇区除了存储用户数据外,还包含地址信息(磁道号、扇区号)和用于错误检测与纠正的校验码。
柱面:所有盘片上相同半径位置的磁道组成一个柱面。想象一下,多个盘片的同一个磁道上下对齐,形成一个圆柱面。磁头臂移动时,所有磁头是一起移动的。因此,访问同一柱面上的不同磁道(即不同盘片)不需要移动磁头臂,速度最快。
簇 / 分配单元:这是操作系统管理文件时使用的最小逻辑存储单元(文件系统层面)。一个簇由一个或多个连续的扇区组成。操作系统读写文件时,以簇为单位进行分配和管理,而不是直接操作单个扇区。
读写数据的过程:
接收指令: 硬盘控制器接收到来自计算机操作系统的读写请求(包括要访问数据的逻辑地址 - LBA)。
地址转换: 控制器将逻辑地址转换为物理地址(柱面号、磁头号、扇区号 - CHS,或直接映射)。
(补充:转换过程可以将整个磁盘看作线性数组,算出LBA逻辑地址的编号对应的具体盘数,磁道,扇区)
移动磁头臂(寻道): 音圈电机驱动磁头臂移动到目标数据所在的柱面位置。这个移动过程所需的时间称为寻道时间,是影响硬盘速度的关键因素之一。
等待旋转(旋转延迟): 磁头到达目标磁道后,需要等待盘片旋转,直到目标扇区转动到磁头正下方。这个等待时间称为旋转延迟。平均旋转延迟是盘片旋转半圈所需的时间~
以上是对磁盘的介绍,有了这些铺垫,来看看文件系统与磁盘管理是如何结合的?
2对磁盘进行分区管理
文件由文件内容以及文件属性组成,但是内容和属性并不储存在一起,并且磁盘上也有寄存器,eg:控制/数据/地址/状态寄存器,磁盘很大,一般分区进行管理:
磁盘都是很大的,500G 的空间如果要全部管理起来会很费劲,但是由于所有的磁盘区域都能够使用同一个管理方法,因此可以对磁盘使用分区管理。
500G 和 100G 的管理方法一样,将 500G 分成 5 个 100G 管理就会轻松很多。如:将一块磁盘分成 C D E F ... 盘,管理起来明显会变轻松。
3对磁盘分区进行分组管理
管理一块 500G 的磁盘可以复用管理一块 100G 区域的管理方式,这 100G 的空间也可以划分成块组进行管理。不同文件系统有不同的分组方式,此时假设以 2G 为一块组对一个 100G 的磁盘分区划分出 50 个块组。只要能管理好 1 个组,就能管理 50 个块组 (1 个磁盘分区),能管理好 1 个磁盘分区就能管理好整个磁盘!
首先一个磁盘分区可以这样划分:
Boot Block:Boot Block 属于固件(像主板 BIOS、设备 Bootloader 等里的一段特殊程序 )的一部分,存储着最基础的启动初始化代码,这不是重点,了解即可。
Block group:需要重点了解的分组:将一个Block group可以这样进行分组:
super block:记录ext2文件系统的整体信息(整块分区),如一共有多少组,每个组的大小,每个组的inode数量,每个组的起始inode,文件系统的类型与名称(请注意,super block并不是每一个block group都会存在,通常只有一个,但也会存在备份,数量不会太多)
group descriptor table:描述每个块组的属性,如块组中 inode 位图起始位置、数据块起始位置等,方便系统管理各个块组
block bitmap:将比特位的位置与块的位置对应起来,用0/1标记data blocks中哪些块被使用,删除文件只需要更改比特位0/1就行了
inode bitmap:将比特位的位置与inode的位置对应起来,用0/1标记inode table中哪些位置被使用,删除文件还需要更改inode的比特位
inode table:一个文件对应一个inode(存储单个文件所有属性),大小为128字节,多个文件的inode组成表,用编号标识每个inode
data blocks:存储文件内容的区域,分为百万计的小块(4KB)
每一个文件都有编号,每一个块都也有编号
(ls -li查看文件对应的inode编号)inode是inode table中的数组下标,数组存储着struct inode* 地址,inode编号的数量在一块分区内是一定的,所以存在inode用完块没用完或者inode没用完但块已经用完的情况
直接索引:i_block[0]到i_block[11](共 12 个),直接存储数据块编号。一级间接索引:i_block[12],存储的是 "间接块" 的编号(该块中存放的是数据块编号列表)。
二级间接索引:i_block[13],存储的是 "一级间接块的索引块" 编号(嵌套两层)。
三级间接索引:i_block[14],存储的是 "二级间接块的索引块" 编号(嵌套三层)。
每一个分区在使用之前,都必须提前提前将部分文件系统的属性信息提前设置进对应的分区中,方便我们后续使用这个分区或者是分组
如何理解目录呢?
目录也是文件,也有自己的inode ,目录也要自己的属性,目录里面的数据块放的是该目录下文件的文件名和对应文件的inode的映射关系
1所以我们就可以解释为什么一个目录下面为什么不能有同名的文件(目录中文件是以k_v结构被目录数据块中被存储的)
2目录下,没有w权限,我们无法创建文件(能创建也不能给它搞数据)
3目录下,没有r权限,我们无法查看文件(ls指令的时候就去判断权限)
4目录下,没有x,我们就无法进入这个目录(cd 的时候要读取权限,可以的话访问并且更新环境变量)
又有一个问题?目录怎么知道他的inode呢?
所以就知道,访问任何文件就要访问根目录,在系统中找文件就要必须带绝对路径,文件查找都是从根目录开始的
问题又来了,这个就涉及效率问题
linux中有dentry缓存,根据最近使用次数的多少,把这些路径加载到缓存当中去
认识软硬链接,对比区别
软链接是一个独立的文件,有独立的inode,它里面保存的是目标文件的路径和文件名
硬链接不是一个独立的文件,因为它没有独立的inode
所谓的建立硬链接,其实本质就是在特定的目录数据块中新增文件名和指向文件的inode的编号的映射关系
任意一个文件无论是目录还是普通文件都有inode,每一个inode内部都有一个叫做引用计数的计数器(代表有多少个文件名指向我)
目录里面保存的是 文件名:inode编号 的映射关系
ln 链接文件 + 目标文件 : 建立连接的关系,直接使用是建立硬链接
-s : 建立软链接
删除链接用unlink
linux系统不允许对目录建立硬链接,容易造成环路问题(操作系统在搜索的时候会在目录下搜索)
软链接的用途:其实就是桌面上的快捷方式,下载的软件其实是装在某盘的很深处,可执行程序也在这里,通过软连接,我们就可以在其他任何地方去执行那个藏在很深地方的文件了(软链接之后,这个链接文件相当于那个目标文件)
硬链接的用途:我们发现创建一个目录dir,默认的硬链接数是2,这是因为创建目录会自动在这个目录下创建 . .. 这两个文件这个 . 文件就是指向目录dir .. 指向的是dir的父目录
认识动态静态库,学会结合gcc选项,制作动静态库
内存和磁盘进行交互的时候大小是4KB(交换数据的单位)(再物理内存中也叫页框)
为什么是4KB呢?
1减少IO的次数,减少访问外设的次数
2 基于局部性原理(访问某一部分的时候,他的旁边一部分很有可能被访问),预加载机制
操作系统如何管理内存?任然是先描述再组织
操作系统是可以看到内存的物理地址的
我们要访问一个内存,我们只要找到这个4KB的对应的Page,就能在系统中找到对应的物理页框
所有申请内存的动作,都是再访问内存page数组
Linux中,我们的每一个进程,打开的每一个文件都要有自己的inode属性和自己的文件页缓冲区,刷新的时候就去写到磁盘当中
自己设计一个静态库
把我们的方法给别人用:
1 吧我们的源文件给他
2 把我们的源代码想办法打包成库 = 库 + .h
静态库的后缀是.a想要一个静态库,需要的是源文件生成的.o 文件(. o文件再链接就变成可执行文件了),我们把.o文件去打包就变成了一个库
ar - rc libmymath.a add.o sub.o : 这就是打包成静态库的指令(ar是gnu归档工具,rc表示(replace and create) )
gcc main.c -I ./lib/include/ :这个gcc -I 指的是gcc找不到的时后(系统搜索路径和当前路径下没找到)就去指定的位置去寻找
gcc main.c -L +路径:这个选项是 -L 指定库路径
gcc main.c -l库名: 指定库名,这个库名字要去掉前缀lib和后缀.a ,注意这个-l和库名之间没有空格
为什么头文件的时候不去指定那个头文件的名字? 因为头文件的名字已经在写程序的时候已经带了
示例:gcc main.c -I ./lib/include/ -L ./lib/mymathlib/ -lmymath
我们把这个头文件放在/usr/include/下面
库文件放在/lib64/lib64/下面
可以不指明头文件和库文件的路径了,但是还是需要-l库文件 这个一定是必须的
所以我们安装库其实就是把别人的头文件和库文件装到系统指定的那个文件下
1这种库就是第三方库,在使用的时候一定要使用gcc -l ,其他的参数有时候可以不带
2如果系统中只提供静态链接,gcc则只能对该该库进行静态链接
3如果系统中需要连接多个库,则gcc可以链接多个库
生成动态库
gcc -fPIC -c sub.c add.c
gcc -shared -o libmymath.so *.o
动态库直接用gcc打包,因为动态库是gcc的亲儿子,静态库下需要用ar命令去打包
shared: 表示生成共享库格式
fPIC:产生位置无关码(position independent code)
库名规则:libxxx.so
动态库我们看到是可执行的,意思是这个动态库会以可执行程序的方式加载到内存中
gcc main.c -I ./mylib/include/ -L ./mylib/lib/ -lmymethod -lmymath我们这个指令告诉了编译器动态库在哪,但是动态库要加载到内存,所以还要告诉系统动态库在哪里,要不然运行./a.out的时候会出现
./a.out: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory
我的库的结构:
ldd+ 可执行文件 :用于查看可执行文件或共享库(.so文件)所依赖的动态链接库(shared libraries),以及这些库在系统中的路径。这对于排查"缺少库"、"版本不兼容"等问题非常有用。
我们发现这个动态库找不到,所以我们就要在/lib64/下面建立一个软连接
sudo ln -s /home/zcy/code/chapt9/test/mylib/lib/libmymethod.so /lib64/libmymethod.so
刚刚不能执行的a.out现在直接可以运行了
所以解决加载找不到动态库的方法
1:拷贝到系统默认的库路径/lib64 /usr/lib64/
2:在系统给默认的库路径/lib /usr/lib64/下建立软连接
3:将自己的库所在的路径,添加到系统的环境变量LD_LIBRARY_PATH
4:/etc/ld.so.conf.d 建立自己的动态库路径的配置文件,然后从新idconfig即可
实际情况,我们的库都是别人的成熟的库,都是直接安装到系统的方式
静态库是把内容拷贝到可执行程序中了(比如生成可执行程序后,把这个静态库删掉,可执行程序任然可以跑)动态库是在内存中被加载(生成可执行程序后,把这个动态库删掉,可执行程序不能跑)
常见的动态库被所有的可执行程序(动态链接的),都要使用,也叫共享库,会被所有的进程共享docker是linux中的内核级虚拟机
结论:建立映射,从此往后,我们执行的任何代码,都是在我们的进程地址空间中进行执行的(需要访问的文件通过页表映射到物理内存从而访问)
事实:系统在运行中,一定会存在多个动态库 -- os管理起来 ,所有的库加载情况os很清楚
那么都用一个动态库,如果出错的时候,动态库中errno会被改变,那么所有使用则个共享库的进程都会被影响吗?不会,出错的时候会发生写实拷贝!(共享的情况一般都是这样)

动态库指放到共享区里面,共享库大了,具体映射到哪里呢?
动态库被加载到地址空间中固定的位置是不可能的
所以为了使库可以在虚拟内存中任意位置加载,就让自己内部函数(比如printf的地址)不要采用绝对编址,只表示每个函数在库中的偏移量即可(只要知道库的起始地址就可以了,地址=起始地址+偏移量)
所以形成动态库的二进制代码的时候,带上的参数fPIC : 与位置无关码
这个就是用偏移量对库中的函数进行编址






















