【Linux操作系统】基础IO

一、接口使用

1.1 铺垫知识

  • 文件=文件内容+文件属性 。一个文件如果它的文件内容为空,并不代表它没有占用内存,因为它的文件属性也是要占内存的,比如文件名、文件类型、文件权限等。
  • 访问文件前,文件必须被打开,必须加载到内存中。修改文件,都是通过执行代码的方式进行修改,也就是执行这个代码的程序运行起来。
  • 是进程打开文件,进程可以打开多个文件。
  • 进程和文件的关系——struct task_struct与struct xxxxxx
  • 没有被打开的文件在磁盘中

1.2 C接口使用

权限w:打开一个文件,如果当前目录里面没有这个文件,就会新建一个。
在这里插入图片描述
往文件里面写入,使用fwrite
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果把代码中的写入注释掉,再打开一次文件,发现文件的内容被清空了。以w方式打开文件,文件内容会被自动清空
在这里插入图片描述
用 > 输出重定向也能有同样的效果:每次输入之前的内存被清空了
在这里插入图片描述
权限a: 在原来的文件内容的基础上增加
在这里插入图片描述
用 >> 追加重定向也有同样效果:
在这里插入图片描述

权限r: 读取文件内容
在这里插入图片描述
在这里插入图片描述

什么是当前路径?

进程启动时,会自动记录自己所在的目录,叫当前工作目录。打开的文件或者新建的文件就在当前路径

在这里插入图片描述
在这里插入图片描述
可以使用函数chdir来修改当前工作目录,修改后,文件在新的路径里
在这里插入图片描述
在这里插入图片描述

程序默认打开的文件流,是可以直接被使用
在这里插入图片描述
在这里插入图片描述

用printf等输出函数为例:
在这里插入图片描述
在这里插入图片描述

1.3 系统接口使用

访问文件不仅有C语言的接口,操作系统必须要有提供对应的系统调用(flags标志符还有很多)
在这里插入图片描述
在这里插入图片描述

先了解两个open函数,作用是新建或者打开文件。第一个参数是文件路径,直接传文件名就行;第二个参数是标记,第三个是设置的权限掩码。如果文件已经存在,用第一个;如果文件不存在,用第二个创建文件,记得设置权限掩码。

创建一个文件
在这里插入图片描述
在这里插入图片描述
设置的权限掩码是666,为什么是664
在这里插入图片描述
因为受系统默认的权限掩码的影响:
在这里插入图片描述
所以我们可以自己设置权限掩码,用该接口
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

关闭文件:close。用法与C接口同
在这里插入图片描述

向文件里写入——write,注意strlen字符串的长度不包括斜杠0,因为斜杠0是用于C语言中,在C语言中,斜杠0表示字符串结束,而这里是操作系统,与C无关。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这相当于C的w,写入前先清空
在这里插入图片描述

如果要追加,如下,相当于C的a
在这里插入图片描述
在这里插入图片描述
读取文件内容——read。read的返回值是所读的字符串的长度,如果返回值大于0,在打印的字符串的最后一个位置的下一位设置为0(或者说是斜杠0),用返回值作为数组下标。
在这里插入图片描述
在这里插入图片描述

open函数的返回值,
在这里插入图片描述
在这里插入图片描述

从3开始,为什么从3开始,012呢?因为012已经是系统帮我们做好了,对应前面C文件的012
在这里插入图片描述
这是系统的,前面C语言的也有,说明C语言的文件操作与系统的文件操作必定有联系,C语言中的FILE * 类型是什么?是C标准库自己封装的结构体,必定封装了fd。为什么封装fd?因为系统的012只看fd,所以标准库封装的结构体必定与fd有关。
在这里插入图片描述
在这里插入图片描述

总结:C语言的文件接口,是封装了系统调用。为了跨平台性。

二、认识fd

为什么用系统调用接口访问文件都要有fd?

因为fd的本质是数组下标,通过下标找到指定文件

在这里插入图片描述

程序运行起来,先创建task_struct结构体对象,里面有一个成员是files_struct类型的指针,该指针指向files_struct类型的结构体,结构体里面有一个叫fd_array的指针数组,数组中的每个元素指向一个文件的结构体。文件结构体是啥?就是一个打开的文件,一个文件=内容+属性。通过fd_array的数组下标找到对应的文件,fd就是返回的数组下标,然后才能对文件进行操作。

理解一切皆文件
在这里插入图片描述
在这里插入图片描述

