深入理解linux内核

目录

目录

操作系统(os)

必须完成的目标

多用户系统

特点

用户和组

进程

进程和程序:几个进程能并发执行同一个程序,同一个进程能顺序的执行几个程序。

单处理系统

内核体系结构

模块优点

文件系统

文件

硬链接和软连接

限制

软连接(符号链接)

文件类型

文件描述符

索引节点(inode)

访问权限和文件模式

文件操作的系统调用

进程/内核模式

激活内核例程

进程的执行

可重入内核

进程地址空间

同步和临界区

非抢占式内核

关中断

信号量

自旋锁(spin lock)

避免死锁

信号和进程间通信

进程管理

僵死进程

内存管理虚拟内存

RAM(随机访问内存)使用

内核内存分配器(KMA)

特点

进程虚拟地址空间的处理

交换与高速缓存

设备驱动程序

优点

第二章 内存寻址

段寄存器

段描述符

段选择符

段单元

硬件的分页单元

等待队列

进程的使用限制

进程切换

硬件上下文

创建进程

内核线程

第四章  中断和异常

中断

中断信号的作用

中断处理须要满足的约束

异常处理

第五章  定时测量

内核必需的2种主要定时测量:

定时器的作用

静态定时器(内核使用) 

第六章  内存管理

避免外碎片的方法:

伙伴(buddy)系统算法

举例:

称为伙伴的两个块应该满足的条件:

内存区管理

第七章  进程地址空间

第八章 系统调用

第九章 信号

内核实现信号处理:

第十章  进程调度

第十一章  内核同步

cpu交错执行和控制路径的情况:

同步技术:

第十二章  虚拟文件系统vfs

第十三章  管理IO设备

第十四章  磁盘高速缓存



操作系统(os)

计算机系统都包含一个基本的程序集合,称为操作系统.在这个集合里,最重要的程序被称为内核(kernel),当操作系统启动时,内核被装入ARM中,内核中包含了系统运行时必不可少的很多过程。

必须完成的目标

1.与硬件部分相互作用,为所有包含在硬件平台上的低层可编程部件提供服务

2.为运行在计算机系统上的应用程序(用户程序)提供可执行环境

 

当程序想使用硬件资源时,必须向操作系统发出一个请求,内核对整个请求进行评估,如果允许使用这个资源,内核将代表应用程序与相关的硬件部分进行交互。

为了实施这种机制,os依靠特殊的硬件特性来禁止用户程序直接与底层硬件打交道,或者直接访问任意的物理地址。

硬件为cpu引入了至少两种执行模式:用户的非特权模式(用户态)和内核的特权模式(内核态)

多用户系统

一台能并发和独立的执行分别属于两个或多个用户的若干应用程序的计算机。

并发:几个应用程序能同时处于活动状态并竞争各种资源,如CPU,内存,硬盘等。

独立:每个程序能执行自己弄的任务,而无需考虑其他用户的应用程序在干什么。

特点

1.认证机制,核实用户身份

2.保护机制,防止有恶意的用户程序妨碍其他应用程序在系统中运行,防止有恶意的用户程序干涉或窥视其他用户活动。

3.记账机制,限制分配给每个用户的资源数

用户和组

在多用户系统中, 每个用户在机器上有私用空间,如磁盘空间的限额存储文件。操作系统必须保证用户空间的私有部分仅对拥有者可见。

所有用户由一个唯一的数字来标识,用户标识符(user id ,UID)。

为了和其他用户有选择的共享资料,每个用户是一个或者多个组的成员,组内有唯一的组标识符(Group ID, GID),每个文件也恰好与一个组相对应。

任何unix os都有root(超级用户),os对root不会使用常用的保护机制,root能访问系统中每一个文件,能干涉每一个正在执行的用户程序的活动。

进程

程序执行的一个实例,或者一个运行程序的“执行上下文”。os允许具有多个执行流的进程(在相同的地址空间可以执行多个指令序列)。

