Linux 内核源代码情景分析 chap 1 预备知识

47 篇文章 0 订阅
32 篇文章 1 订阅

最近一段时间面试了一些公司后发现, 自己对操作系统的一些概念还是理解的不够深刻, 之前看的是《操作系统概念 第六版》, 这次觉得应该加点难度, 正好就开始看这本《Linux 内核源代码情景分析》好了。

1.1 Linux内核简介

1. 微内核和宏内核的区别

内核中提供各种服务的成分和使用这些服务的进程之间形成了一种典型的Client/Server 关系, 由于有些服务提供者并不是一定非得留在内核中, 他们仅仅只是服务一部分对象, 这些服务提供者完全可以从内核中转移出来到进程的层次上, 这样就可以让内核得到精简。最终内核中仅剩下进程间通信的服务了。此时的内核, 我们称为微内核。 与之相对应的内核称为 宏内核。

2. linux 源码的安装位置

一般在 /usr/src/linux 目录下。
像我的ubuntu 是在如图所示的位置中, (终于知道源码的位置了, 想想有些小高兴=_=~~)
这里写图片描述

3. linux 版本号

linux 版本号 x.yy.zz, x 表示内核在涉及或者实现上的重大改变, yy 表示版本变迁, 偶数为稳定版, 奇数为开发版

4. gpl 协议

如果一个软件是在gnu源代码的基础上修改扩充来的, 那么这个软件的源代码也必须对使用者公开, 但可以收费

1.2 Intel X86 CPU 系列的寻址方式

1. 实模式

为处理地址总线和数据总线数目不匹配的问题(20 地址总线宽度, 16 数据总线宽度), intel 在8086cpu 中提出了 用段地址 + 偏移的寻址方式。在这种模式下, 内存是完全暴露的, 很容易被攻击

2. 保护模式

保护模式还是建立在段寄存器的基础上的, 通过添加两个新的段寄存器 FS, GS实现。
他的基本思路是, 在保护模式下改变段寄存器的功能, 使其从一个单纯的基地址(变相的基地址)编程一个指针。

如果说实模式是直接通过段寄存器获取目标位置的地址, 保护模式实际上是在这个过程中间加了一层间接性, 他需要通过访问相应的 地址段描述结构 才能获取基地址

为实现这个效果80386cpu 增加了两个寄存器, GDTR 全局性的段描述表寄存器, 和 LDTR 局部性的段描述表寄存器, 访问这两个寄存器的指令被设计为特权指令。
同时, 段寄存器的定义也被相应修改了
这里写图片描述
只有将段寄存器中的低 3 位屏蔽后, 与GDTR 或者 LDTR 中的基地址相加 才能得到描述表项的起始地址

3. intel 的平面地址

这其实是将段寄存器设置为0 的一种特例情形

4. 80386 虚存管理

当需要访问一段内存的时候, 先访问到他的描述表项, cpu 会检查描述表项中的 p 标志位, (p 标记当前描述表项所指示的内容是否在内存中), 如果 p 为0, 内容不在内存中, cpu 执行一次中断, 将相应内容载入内存空间

5. 80386 权限管理

当需要改变一个段寄存器的内容的时候, cpu 会检查, 确保该段程序的当前执行权限和段寄存器所指定的要求的权限均不低于所要访问的那一段内存的权限dpl

2.1 i386 的页式内存管理机制

1. 段式管理和页式管理

页式管理相对段式管理更加先进。 段式存储管理机制的灵活性和效率都比较差。 一方面是 ‘ 段’ 是可变长度的, 这就给盘区的交换操作带来不便, 另一方面, 如果未来增加灵活性而将进程的空间划分成很多的小段的时候, 就需要在程序中频繁的改变段寄存器的内容。 因而, 段式管理相对而言是比较差的。

2. 80386 的页式管理

由于80386 是通过段式存储来实现保护模式的, 因而, 80386 的页式管理就必须建立在段式存储管理的基础上。
ie, 80386中, 页式存储管理是通过在段式存储管理所映射的地址上再加一层地址映射得到的。
线性内存, 由段式存储管理所映射得到的地址。
总结一下: 段式存储管理先将逻辑地址映射成线性地址, 然后再由页式存储管理将线性地址映射成物理地址, 或者, 当不使用页式存储管理的时候, 就将线性地址直接用作物理地址。

3. 线性地址到物理地址的映射过程

  1. 从CR3取得页面目录的基地址
  2. 以线性地址中的dir 位段为下标, 在目录中获取相应页面表的基地址
  3. 以线性地址中的page位段为下标, 找到相应的页面描述项
  4. 将页面描述向中给出的页面基地址与线性地址中的offset 位段 相加得到物理地址
    这里写图片描述
    这里写图片描述

4. 为什么使用两个层次结构呢

出于空间效率的考虑, 如果只是单个层次的话, 由于很难有程序会用到4G的全部空间, 大部分情况下表项是空的, 造成浪费。
然而使用两个层次的结构, 页表可以根据需要来设定,可以不必为空的目录表项设置相应的页表, 节省空间
32bit cpu 页表项最多1024 个, ie, 一个页面 4KB, 而 64bit 中 一个页面时 8KB