文件fd的分配规则
规则:最小的没有被使用的数组下标,会分配给最新打开的文件
首先先来正常的,创建或者打开一个文件,给它的文件fd是3,012是给系统默认打开的标准输入、标准输出和标准错误
在这里插入图片描述
在这里插入图片描述
如果分别把0和2关闭,
在这里插入图片描述
在这里插入图片描述
在文件描述符表中,正常情况下数组下标是对应的:
在这里插入图片描述
用0做例子,2也是一样的。关闭0,open函数创建或者打开一个文件,根据fd的分配规则,下标0会给log.txt,最后打印时得到fd是log.txt下标,所以是0
在这里插入图片描述

用系统调用实现输出重定向的两种方式
一、close(1)
在这里插入图片描述
在这里插入图片描述
为什么不打印1呢,而且查看文件内容时发现是要打印的内容?首先,根据前面的知识,关闭了1,也就是下标不指向显示器了,数组下标指向log.txt,而printf函数只认识stdout->file = 1,也就是printf只认这个1,即它打印或者写入的内容就看下标1指向谁。所以,printf就不会往显示器写内容,也就是说,显示器是空,打印它所以也是空的;printf会往log.txt写入内容,就是printf要输出的格式,所以查看文件log.txt时就是要打印的内容,而打印显示器是空的。
在这里插入图片描述

二、系统调用接口——dup2()
在这里插入图片描述
old拷贝给new,这样原来new位置的下标,也是old。拷贝的是指向的内容,也就是下标1还在,因为printf只看下标1,但是它的内容即指向的由显示器变成了log.txt,然后跟前面的效果一样。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同时实现下追加重定向(追加内容,叠层层;重定向,由显示器到log.txt)
在这里插入图片描述
在这里插入图片描述
模拟标准输入重定向: 下面一个是用小于号的,然后再dup2。文件里面有内容,读取的就是文件的内容;没有就是空
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、缓冲区

什么是缓冲区?

缓冲区是一块内存区域

为什么要有缓冲区?

以寄快递为栗子,给同学寄快递的时候,假如我自己在福建,同学在北京,为了快速的给他寄快递,不可能是自己把东西拿到北京再给他,而是去楼下的快递站,填好寄什么,人、时间、地点等等就好了,大概就几分钟,然后就上楼并且跟同学说已经送给你了。在系统要对文件进行修改,其实就调用对应的接口就行了,至于后面的具体怎么操作,是缓冲区的事情,所以提高了使用者的效率。 我们知道去寄快递,并不是当天寄快递部门就当天把快递寄出去,而是等他们积累到一定数量的快递,然后同时送出去。在缓冲区也是一样的,等到数据聚集到一定时间,一次拷贝出去(也叫刷新),提高了整体效率。所以,有了缓冲区,可以用空间换时间

在这里插入图片描述

语言中的缓冲区与内核的缓冲区:怎么做?

首先,这两个的缓冲区不是一个东西。C语言层,要修改某个文件的内容,需要调用对应的库函数,调用库函数后,并不是就直接把传入的信息交给系统调用,然后就让系统调用把数据刷新到磁盘中。系统调用是有成本的,好比不可能把快递给楼下的快递站,他就立即给你送出去,这样成本太高了。C语言层,有一个缓冲区专门是用来暂存调用库函数后的数据信息。怎样刷新如下第一张图(应用层或用户层)。数据积累到一定数量,然后系统调用接口就一次将这些数据交给内核。为什么是系统调用?因为修改文件->访问操作系统->必须通过系统调用接口。然后在内核层,fd找到对应的文件,每个文件都有自己的缓冲区,把数据传入到缓冲区中,这个缓冲区就是内核角度的缓冲区,然后缓冲区再把数据更新到磁盘中,内核的缓冲区是由OS自主决定什么时候刷新的。

在这里插入图片描述

缓冲区刷新的3种方式:

  • 无刷新,无缓冲
  • 行刷新——显示器
  • 全缓冲,全部刷新;普通文件,缓冲区被写满才刷新

可以强制刷新;进程退出时也会自动刷新

缓冲区具体在哪?

在FILE * 结构体中。FILE * 是C语言使用文件操作时定义一个文件的类型,这个类型其实是一个结构体,结构体中就有一个缓冲区,所以每个文件都有一个缓冲区,我们调用C接口,把数据通过标准输入、输出,其实是把数据交给缓冲区,等适合的时候再把缓冲区的内容刷新(交给系统调用),所以我们使用C接口时很方便,直接调用就完了。