多用户系统必须实施一种执行环境,在这种环境里,几个进程能并发的活动,并能竞争系统资源。允许经常并发活动的系统被称为多处理技术系统。

进程和程序:几个进程能并发执行同一个程序,同一个进程能顺序的执行几个程序。

 

单处理系统

只有一个进程能占用cpu,在某一时刻,只能有一个执行流,cpu个数有限,只有几个进程能同时执行,选择哪个进程执行时由os的调度程序(scheduler)决定。一些os只允许有非抢占式进程,只有当进程自愿放弃cpu,调度程序(scheduler)才被调用

多处理系统的scheduler必须是抢占式的,os记录每个进程占用cpu时间,周期性的激活调度程序。

内核体系结构

模块是内核的特点。是一个目标文件,代码可以在允许时链接到内核或从内核中取下。

模块优点

模块化方法:可以在运行时链接或者卸下模块,开发者必须引入定义明确的软件接口来访问由模块处理的数据结构,让开发新模块变得容易。

平台无关性:即使模块依赖某些特殊的硬件特点,但不依赖某个固定的硬件平台。

节省内存:当需要模块的功能时,把他链接到正在允许的内核中,否则,卸下该模块(链接和卸下由内核完成)

没有性能损失:模块的目标代码一旦被链入到内核的作用与静态链接的内核的目标代码等价。

文件系统

文件

以一系列自己组成的信息载体,内核不解释文件内容。由一系列字符组成。

硬链接和软连接

包含在一个目录中的文件名就是一个文件的硬链接,简称链接。

ln f1 f2 //为路径f1标识的文件创建一个路径名为f2的硬链接

限制

1.不允许给目录创建硬链接,因为这个可能把目录树变成环形图,从而就不能通过名字来定位一个文件‘

2.只有在同一个文件系统中的文件之间才能创建链接。

软连接(符号链接)

是一个短文件,这个文件包含了另一个文件的任意一个路径名,这个路径名指向位于任意一个文件系统的任意文件,甚至可以指向一个不存在的文件。

// 创建一个名为f2的新的软连接指向路径名f1,当这个命令指执行时,文件系统创建一个新的软连接,并把路径名f1写入这个链接,然后插入一个新的目录项,该目录包含路径f2中最后一个名字。以这种方式,任何对f2的引用都会被自动转换称指向f1的一个引用
ln -s f1 f2 

文件类型

正规文件(regular file)

目录(directory)

符号链(symbolic link)

块设备文件(block-orinted device file)

字符设备文件(character-oriented device file)

管道(pipe)和命名管道(named pipe, fifo)

套接字(socket)

文件描述符和索引节点

文件描述符

表示了进程与打开文件之间的相互作用。

索引节点(inode)

文件系统用来管理文件的数据结构,每个文件都有自己的索引节点,文件系统用文件来识别文件。

访问权限和文件模式

文件的潜在用户:文件所有者,同组(group)但不包括所有者,所有剩下的用户

访问权限:读,写,执行

当进程创建一个文件时,文件拥有着的id就是该进程的uid,组id既可以是创建者的gid,也可以是父目录的gid。

文件操作的系统调用

当用户访问一个正规文件或者目录文件的内容时,实际上访问的是存储在硬件块设备上的一些数据。从这个意义上说,文件系统是用户级的观点来看硬盘分区的物理组织,因为处于用户态的进程不能直接与底层硬件打交道,所以每个文件的操作必须要在内核下进行。

 

进程/内核模式

当应用程序在用户态下执行时,不能直接访问内核数据结或内核程序。当程序在内核态下执行不再有限制。

进程是动态的实体,在系统内通常只有有限的生存期,创建、车型以及同步已存在进程的任务都委托给内核中的一个例程完成。

内核本身不是进程,而是进程的管理者,进程/内核模式假定:需要内核服务的进程使用系统调用(system call)机制,每个系统调用都设置了一组参数来识别进程的要求,然后执行与硬件相关的cpu指令完成从用户态到内核态的转换。