1.4 Linux 内核中的C语言代码

1. 单次执行宏

#define DUMP_WRITE(addr, nr) do {memcpy(bufp, addr, nr); bufp += nr;} while (0)

2. 空操作宏

#define prepare_to_switch() do {} while (0)

3. Linux 内核中的双链队列管理

底层是一个list 链表

struct list_head{
    struct list_head * next, * prev;
}

通过将这个部分加入到需要管理的对象中, 可以实现对相应对象的管理

typedef struct page{
    struct list_head list;
.........
    struct page * next_hash;
.........
    struct list_head lru;
.........
} mem_map_t;

那么如何获取一个page的地址呢?

#define memlist_entry list_entry

#define list_entry(ptr, type, member) \ 
    ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))

而page地址的获取方式如下:

page = memlist_entry(curr, struct page, list)

经过C 预处理器的文字替换之后得到:

page = ((struct page *)((char *)(curr) - (unsigned long)(&((struct page *)0)->list)));

ie, 通俗的来讲:
已知 结构体中 list 项的地址信息, 只要减去这个list项在这个结构体中的偏移, 就可以得到这个结构体的地址了

1.5 Linux 内核源码中的汇编语言

1. 使用汇编的重要性

  1. 有些需要和硬件打交道的代码, C语言没有相应相应的指令
  2. CPU 的一些特殊指令也没有C 语言部分
  3. 有些函数,程序, 过程会被频繁的调用, 时间效率非常重要, 需要使用高效率的汇编
  4. 有些空间效率有要求的, 也必须使用汇编

2. GUN 的386 汇编语言

GNU 中采用的是 AT&T 的汇编语言, 这和intel 的汇编语言的差别是相当大的

3. 嵌入C代码中的386汇编语言程序段

  1. 这里的代码都是AT&T 风格的, 有不明白的需要对照 intel 的才能看懂
  2. 在C 中插入汇编比较复杂, 会涉及如何分配寄存器, 以及如何与C 代码中的变量相结合的问题。
    一般的代码片表示形式:
    指令部 : 输出部 : 输入部 : 损坏部

4. memcpy 的底层实现:

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本PDF电子书包含上下两册,共1576页,带目录,高清非扫描版本。 作者: 毛德操 胡希明 丛书名: Linux内核源代码情景分析 出版社:浙江大学出版社 目录 第1章 预备知识 1.1 Linux内核简介. 1.2 Intel X86 CPU系列的寻址方式 1.3 i386的页式内存管理机制 1.4 Linux内核源代码中的C语言代码 1.5 Linux内核源代码中的汇编语言代码 第2章 存储管理 2.1 Linux内存管理的基本框架 2.2 地址映射的全过程 2.3 几个重要的数据结构和函数 2.4 越界访问 2.5 用户堆栈的扩展 2.6 物理页面的使用和周转 2.7 物理页面的分配 2.8 页面的定期换出 2.9 页面的换入 2.10 内核缓冲区的管理 2.11 外部设备存储空间的地址映射 2.12 系统调用brk() 2.13 系统调用mmap() 第3章 中断、异常和系统调用 3.1 X86 CPU对中断的硬件支持 3.2 中断向量表IDT的初始化 3.3 中断请求队列的初始化 3.4 中断的响应和服务 3.5 软中断与Bottom Half 3.6 页面异常的进入和返回 3.7 时钟中断 3.8 系统调用 3.9 系统调用号与跳转表 第4章 进程与进程调度 4.1 进程四要素 4.2 进程三部曲:创建、执行与消亡 4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件系统 5.1 概述 5.2 从路径名到目标节点 5.3 访问权限与文件安全性 5.4 文件系统的安装和拆卸 5.5 文件的打开与关闭 5.6 文件的写与读 5.7 其他文件操作 5.8 特殊文件系统/proc 第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6.5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 第7章基于socket的进程间通信 7.1系统调用socket() 7.2函数sys—socket()——创建插口 7.3函数sys—bind()——指定插口地址 7.4函数sys—listen()——设定server插口 7.5函数sys—accept()——接受连接请求 7.6函数sys—connect()——请求连接 7.7报文的接收与发送 7.8插口的关闭 7.9其他 第8章设备驱动 8.1概述 8.2系统调用mknod() 8.3可安装模块 8.4PCI总线 8.5块设备的驱动 8.6字符设备驱动概述 8.7终端设备与汉字信息处理 8.8控制台的驱动 8.9通用串行外部总线USB 8.10系统调用select()以及异步输入/输出 8.11设备文件系统devfs 第9章多处理器SMP系统结构 9.1概述 9.2SMP结构中的互斥问题 9.3高速缓存与内存的一致性 9.4SMP结构中的中断机制 9.5SMP结构中的进程调度 9.6SMP系统的引导 第10章系统引导和初始化 10.1系统引导过程概述 10.2系统初始化(第一阶段) 10.3系统初始化(第二阶段) 10.4系统初始化(第三阶段) 10.5系统的关闭和重引导
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值