用代码证明缓冲区的存在:
在最后没有fork之前,往显示器打印和往文件打印(输出重定向)都是一样的,是什么打印什么:
在这里插入图片描述
在这里插入图片描述

然后修改下代码,最后有fork,往显示器打印没有变化,往文件打印发现调用C接口打印的又多了一行:
在这里插入图片描述
在这里插入图片描述

为什么用系统接口不会有两份?

因为调用系统的接口,不管有没有fork,都是直接将数据交给系统的缓冲区,剩下的就是操作系统的事情;主要是系统接口把数据给OS的缓冲区与应用层的缓冲区不一样,OS缓冲区如何处理是OS自己决定的。

为什么往显示器打印不管有没有fork都没有两份?

因为往显示器进行打印,在应用层的缓冲区的刷新方案是行刷新,即遇到反斜杠n就刷新,OS的缓冲区也可以这么处理。

为什么fork后用C接口打印结果有两份?

( 都是重定向到文件才有区别)原因:往文件打印的刷新方案是缓冲区被写满才刷新,缓冲区的容量是比较大的,所以这里的写入不足以写满缓冲区。让缓冲区刷新有两种方式:一是强制刷新,二是进程退出时,会自动刷新。选择让进程退出再刷新,必定经过后面的fork,fork之后,创建了一个子进程,原来的是父进程,前面写入到缓冲区的数据是父进程的,父子进程目前共享父进程的数据。然后代码执行完了,进程要退出,就要刷新缓冲区,刷新缓冲区就是清空数据,也是修改数据的一种方式,修改即父进程重新写入数据,这时候就要发生写时拷贝(任意一方写入,都会发生写时拷贝),然后子进程得到前面父进程的数据,最后父子进程两个的缓冲区的数据再经过一系列步骤交给操作系统内部,更新到磁盘上,所以看到的有两份。

在这里插入图片描述

四、文件系统

文件=内容+属性,内容和属性是分开存储的,文件内容的大小是不确定的(可变),但是文件属性的大小是固定的,文件属性的分类是一样的,但具体分类的内容不一样。比如属性有权限、修改时间、文件类型和大小等等,每个文件的属性都有这些,但是不同的文件属性的内容不一样(A文件的权限是r,B文件的权限是rw)。有一个描述文件属性的结构体叫inode,这个结构体的大小是128字节。系统中,标识一个文件不是用文件名,而是inode编号,所以属性里面没有文件名。查看文件的inode编号:
在这里插入图片描述
一个磁盘的空间是很大的,要进行划分,管理好一个,就能管理好多份。每个Block Group都有着相同的结构组成。一个磁盘分成多个分区,一个分区有多个分组,一个分组=一个Block group,一个分组有多个inode编号
在这里插入图片描述
inode Table:存放每个文件的属性(一个inode编号是整个分区唯一)
inode Bitmap:每个比特位表示一个inode(文件)是否可用。比特位位置:第几个inode。比特位内容:是否使用过
Data blocks:有多个数据块,一个数据块存放一个文件的内容
Block Bitmap:比特位位置:表示块号(一块4kb)。比特位内容:是否使用过

创建一个文件过程?

当用户输入创建一个新文件的指令,文件系统会在一个分组里的inode Bitmap找最近的没有被使用过的inode,(分组也是有顺序排号的),根据这个inode去inode Table里面对应的inode结构体里面填好字段,也就是文件的属性信息;那文件内容怎么办?通过文件属性中的block数组,大小是15,然后映射对应的数据块(有内容就填,没有就什么也没有),Block Bitmap具体的比特位也要随着数据块有使用然后标记为1(1是使用过)。

删除一个文件过程?

删除先要查找,查找一个文件都要通过inode编号,根据编号确定在哪个分组,然后在inode Bitmap找到对应的inode,删除Block Bitmap和inode Bitmap的标记(由1变0)。不需要删除内容(属性内容和数据内容),因为内容可以被覆盖。

关于block数组:文件属性中有block数组,大小是15,通过数组下标映射对应的文件内容。可是如果文件很大这么办?即数组下标只有15个,但是数据块假设有1000个,这时候会根据情况采用多级索引的方式,就能同时映射多份内容
在这里插入图片描述
超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。GDT,Group Descriptor Table:块组描述符,描述块组属性信息。超级块坏了,影响整个分区,即整个文件系统;GDT坏了,影响整个分组