激活内核例程

1.进程调用一个系统调用

2.正在执行的cpu发出异常信号

3.外围设备向cpu发出一个中断信号以通知一个事件的发生。每个中断信号都是由中断处理程序来处理。

4.内核线程被执行

进程的执行

为了内核管理进程,每个进程由一个进程描述符表示。

当内核暂停一个进程的执行时,在进程描述符中保存处理器寄存器内容:

1.程序计数器pc和栈指针sp寄存器

2.通用寄存器

3.浮点寄存器

4.包含cpu状态信息的处理器控制寄存器

5.跟踪进程对RAM访问的内存管理寄存器

当内核恢复进程时,用进程描述符中合适的域装载cpu的寄存器。因为程序计数器中所存的值指向下一条要执行的指令,进程从他停止的地方恢复执行。

可重入内核

几个进程可同时在内核态下执行。

进程地址空间

同步和临界区

非抢占式内核

关中断

信号量

是与数据结构相关的计数器。内核线程访问这个数据结构前都要检查这个信号量。该数据结构组成:

整数变量,等待进程的链表,原子方法(down(), up())

down()对信号量的值减1,如果这个值小于0,就把正在允许的进程加入到信号量链表,然后阻塞该进程(调用scheduler)。up()对信号量的值加1,如果这个值大于等于0,激活这个信号量的链表中的一个或者多个进程。

每一个要保护的数据结构都有自己的信号量,初始值为1 ,当内核控制路径希望访问这个数据结构时,在适当的信号量上执行down()方法,如果新信号量的值不是负数,则允许访问这个数据结构,否则,则把控制路径的进程加入到这个信号量的链表并阻塞他,当另一个进程在那个信号量上执行up()时,允许信号量链表上的进程之一继续执行。

自旋锁(spin lock)

信号量并不是解决同步问题的最佳方案。没有进程链表,当一个进程发现锁被另一个进程锁着时,他就不停旋转,不断执行直到锁打开。

自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断是否能够被成功获取,知直到获取到锁才会退出循环。
 获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting
 它是为实现保护共享资源而提出的一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能由一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋”一词就是因此而得名。

https://studygolang.com/articles/16480

避免死锁

1.引入有限的信号量类型

2.以递增的顺序请求信号量

信号和进程间通信

通过信号把系统事件报告给进程。每种事件都有自己的信号编号。

两种事件:异步通告,同步错误或异常

进程可以以以下2种方式对信号的接收做出反应:

1。忽略该信号

2.异步的执行一个指定的过程(信号处理程序)

如果进程不指定选择以上的两种方式,内核会根据信号的编号执行缺省操作:

1.终止进程

2.将执行上下文和进程地址空间的内容下入一个文件,并终止进程

3.忽略信号

4.挂起进程

5.如果进程曾被暂停,则恢复执行

进程管理

fork()创建新的进程。调用fork()的是父进程,而新进程是他的子进程,父进程能相互找到对方,因为描述每个进程的数据结构都包含两个指针,一个直接指向他的父进程,另一个直接指向子进程。

exit()终止进程

exec()加载新程序

僵死进程

父进程查询子进程的终止:wait()进程等待,直到其子进程之一结束,返回被终止的子进程的进程标识符(PID)

 

内存管理虚拟内存

1.几个进程可以并发执行

2.应用程序所需要内存大于可用物理内存时也可以运行

3.进程可以执行只有部分代码装入大盘内存的程序

4.允许每个进程访问可以物理内存的一个子集。

5.进程可以共享库函数或程序的单一的内存映像。

RAM(随机访问内存)使用

一部分存储内核映像(内核代码和内核静态数据结构),其余部分由虚拟存储器系统来处理,通过以下3种可能的方式来使用:

1.满足内核对缓存、描述符以及其他动态内核数据结构的请求

