Linux下系统API和库函数的关系

  • ​​载入代码​​
  • ​​文件指针​​
  • ​​调用write函数​​
  • ​​内核​​
  • ​​ELF文件​​

载入代码

首先写入一段代码

void main() {
printf("Hello World\n");
}

文件指针

先明白其调用了文件指针

FILE* stdout;   // 标准输出

其包括三个部分

在此介绍一下buffer何时刷新(同步):

  • 手动刷新(flush)
  • fclose()关闭
  • buffer满了

调用write函数

同时,向下调用write函数,到此为止是应用层

write(1, "Hello World\n", 6);

进入内核,调用系统的write函数

sys_write("Hello World");

调用设备驱动(显示器,网络等等)

内核

计算机是由各种外部硬件设备组成的,比如内存、cpu、硬盘等,如果每个应用都要和这些硬件设备对接通信协议,那这样太累了。

所以,这个中间人就由内核来负责,让内核作为应用连接硬件设备的桥梁,应用程序只需关心与内核交互,不用关心硬件的细节。

Linux下系统API和库函数的关系_系统调用


内核有哪些能力呢?

现代操作系统,内核一般会提供 4 个基本能力:

  • 管理进程、线程,决定哪个进程、线程使用 CPU,也就是进程调度的能力;
  • 管理内存,决定内存的分配和回收,也就是内存管理的能力;
  • 管理硬件设备,为进程与硬件设备之间提供通信能力,也就是硬件通信能力;
  • 提供系统调用,如果应用程序要运行更高权限运行的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。

内核是怎么工作的?

内核具有很高的权限,可以控制 cpu、内存、硬盘等硬件,而应用程序具有的权限很小,因此大多数操作系统,把内存分成了两个区域:

  • 内核空间,这个内存空间只有内核程序可以访问;
  • 用户空间,这个内存空间专门给应用程序使用;

用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。

因此,当程序使用用户空间时,我们常说该程序在用户态执行,而当程序使内核空间时,程序则在内核态执行。

应用程序如果需要进入内核空间,就需要通过「系统调用」,下面来看看系统调用的过程:

其中进程管理中的PCB(进程控制块)可以看做一个结构体,其中有一个文件描述符表

Linux下系统API和库函数的关系_目标文件_02


内核程序执行在内核态,用户程序执行在用户态。当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作。

其中每一个数对应一个文件描述符(0<= <=1023),通过这个文件操作符可以去操作文件(默认打开0,1,2)

  • 0:标准输入,stdin
  • 1:标准输出,stdout
  • 2:标准错误,stderr

按照上面的代码,此时在用户态的write应该是

// 1:表示标准输出
// “hello”:表示要输出的内容
// 6:表示内容的大小
write(1, "hello\n", 6);

ELF文件

此内容作为扩展

ELF文件(目标文件)格式主要三种:

  • 可重定向文件(Relocatable file):文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即 linux 通常后缀为 .a 和 .o 的文件)这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file),内核可加载模块 .ko 文件也是 Relocatable object file。
  • 可执行文件(Executable file):文件保存着一个用来执行的程序。(例如bash,gcc等)
  • 共享目标文件:即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。

如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空 间;另外如果拿它们放到 Linux 系统上一起运行,也会浪费掉宝贵的物理内存。

一般的ELF文件有三个重要的索引表:

  • ELF header:在文件的开始,描述整个文件的组织。
  • Program header table:告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
  • Section header table:包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
  • sections 或者 segments:segments 是从运行的角度来描述 ELF 文件,sections 是从链接的角度来描述 ELF 文件。