五、软硬连接

创建一个软链接:
在这里插入图片描述
软链接是有自己的inode编号,所以它是文件

创建一个硬链接:
在这里插入图片描述

硬链接不是一个文件,因为它的inode编号和目标文件相同。是新建文件名,然后与inode编号形成映射关系。有多个硬链接,就有多个新建的文件名与inode联系,inode属性里面有一个引用计数,用来表示几个这样的文件名指向当前的inode
在这里插入图片描述

具体引用计数表示——硬链接数
在这里插入图片描述

软硬链接的原理:
硬链接本质是在指定目录下新建一个文件名与目标文件形成映射关系,并让inode引用计数++
软链接是一个独立的文件,内容放的是目标文件的路径。(类似于windows的快捷方式)

应用场景
软链接:张三写了一个程序,可以给别人运行,但是不能看到源代码。程序里面的比较多的目录,要找到可执行程序有点麻烦,用软链接可以快速执行要执行的程序代码,因为软链接的内容就是目标文件的路径,所以最终就给别人软链接就行了
在这里插入图片描述
把目录a删除了,里面的可执行程序和代码没了,只有软链接在,也能运行
在这里插入图片描述

硬链接:可以计算一个目录下有多少个子目录。创建一个目录,目录里面没有内容,它的硬链接数是2
在这里插入图片描述
首先在file1_5_26的目录中,mydir是一个文件名,它指向引用计数,硬链接数为1;然后在mydir目录中,./ 也是一个文件名,指向与mydir一样的引用计数,再加1引用计数为2
在这里插入图片描述
所以可以在mydir目录中新建多个目录,只要去掉前面说的两个,剩余的硬链接数就是新建的子目录数量:
在这里插入图片描述
可以给目录建立软链接,但是不能给目录建立硬链接:因为环路问题。
在这里插入图片描述

假设给家目录建立硬链接,在如下图的目录中,硬链接的inode是指向家目录home的。当查找一个文件时,要进行路径解析,即递归,当到了dir时,把它也当做一个目录,但是它指向的是家目录,所以返回到了家目录,然后又路径解析,又到了dir,又回去,循环了(查找文件会出现循环问题)。那为什么普通文件不会?因为普通文件不是目录,不管它在哪层目录,都不会影响路径解析,也就是说找文件递归的时候是看目录的。
在这里插入图片描述
就算硬链接是自己给自己,比如:。。也会形成小范围的环路问题。所以系统为了杜绝无限循环,就不让用户自己给目录创建硬链接
在这里插入图片描述
既然目录不能给硬链接,为什么./和…/可以? 因为这是系统自己给目录创建的硬链接。

六、动静态库

库是什么?

库是写好的、可以进行复用的代码的打包,形成一个文件。

为什么要有库?

1、提高开发效率;2、隐藏源代码

如果没有用库打包就会比较麻烦(如下示例):自己写了一个程序,有头文件和.c源代码,给别人使用时,不能给源代码,只能给头文件和.o目标文件;在其他用户使用时,他有自己的main函数,把main.c也变成目标文件,和另一个.o目标文件的函数,生成可执行程序。这还是只有一个目标文件给当前这个使用者,如果.o目标文件很多,用户就要一个一个的输入,再变成可执行程序,就很麻烦。有库只需要给一个库文件(因为库把.o都打包好了)+一堆头文件。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
库分为动态库和静态库,在Linux下,动态库是以 . so 为后缀,静态库是 . a ;Windows下,动态库是 . dll,静态库是 . lib
动态库与静态库主要区别体现在动态和静态 。静态库是在编译阶段,会将汇编生成的目标文件.o与要用的库一起打包到可执行程序中,这个过程也叫静态链接。动态库是在程序运行时才会链接,过程叫动态链接。
在Linux下我们运行的程序默认链接的是C标准库,是动态链接。可用下面的指令查看使用的是什么库(动态库or静态库)和什么链接:
在这里插入图片描述
如果要静态链接,使用-static选项
在这里插入图片描述
关于的库的名字 。库的真实名字是xxx(libxxx.a/so),要去掉前缀和后缀

动态库的优缺点:
优点:节省资源,不会出现太多重复的代码
缺点:依赖库,一旦库缺失,程序就无法正常运行

静态库优缺点:
优点:不依赖库,同类型平台中都可以直接运行使用
缺点:可执行程序体积大,浪费大量资源

6.1 静态库的制作和使用