2.满足进程对一般内存区域的请求及对文件的内存映射的请求

3.用高速缓存的方法从磁盘及其他缓冲设备获得较好的性能

每种请求类型都是有价值的,但从另一方面说,ram是有限的,必须在请求类型间做出平衡,尤其当可用内存剩下不多时。当可用内存达到极限时,可用调用页框回收算法释放额外的内存。

虚拟内存必须解决的主题:内存碎片。理想情况下,只有当空闲页框数太少时,内存请求才会失败。然而,内核通常被迫使用物理上连续的内存区域,因此即使有足够的可用内存,但不能作为一个连续的大块使用时,内存的请求也会失败。

内核内存分配器(KMA)

是一个子系统,试图满足系统中所有部分对内存的请求。其他一些请求来自内核其他子系统,他们需要一些内存供内核使用,还有一些请求来自用户程序的系统调用,用来增加用户进程的地址空间。

特点

1.必须快。最重要的属性,由所有的内核子系统调用(包括中断处理程序)

2.必须把内存的浪费减到最少

3.必须努力减轻内存的碎片问题

4.必须能与其他内存管理子系统合作,以便借用和释放页框

进程虚拟地址空间的处理

一个进程的虚拟地址空间包括所有进程被允许引用的虚拟内存地址。内核通常把进程虚拟地址空间当作内存区域描述符的链表保存

交换与高速缓存

为扩充进程所用的虚拟地址空间大小。虚拟内存系统以一个页框的内容作为交换的基本单位。

 

设备驱动程序

内核通过设备驱动程序与I/O设备打交道。设备驱动程序包含在内核中,由控制一个或多个设备的数据结构和函数组成。这些设备包括硬盘、键盘、鼠标、监视器、网络接口以及链接到SCSI总线上的设备。通过特点的接口,每个驱动程序与内核中的其余部分相互作用。

优点

1.把特点设备的代码封装在特定的模块中

2.厂商可用不懂内核源代码,只知道接口规范,并且通过相同的接口访问这些设备

3.可用把设备驱动协程模块,并动态的把他们装进内核,不需要重新启动系统。不再需要时,可用卸下模块,以减少存储在ram中内核映像的大小。

 

第二章 内存寻址

内存地址

段寄存器

逻辑地址由一个段标识符和一个指向段内相对地址的偏移量。

cs:代码段寄存器,指向存放程序指令的段。含有一个两位的域,用来指明cpu当前的权级。0代表最高优先级,3代表最低优先级。linux只用0级(内核态)和3级(用户态)。

ss:栈段寄存器,指向存放当前程序栈的段

ds:数据段寄存器,指向存放静态数据或者外部数据的段

段描述符

段选择符

段单元

硬件的分页单元

把先行地址转为物理地址。把请求的存取类型和线性地址的存取权限相比,如果存取无效,则产生错误页面。

线性地址被分成以固定长度为单位的组,称为页。页内连续的线性地址被映射到连续的物理地址中。

 

等待队列

运行链表把处于task_running状态的所有进程组织在一起,当要把其他状态的进程分组,不同状态处理不同:

1.task_stopped或者task_zombie状态进程不链接在专门的链表中,也没必要分组,因为父进程可用通过进程的pid或进程间的亲子关系检索到子进程

2.把task_interruptible或者task_uninterruptible状态的进程再分成很多类,每一类对应一个特定事件。在这种情况下,进程状态提供的信息满足不了快速检索进程,因此有必要引入另外的进程链表,这些附加的链表叫等待队列

等待队列在内核中有很多用途,尤其对中断处理、进程同步、定时用处更大。表示一组睡眠的进程,当某一条件为真,由内核唤醒。

等待队列由循环链表实现,元素包括指向进程描述符的指针。

等待队列

struct wait_queue {
    // 
    struct task_struct * task;
    // 等待队列指针
    struct wait_queue * next;
}

