文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

C语言与操作系统的内存布局

2024-12-01 13:20

关注

1,所有的全局变量都被常量初始化,

2,不需要运行时的状态,

3,也不需要在main()函数之前运行额外的初始化代码。

操作系统的初始化是很复杂的。

在C语言写成的内核main()函数运行之前,操作系统要运行一段很复杂的汇编代码,以完成内核的内存初始化。

这段汇编代码包含着很多重要的内核全局数据,它是由内核作者精心定制的,没法由编译器自动生成。

对于内核程序员来说,编译器做的事越少越好,但是又不能像汇编器那么少​

C语言适合写操作系统,我觉得跟丹尼斯-里奇发明它的目的就是为了写Unix有关:不好用的地方已经被优化过了。

1970年,丹尼斯-里奇怎么一边改unix系统的代码、一边改cc编译器的代码的咱就不回忆了。

这里说说C语言和操作系统的内存布局。

1.C语言的内存布局。

C语言编译连接之后的可执行文件,分为:

1) 代码段(.text),

2) 只读数据段(.rodata),

3) 数据段(.data),

4) 堆 (heap),

5) 栈 (stack),

其中需要存储在文件里的只有前3个,

后2个在进程运行期间是动态变化的临时数据,并不需要存储在文件里。

代码段的权限是只读+可执行,

只读数据段的权限是只读,

数据段、堆、栈的权限都是可读可写的,但不能运行。

如果系统内核发现了进程的内存权限是错误的,那么就是段错误:信号是SIGSEGV。

*("hello") = 1;

这种代码肯定是“段错误”的,因为常量字符串位于只读数据段,它的内容是不可写的。

通过缓冲区溢出来覆盖栈的返回地址的黑客代码,也会被系统内核发现运行地址不在代码段,所以也是段错误。

2.内核的内存布局。

内核的内存布局,包含这几个重要的全局数据:

1)内核页表

它是内核的虚拟内存与物理内存的映射。

在开启分页机制之前,就要设置好内核页表的前几页:

至少要把内核代码所在的内存空间映射到页表里,否则开启分页机制时就直接出错了。

在32位机上,它是由页目录-页表构成的2级数组:

页目录里的每一项记录每个页表的物理地址,页表里的每一项记录每个内存页的物理地址。

在64位机上页表的结构更为复杂,intel手册上有:我没仔细看过,有兴趣的可以看看。

1个内存页是4096字节,所以物理地址的最低12位全是0,用来记录每个页的读写权限。

页目录里每项的最低12位,用于记录它对应的整个页表的读写权限。

1个页表记录1024个页,每个页4096字节,所以1个页表管理4M的物理内存。

2)中断向量表

它存放各种硬件中断、以及int 0x80软件中断的处理函数,也叫中断服务例程(irq)。

int 0x80软件中断,就是Linux系统调用的中断号。

当然,在64位机上,直接使用syscall汇编指令就行。

syscall的软件中断机制,是intel在64位上又新造的一种进入CPU ring0特权级的指令,使用方式跟之前的int指令不大一样。

我怀疑intel的CPU研发也是有KPI的,怪不得Linus大牛也经常吐槽intel的CPU设计。

一个版本加一个新的指令,纯属给系统软件的开发者找难题​

中断向量表,也是个256项的数组,每项都是某个中断的函数指针。

在中断被触发之后,CPU就是靠这个数组去查找对应的中断处理函数的。

3)全局描述符表

它描述的是内核的内存布局,每项8个字节,共256项。

但实际上,只需要使用前5项就行:

0x0,不使用,

0x8,内核代码段,

0x10,内核数据段,内核堆栈段,它们2个的权限一样,可以共用一项。

0x20,任务门的描述项,

0x28,局部描述符表的描述项。

siska内核demo的内存布局

因为每项都是8字节,所以地址都是8的倍数。

4)局部描述符表

它是用于进程的,进程因为跟内核的权限不同,所以进程的段选择符都在局部描述符表里:

内核的段选择符是0x8,进程的是0xf。

段寄存器CS、DS、SS,到了保护模式下都成了段选择符,真正的内存地址在GDT表里。

在16位的实模式下,它们才存储真正的段的内存地址。

5)任务门

CPU把每个进程看做一个任务,所以要切换进程时需要任务门的描述结构。

它是104个字节。

但是,Linux系统的进程切换是软切换:任务门的描述结构只在系统初始化时加载一次,具体的进程切换时只切换页表和内核栈,然后就可以骗过CPU了​

重新加载任务门的时间消耗比较大,而软切换的时间消耗比较小。

intel的这个设计,也是不受Linus大牛待见的设计之一​

6)系统调用表

它也是一个大数组,它的每一项也是函数指针。

系统调用的入口是int 0x80软件中断(64位机上是syscall指令)。

进入内核之后,每个号码对应一个系统调用。

open()、close()、write()、read(),这些系统调用都有各自的号码,这些号码就是系统调用表的数组索引。

如果open()的系统调用号码是i,那么open()在内核里实际运行的就是这行代码:

syscall_table[i]();

7)物理内存的管理数组

物理内存的管理结构,是一个很大的一维数组。

假设物理内存有4G,1个内存页是4K,那么这个数组的元素个数就是1024x1024,1M。

数组的每一项,记录1个物理内存页的状态。

如果每项是4个字节的话,那么管理效率就是:(4096-4) / 4096。

管理数据所占的字节数越多,对物理内存的浪费越大。

get_free_pages()函数,就是通过查看这个数组来分配物理内存页的。

因为内核是一个高并发环境,这个管理结构里必须要有自旋锁,以控制多个CPU的并发访问。

自旋锁+引用计数就至少8字节,所以这个数组也是非常浪费内存的。

如果多个线程之间要共享内存,那么只要把同一个物理内存页映射到这几个线程的页表里,然后增加物理内存页的引用计数就行:

这就是共享内存在内核里的本质。

8)进程的页表和内核栈

进程的页表和内核栈,不属于内核的全局数据,而是附属于进程的局部数据。

内核在调度某个进程的时候,就把页目录基地址寄存器cr3和栈寄存器rsp切换成这个进程的页表和内核栈。

不同的进程之间,之所以有各自的虚拟内存空间,互相不干扰,就是因为每个进程的页表不一样。

要在进程之间共享内存,也跟线程之间共享内存一样,把同一个物理内存页映射到它们各自的页表就行。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