生成静态库的指令:ar -rc 静态库名 一堆.o文件
把.c文件生成.o目标文件,然后用上面的指令生成静态库,拷贝一份给user(还有头文件)
在这里插入图片描述
gcc编译下,发现找不到函数
在这里插入图片描述
因为gcc默认在系统指定目录下找库,同时要告诉gcc链接哪个库:最后就能运行了
在这里插入图片描述
在这里插入图片描述

6.1 动态库的制作和使用

先把.c变成.o目标文件,(注意指令与静态库的区别)
在这里插入图片描述
生成动态库的指令:gcc -shared -o 动态库名 一堆目标文件
在这里插入图片描述

如果感觉这样比较麻烦,可以使用makefile:快速的生成.o目标文件和动态库(效果与前面一样,只是方便了很多)
在这里插入图片描述
在这里插入图片描述

把动态库和头文件拷贝到user中,然后生成可执行程序(与静态库一样,也要-l 和-L),编译、运行
在这里插入图片描述

发现找不到动态库在哪(后面等会解决)。如果动态库在当前路径,有的其实是可以跑的(其实这与环境有关系,这里是ubuntu运行跑不过,但是centos7可能可以运行成功,但不是很重要)。如果动态库不在当前路径,Centos或者ubuntu都不行
在这里插入图片描述

改进makefile,将头文件和动态库合并到一个文件中,同时生成这个文件的压缩包,只需要压缩包给用户就行了
在这里插入图片描述
在这里插入图片描述
解压压缩包,合并文件的树状结构
在这里插入图片描述
在这里插入图片描述
编译生成可执行程序,发现找不到头文件
在这里插入图片描述
因为gcc不知道头文件在哪,所以使用 杆大i 指明头文件的路径;同时 -L 和 -l也要带上
在这里插入图片描述
运行一下跟前面的错误一样
在这里插入图片描述
解决方法一:将库安装到系统,即拷贝到系统指定的路径下,注意ubuntu下拷贝的目录
在这里插入图片描述
在这里插入图片描述
有了前面的操作,也就不需要 -L 和 -I 了,但是 -l 还是要的,编译、运行如下。对于静态库,只要编译成功就不需要了;对于动态库,将库安装到系统中,既可以支持编译(指令不用写一大串<-I和-L>),又可以支持运行(不会出现找不到的问题)。(ubuntu安装的路径是/lib/x86_64-linux-gnu/;centos安装的路径是/lib64/)(后两个图是解决前面的)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

查看搜索路径
在这里插入图片描述
解决方法二:软链接
在这里插入图片描述

对比动静态库

  • 如果我们同时提供动态库和静态库,gcc默认使用动态库
  • 如果一定要用静态库,必须带上static选项
  • 如果只提供静态库,只能对该库进行静态链接,但是程序整体不一定是静态链接的
  • 如果只提供动态库,默认只能动态链接,要静态链接就会报错

七、理解动态库加载

系统角度理解——共享库
当一个程序在CPU上运行起来(CPU的寄存器还没获取到可执行程序的数据),就会创建一个task_struct结构体对象,即进程,同时有自己的地址空间和页表,地址空间(本身是一个数据结构mm_struct)上的数据(划分区域)已经被初始化好了(谁初始化的?怎么初始化的?),OS会把可执行程序加载到物理内存,有自己的一块空间和物理地址,在地址空间的正文代码区和数据区的虚拟地址通过页表映射到对应物理内存的物理地址,得到可执行程序的代码和数据,然后最终交给CPU的寄存器,整个程序就运行起来了。(注:OS是可以管理多个库的,所以内存里面可以有多个库)
在这里插入图片描述
库默认是一个磁盘文件,并不会跟着可执行程序一起加载到内存。只有在需要的时候,OS才会将库加载物理内存中,同样也有自己的一份空间和物理地址,空间里面的就是库的代码和数据。不管多少个程序要使用这个库,在物理内存中只需一份,所以这个库就叫做动态库,也叫共享库。然后通过页表映射,动态库的代码和数据会到地址空间的共享区。如果没有程序要用这个库,那么OS会释放资源。所以动态库在系统进程中是公共的代码和数据,只需存在一份。