进程的使用限制

RLIMIT_CPU:进程使用cpu的最长事件,如果进程超过了这个限制,内核就向他发一个sigxcpu信号,如果进程还不终止,再发一个sigkill信号。

RLIMIT_FSIZE:允许文件大小的最大值。如果进程试图把一个文件的大小扩充到大于这个值,内核就给这个进程发送sigxfsz信号

RLIMIT_STACK:栈大小的最大值。在扩充进程的用户态堆栈之前,内核检查这个值

RLIMIT_CORE:内存信息转出文件的大小。当一个进程异常终止时,内核要在进程的当前目录下创建一个内存信息转储文件,在这个文件创建之前,内核检查这个值。如果这个限制为0,那么内核就不创建这个文件。

RLIMIT_RSS:进程所拥有的页框的最大数。实际上,内核从来不检查这个值,没有实现这个使用限制。

RLIMIT_NPROC:用户能拥有的进程最大数

RLIMIT_NOFILE:打开文件的最大数。当打开一个新文件或复制一个文件描述符时,内核检查这个值。

RLIMIT_MEMLOCK:非交换内存的最大尺寸。当进程试图通过mlock()或者mlockall()系统调用锁住一个页框时,内核检查这个值。

RLIMIT_AS:进程地址空间的最大尺寸。当进程调用malloc()或相关函数扩大它的地址空间时,内核检查这个值。

进程切换

为了控制进程的执行,内核必须有能力挂起正在CPU上允许的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换、任务切换或者上下文切换。

进程切换主要内容:

1.硬件上下文

2.硬件支持

3.linux代码

4.保存浮点寄存器

硬件上下文

进程恢复执行前必须转入寄存器的一组数据。

每个进程可以拥有属于自己的地址空间,但所有进程必须共享cpu寄存器。因此,恢复一个进程的执行前,内核必须确保每个寄存器装入了挂起进程时的值。硬件上下文是进程可执行上下文的一个子集,可执行上下文包含了进程执行需要的所有信息。

进程切换只发生在内核态。在进程切换之前,用户态下的进程使用的所有寄存器的内容都已经被保存,也包括指定用户态对战指针地址的ss和esp寄存器内容。

创建进程

传统方式:父进程所拥有的资源被复制,被复制的那份用来运行子进程。低效,因为要拷贝父进程的整个地址空间。

改进方式(3种):

1.写时复制技术允许父子进程能读相同的物理页,只要两者中有一个进程试图写一个物理页,内核就把这个页的内容拷贝到一个新的物理页,并把这个新的物理页分配给正在写的进程。

2.轻量级进程允许父子进程共享每一进程在内核的数据结构,如页表及打开文件表。

3.vfork()系统调用创建的一个进程能共享其父进程的内存地址空间,为了防止父进程重写子进程需要的数据,阻塞父进程的执行,一直到子进程的退出或者执行一个新的程序。

内核线程

内核线程与普通进程的区别:

1.每个内核线程执行一个单独指定的内核函数,而普通进程只有通过系统调用执行内核函数

2.内核线程之运行在内核态,而普通进程既可运行在内核态,也可运行在用户态。

3.地址空间。内核线程使用大于page_offset的线性地址空间。普通进程不管内核态还是用户态用4GB的线性地址空间。

 

进程可以创建一个子进程执行特定的任务,然后调用wait()类系统调用检查子进程是否终止,如果子进程已经终止,他的终止代号将告诉父进程这个任务是否已经成功进行。内核在进程终止后丢弃包含在进程描述符域中的数据。只有当父进程发布了一个与已终止进程相关的wait()之后才允许这样。(这就是为什么僵死状态的进程的描述符必须保存,直到父进程得到通知)

如果父进程在子进程之前结束:已僵死的进程可能用可用的task项塞满了系统。必须强迫所有的孤儿进程成为init进程的子进程。init进行在调用wait()检查合法性的子进程终止时,就可以撤销僵死的进程。(调用release()释放僵死进程)

 

