学习Linux内核已经快1个月了,已经看完《Linux内核设计与实现》这本书了,只是粗略的看了一遍,对于碰到的很多问题都没有去深入研究,现在开始看《深入理解Linux内核》,也是该深入的去理解内核了,以后每一章都要写学习笔记,去真正的吃透它。闲话不多说,开始第一章--绪论。
关于Linux的一些基本概念和起源已经在我转载的另一篇博文里面说到了《Linux内核设计与实现》读书笔记(一)-内核简介,今天主要说的是在准备深入研究内核时要了解的关于Linux系统的基本概念。在此之前先要知道操作系统的概念。
任何计算机系统都包含一个名为操作系统的基本程序集合。在这个集合里,最重要的程序就是内核了。当操作系统启动时,内核被装入到RAM中,内核中包含了系统运行所必不可少的很多核心过程。
操作系统必须完成两个主要目标:
1. 与硬件部分交互,为包含在硬件平台上的所有底层可编程部件提供服务。
2. 为运行在计算机系统的应用程序提供执行环境。
Linux系统有如下特性:
多用户系统
多用户系统就是一台能并发和独立的执行分别输入两个或多个用户的若干应用程序的计算机。并发意味着多个应用程序可以同时处于活动状态并竞争各种资源,eg CPU,内存,硬盘等。独立意味着每个应用程序能执行自己的任务。而无需考虑其他程序做什么。
用户和组
Linux系统是分用户和组的,在多用户系统中每一个用户都有自己的私密空间,所有的用户由一个唯一的数字来标记,这个数字就是用户表示符(UID)。为了和其他用户有选择地共享资料,每个用户是一个或多个用户组的一名成员,组由唯一的用户组标示符(UGID)标识。每一个Linux系统都存在一个超级用户,叫root.他可以访问系统的任何资源。几乎无所不能,通常只有系统管理员有此权限。
进程
程序: 程序是一组指令的集合,它是静态的实体,没有执行的含义
进程: 程序执行时的一个实例,或者说是运行着的程序。
多用户系统的意思就是多个进程可以并发的执行。如果是单核处理器,那种并发称之为伪并发,也就是说多个可执行的进程是交替运行的,通过不断的切换进程实现“并发”;而多核处理器确实真正的并发执行程序,有几个核就可以同时的执行几个进程。关于进程的概念以后会有专门的讲解。在这里先是了解下什么是进程。还有,进程在Linux系统中是非常重要的。
下面要说的就是在以后Linux内核的学习中要碰到的不可回避的知识。
进程/内核模式
CPU既可以运行在用户态,也可以运行在内核态。运行在用户态下的程序是不能访问内核的数据结构或程序的。一个程序在执行时,大部分的时间都是在用户态下运行的,只有在执行系统调用或是需要内核提供服务的时候才切换到内核态。
内核本身并不是一个进程,而是进程的管理者。除了用户进程外,Unix系统还包含几个内核线程的特权进程,他们有以下特点:
l 他们以内核态运行在内核地址空间。
l 他们不与用户直接交互,因此不需要终端设备。
l 他们通常在系统启动时创建,然后一直处于活跃状态到系统关闭。
进程实现
为了让内核管理进程,每个进程有一个进程描述符表示,这个描述符包含进程当前状态的信息。
当内核暂停一个进程的执行时,就把相关的寄存器的内容保存在进程描述符中,这些寄存器包括:
l 程序计数器(PC)和栈指针(SP)
l 通用寄存器
l 浮点寄存器
l 包含CPU状态信息的处理器控制寄存器
l 用来跟踪进程对RAM访问的内存管理寄存器
当内核决定恢复一个进程时,将用进程描述符中合适的字段来装载CPU寄存器。因为PC所存的值指向下一条将要执行的指令,所以进程从它停止的地方恢复执行。
可重入内核
若干个进程可以同时在内核状态下执行。
如果一个硬件中断发生,可重入内核能挂起正在执行的进程,即使这个进程处于内核态下。这种机制也就是从内核2.6版本以后提供的内核抢占功能。
同步和临界区
实现可重入内核需要利用同步机制,如果内核控制路径对某个内核数据结构操作时被挂起,那么其他内核控制路径就不能再对该数据结构进行操作。否则,两个控制路径的交互作用将破坏存储的信息。
临界区: 当某一进程进入临界区后必须执行完临界区的所有代码才能被打断运行。这样就避免了不同的进程在“同一时间”操作一个数据的悲剧了。
信号量
信号量是一个与数据结构相关的计数器,所有的内核线程在试图访问这个数据结构之前都要检查信号量,可以把它理解为一个对象,其组成如下:
l 一个整数变量
l 一个等待进程的链表
l 两个原子方法
down()方法对信号量减1,如果新值小于0,则把正在运行的进程加入到这个信号量链表,然后阻塞该进程。
up()方法对信号量值加1,如果该信号量新值大于等于0,则激活这个信号量链表的一个或多个进程。
每个要保护的数据结构都有自己的信号量,初始值为1,当内核控制路径希望访问该数据结构时,他在相应的信号量上执行down()方法,如果信号量不是负数,则运行访问数据结构。负责将其加入到信号量链表中并阻塞进程。
自旋锁
信号量并不总是高效实用的,为此引进了自旋锁。它与信号量非常相似,但没有进程链表;当一个进程发现锁被另一个进程占着的时候,它就不听的旋转,执行一个紧凑的循环指令直到锁打开。自旋锁在单处理器下是无效的。
信号和进程间通讯
信号提供了把系统事件报告给进程的机制。每种事件都有自己的信号编号。POSIX标准定义了大约20种不同的信号,有两种是用户自己定义。进程一般可以以两种方式对接收的信号做出反应。
1. 忽略该信号
2. 异步的执行一个指定的过程
如果进程不指定选择何种方式,内核就根据信号的编号执行一个默认的操作。
l 终止进程
l 将执行上下文和进程地址空间的内容写入一个文件(核心转储),并终止进程
l 忽略信号
l 挂起进程
l 如果进程曾被暂停,则恢复他的执行。
进程管理
fock()和_exit()系统调用分别用来创建和终止一个进程,调用exec()则是装入一个新程序。
调用fock()的进程都是父进程,新进程是它的子进程,每一个描述进程的数据结构里面都有两个指针,一个指向他的父进程,一个指向他的子进程。
实现fock()的普通做法就是将父进程的地址空间都拷贝过去,但是这样会很费时间,所有就产生了“写时复制”技术,即父或子进程有一个需要写一个页时才复制这个页。
僵死进程
当子进程已经终止,而父进程却没有调用wait4()系统调用来回收子进程的地址空间时,称该子进程为僵死进程。产生僵死进程的原因有两个,一个是父进程已经死了,无法再产生系统调用了,这时候就需要内核使用一个名为init的特殊系统进程来改变所有子进程的进程描述符,使其成为init的孩子,
进程组和登录会话
$ ls | sort | more
上边是三个进程,每一个进程描述符包括一个包含进程组ID的字段,每一个进程组可以有一个领头进程。
登录会话就是shell进程为用户创建的第一条命令。进程组的所有进程必须在同一会话中。
内存管理
内存管理是linux内核中最复杂的活动,在这里先把它涉及到的主要部分列出来,在以后的笔记中再做详细说明。
1. 虚拟内存
2. 随机访问存储器(RAM)的使用
3. 内核内存分配器
4. 进程虚拟地址空间处理
5. 高速缓存
设备驱动程序
内核通过设备驱动程序与I/O设备交互。它包含在内核中,由控制一个或多个设备的数据结构和函数组成。这些设备包括硬盘,键盘,鼠标,监视器,网络借口及连接到SCSI总线的设备。通过特定的接口,每个驱动程序与内核中的其余部分相互作用这种方式的优点:
l 特定的设备可以封装到特定的模块中
l 在不了解源码下可以增添设备
l 内核以统一的方式对待所有设备,并通过相同的接口访问
l 可以把设备驱动程序写成模块,动态的将他们装进内核
设备驱动程序与内核其他部分及进程之间的接口