理解编址——可执行程序
可执行程序本身有自己的格式信息,可执行程序在没有加载到内存之前,已经编译好了,也有自己的地址。可执行程序没有加载之前,它的内部就基本按照类别(各种属性)划分号区域了,这里主要用代码和数据为例。可执行程序内部的代码和数据每行都有自己的地址,按照绝对编址的方式;在程序的头部,有一个结构体包含main函数的地址,因为可执行程序加载到内存后,CPU的寄存器得到程序的代码和数据要知道从哪开始运行,给了main函数地址,就知道运行的起始位置。虚拟地址是相对编址得到的,start可以从0位置开始,加上偏移量,偏移量也是地址,是程序的地址,最终形成虚拟地址,注意:虚拟地址其实就是可执行程序的地址,只不过用另一种形式表现出来。也就是说,虚拟地址是对代码和数据的地址重新起的别名,但是每个虚拟地址对应程序的地址是同一个东西。所以我们可以认为整个程序内部其实只有一个区域,最终可执行程序加载到内存后编译好的虚拟地址给地址空间,所以地址空间的数据从哪来到?就是可执行程序给的。总结:可执行程序在磁盘中,它的地址一般叫逻辑地址,被加载到内存中,一般叫虚拟地址。
在这里插入图片描述

理解(一般程序)静态库加载
静态库加载到内存,首先要把静态库的代码和数据在编译阶段给可执行程序,在可执行程序的内部编址然后确定好静态库的代码和数据的位置,接着后面的过程与一般可执行程序的加载过程是一样的。

磁盘中的可执行程序在被加载到物理内存之前,就已经有自己的格式信息,代码和数据按照绝对地址的方式编址。当开始运行时,可执行程序要被加载到内存,变成一个进程,首先要有描述它的pcb对象,还要有地址空间和页表。mm_struct的数据是由可执行程序初始化的,不是说每个mm_struct的正文代码、数据区都是固定,而是看初始化它的可执行程序的大小,所以不同程序的地址空间的正文代码和数据区的大小是不一样的。在没加载之前,可执行程序的地址叫逻辑地址,加载后,叫虚拟地址。初始化mm_struct的是虚拟地址,即可执行程序的每个字段的代码和数据的地址,填上就好了。物理内存加载了可执行程序的代码和数据有自己一块空间,虚拟地址到物理内存中转换为物理地址。然后地址空间的数据区、代码区的虚拟地址通过页表的映射到对应的物理地址,进程PCB对象就获取可执行程序的代码和数据,这样CPU处理要运行的进程时,就可以开始运行了。
在这里插入图片描述
但是,从哪开始运行?CPU内部有一个寄存器叫PC指针,也叫程序计数器(保存正在执行指令的下一条指令的地址),它获取到可执行程序的表头入口地址,开始运行的地址就是这里。如何将代码和数据运行起来?CPU内部有一个寄存器叫cr3,用来找到页表的起始地址,通过硬件MMU和页表,将前面的pc指针的虚拟地址映射对应的物理地址(刚开始肯定是从pc指针开始拿虚拟地址,后面就都是指令指令寄存器了),映射到对应的物理地址后,就能访问可执行程序物理地址对应的虚拟地址,然后执行该虚拟地址的代码和数据,就相当于执行某条语句,如果是调用函数的话,CPU的指令寄存器会将要调用的函数的虚拟地址获取,然后通过MMU和页表与物理内存建立映射关系,访问到可执行程序的虚拟地址并执行,以此反复整个程序就跑起来了。

理解动态库加载
动态链接是在可执行程序开始运行时才链接的,库(动态库)将自己的start(起始地址)+偏移量(代码和数据的地址)给可执行程序,这个过程叫动态链接。可执行程序运行起来,首先有pcb对象+地址空间+页表,然后可执行程序加载到内存(代码、数据和所调用的库的函数的起始地址+偏移量),同时地址空间的数据初始化好了,正文代码区的虚拟地址通过页表映射对应的物理地址。当要执行的代码和数据是库的(注意:执行程序的是CPU),操作系统也会将库的代码和数据加载到内存(有了自己的物理地址),同时把库的虚拟地址初始化共享区,这样两者(物理地址和虚拟地址)就可以通过页表有了映射关系。然后正文代码会去共享区通过虚拟地址和页表最终获取到库的数据和代码(获得是给CPU的,因为是CPU处理)。怎么获取代码和数据(也就是前面的函数)?内存里面可以有多个动态库,所以通过库在地址空间的起始位置+偏移量就能对库的数据和函数进行访问(起始位置:哪个库;偏移量:哪个函数)。有多个进程也要使用这个库,同理前面的;因为动态库在内存中只需一份。
在这里插入图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值