第四章  中断和异常

中断

改变处理器执行指令顺序的事件。

同步中断:当指令执行时由cpu控制单元产生,只有在一条指令终止执行后cpu才会发出中断

异步中断:由其他硬件设备依照cpu时钟信号随机产生的。

中断信号的作用

提供了一种方式,让处理器转而去运行正常控制流之外的代码。当一个中断信号达到时,cpu必须停下他当前正在做的事情,并且切换到一个新的活动。为了做到这样,需要在内核堆栈保存程序计数器的当前值,并把与中断类型相关的一个地址放进程序计数器。

中断处理须要满足的约束

1.当内核正打算去做一些其他事情时,中断随时会到来。因此内核的目标就是让中断尽可能快的被处理,尽其所能把其他更多的处理向后推迟。

2.中断随时到来,内核可能正在处理其中一个中断时,另一个中断又发生。应尽可能多的允许这种情发生,因为能维持更多的IO设备处于忙碌状态。

3.尽管内核在处理前一个中断时可用接受一个新的中断,但在内核代码中还是存在一些临界区,在那里中断必须关闭,尽可能的限制这种临界区。

异常处理

目标:1.向进程发一个信号以同胞一个反常情况;2.处理请求分页

异常处理程序的标准结构:

1.在内核堆栈中保存大多数寄存器的内容

2.用高级C函数处理异常

3.通过ret_from_exception()函数从高级语言退出

 

第五章  定时测量

内核必需的2种主要定时测量:

1.持续记录当前的时间和日期以便能通过time(), ftime()和gettimeofday()系统调用返回给用户程序。

2.维持定时器,这种机制能够提醒内核或者用户程序某一时间间隔已经过去了

 

内核必须显示的与实时时钟、时间标记计数器、可编程间隔定时器

cpu分时(time-sharing)

定时中断对于可运行(即处于task_running)的进程之间共享cpu的时间必不可少。通常给进程分配一个时间片,如果时间片到时进程还没终止,那么schedule()函数选择一个新的进程投入运行。

定时器的作用

定时器是一种软件工具,允许在将来的某个时刻,当给定的时间间隔用完时调用函数,超时表示一个时刻,到这一时刻,与定时器相关的时间间隔已经用完。内核和进程广泛使用定时器。大多数设备驱动程序利用定时器检测反常情况。

静态定时器(内核使用) 

为了激活静态定时器,内核必须简单的:

1.登记定时器的fn域要执行的函数

2.计算到期时间,并把这个到期时间存在定时器的expires域

3.在timer_active种设置合适的标志

动态定时器(内核使用)

被动态的创建和撤销,对当前活动动态定时器的个数没有限制

间隔定时器(进程在用户态创建)

第六章  内存管理

内核应该为分配一组连续的页框而建立一种问的的、高效的分配策略。必须解决内存管理的外碎片问题。

避免外碎片的方法:

1.利用分页单元把一组非连续的空闲页框映射到连续的线性地址区间

2.开封一种适当的技术来记录限制的空闲连续页框块的情况,以尽量避免为满足小块的请求而把大块的空间块进行分割。

伙伴(buddy)系统算法

以页框作为基本内存区,只适合大块内存的请求。

把所有空闲页框分组为10块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256和512个连续的页框。

举例:

假设要请求一个128页框的块,该算法先在128个页框的链表中检测是否有一个空闲块,如果没有这样的块,该算法会查找下一个更大的页块,也就是在256个页框的链表中找一个空闲块。如果存在这样的块,内核就把256的页框分成两等份,一半用来满足请求,另一半插入到128个页框的链表中。如果在256个页框的链表中也没有找到空闲块,就继续找更大的块512个页框的块。如果这样的块存在,内核把512个页框的块的128个页框做请求,然后从剩余的384个页框中拿256插入到256个页框的链表,再把最后的128个插入到28个页框的链表。如果512个页框的链表还是空的,该算法就放弃并发出错误信号。

称为伙伴的两个块应该满足的条件:

1.两个块具有相同的大小,记作b

2.物理地址连续

3.第一块的第一个页框的物理地址是2 * b * 2的12次幂的倍数。

内存区管理

内存区:具有连续的物理地址和任意长度的内存单元

内碎片问题:请求内存的大小与分配给它的大小不匹配造成。

解决:按照几何分布的内存区大小,即内存区大小与2的次幂有关而与所存放的数据大小无关。这样不管请求内存大小是多少,都可以保证内碎片小于50%.

 

第七章  进程地址空间

允许进程使用的全部线性地址组成。每个进程所看大的线性地址集合不同。内核通过增加或删除某些线性地址区间而动态的修改进程的地址空间。

内核通过线性区的资源来表示线性地址区间,线性区与起始线性地址、长度和一些存取权限来描述。

1.当用户在控制台输入命令,shell进程创建一个新的进程区执行这个命令,结果是,一个全新的地址空间,也就是一组线性区分配给了这个新的进程。

2.一个正在允许的进程有可能决定装入一个完全不同的程序,此时进程标识符仍保持不变,在装入这个程序以前所使用的线性区被释放,并有一组新的线性区被分配给这个进程。

3.一个正在允许的进程可能对一个文件执行“内存映射”,在这种情况下,内核给这个进程分配的一个新的线性区来映射这个文件。

4.进程可能持续向它的用户堆栈增加数据,直到映射这个堆栈的线性区用完为止,在这种情况下,内核也许决定扩展这个线性区的大小。

5.进程可能创建一个IPC共享线性区来与其他合作进程共享数据,此时,内核给这个进程分配一个新的线性区来实现这个方案

6.进程可能通过调用类似malloc()来扩展自己的动态区。结果是内核可能决定扩展给这个堆所分配的线性区。

分配线性地址空间:

释放线性地址空间:

扫描进程所拥有的线性区链表,并删除与指定线性地址区间相重叠的所有区;更新进程的页表,并重新调整线性区链表。

 

第八章 系统调用

在内核打算满足用户请求之前,必须仔细检查所有系统调用参数。检查类型既依赖系统调用,页依赖特定参数。

如一个参数指定的是地址,那么内核必须检查它释放在这个进程的地址空间之内。

1.验证这个线性地址释放属于进程的地址空间,如果是,这个线性地址所在的线性区就具有正确访问权限。

2.仅仅验证这个线性地址小于page_offset(linux内核用的这个方式)

 

第九章 信号

 深入理解linux内核_第1张图片

深入理解linux内核_第2张图片

深入理解linux内核_第3张图片

 深入理解linux内核_第4张图片

 

内核区分与信号传递的两个不同阶段:

1.信号送达:内核更新进程的描述符以表示一个新的信号已被送达

2. 信号的接收:内核强迫目标进程对这个信号做出反应,通过改变它的执行状态或开始执行一个特定的信号处理程序,或两者都是。

每个被发送的信号只能被接收一次,信号是可消费资源,一旦被接受,进程描述符中有关这个信号的所有信息被取消。

挂起信号:已发送但还没被接收的信号被称为挂起信号,任何时候一个进程仅存在给定类型的一个挂起信号,统一进程同样类型的其他信号不被排队,只被简单丢弃。

给挂起信号保留不可预知的时间要考虑以下因素:

1.信号通常制备当前正在运行的进程接收

2.给定类的信号可以由进程选择性的阻塞,此时,在取消这个阻塞前进程将不接收这个信号

3.当进程执行一个信号处理函数时,通常“屏蔽”相应的信号,即自动阻塞这个信号直到这个处理程序结束。因此已处理的信号另一次出现不能中断信号处理程序。

内核实现信号处理:

1.记住每个进程阻塞哪些信号

2.当从内核状态切换到用户态时,对任何一个进程都要检查释放由信号已经到达,这几乎在每个定时中断时都发生。

3.确定是否可以忽略该信号,以下条件须同时满足:

 3.1目标进程没有被另一个进程跟踪

3.2这个信号没有被日志进程阻塞

3.3这个信号被目标进程忽略

4.处理信号,这个信号可能在进程运行器间的任一时刻请求把进程切换到一个信号处理函数,并在这个函数返回以后恢复原来执行的上下文。

第十章  进程调度

schedule():在运行队列链表中找到一个进程,然后把cpu分配给它。

1.直接调用:current进程所需的资源无法得到满足而必须被立即阻塞时,直接调度调用程序:

1.1把current插入到合适的等待队列

1.2把current当前状态改变为task_interruptible或task_uninterruptible

1.3调用schedule()

1.4检查那个资源是否可用,如果不可用,直接转1.2

1.5一旦那个资源是可以用的,把current从等待队列中删除

2.松散调用,通过把current的head_resched域设置为1,以松散方式调用调度程序。

松散调用在下列情况被执行:

2.1 当current用完它的cpu时间片,通过update_process_times()进行。

2.2 当一个进程被唤醒,并且它的优先级高于current进程的优先级

2.3当发出一个sched_SetScheduler()或者sched_yield()系统调用时

 

第十一章  内核同步

可用把内核看作是一个不断对请求进行响应的服务器,这些请求可能来自正在cpu上执行的进程,也可能来自正在执行中断请求的外部设备。(内核并不是严格按照顺序执行的,而是采用交错执行的方式)

cpu交错执行和控制路径的情况:

1.发生上下文切换

2.中断发生在当cpu正在执行开中断的内核控制路径时。在这种情况下,第一个内核控制路径还没有完成,cpu就开始执行另一个内核控制路径来处理这个中断

同步技术:

1.内核态进程的非抢占式

linux内核是非抢占式的,当正在运行的进程还处于内核态时,不会被抢占。

对于系统调用所启用的其他控制路径来说,处理非阻塞的系统调用的内核控制路径是原子的,简化了很多内核函数的实现:不是由中断或者异常处理程序更新的内核数据结构都可以很安全的被访问。然而,如果处于内核态的进程主动放弃cpu,那么它必须保证剩下的所有数据结构处于一致状态。此外,进程恢复执行时,必须对以前访问过的可能被改变的所有数据结构进行重新检测。

2.原子操作

是避免竞争条件最简单的方法。操作必须在单独一条指令内执行。

3.关中断

确保内核语句序列可以作为一个临界区进行操作的一种主要机制。即使在硬件设备发出IRQ(中断请求)信号,观众段页允许内核控制路径继续执行来保护中断处理程序也会访问数据结构。

4.加锁

linux提供的两种锁:内核信号量,自旋锁

当内核控制路径试图获得由内核信号量所保护的一个繁忙的资源时,相应的进程被挂起,直到该资源被释放时这个进程才变为可运行的。

 

第十二章  虚拟文件系统vfs

文件对象

描述进程怎样与一个打开文件交互的过程。文件对象是在文件被打开时创建的,由一个file结构组成。存放在文件对象中的主要信息对象是文件指针,即文件中当前操作的位置。

第十三章  管理IO设备

总线:数据总线,地址总线,控制总线

当总线连接的是cpu和io设备时,称为IO总线。

监控IO操作:

1.轮询模式

cpu重复检查设备的状态寄存器,直到寄存器的值表明IO操作已经完成

2.中断模式

如果IO控制器能够通过IRO线发出IO操作结束信号,中断方式才被利用

访问IOG共享内存

内核驱动程序把IO共享内存单元的物理地址转换称内核空间的线性地址。(内核程序作用在线性地址上)

 

第十四章  磁盘高速缓存

 

 

 

 

 

 

 

 

你可能感兴趣的:(编程思想,运维)