Presentation is loading. Please wait.

Presentation is loading. Please wait.

第六章 Linux内核.

Similar presentations


Presentation on theme: "第六章 Linux内核."— Presentation transcript:

1 第六章 Linux内核

2 6.1 通用内核职责 总体来说,内核负责抽象与管理一台机器的硬件资源以及这些资源在执行程序之间的共享。内核必须支持进程、文件和其他资源,以使它们能够通过传统UNIX系统调用来进行管理。

3 资源抽象 资源抽象:是指创造软件(通常情况)来简化必须应用于硬件的操作以使该硬件能恰当地工作。例如,一个设备驱动程序就是一个软件资源抽象。 计算机部件被抽象为进程和资源。 进程管理:操作系统中的所有执行进程抽象的所有方面称为进程管理。 资源管理:是创建资源抽象以及在进程执行时为它们分配和回收系统资源的过程。 UNIX试图将除CPU和可执行内存之外的每一种资源都视为一个文件。

4 共享资源 进程可以请求、使用并且释放资源。当一个进程请求资源时,它通常需要对该资源的独占使用。对一个资源的独占作用意味着当一个资源被分配给一个进程时,没有其他进程能够访问这个资源。 资源管理有两个关键的方面:对获得资源的竞争和对独占使用的确保。

5 资源管理程序示意图 请求K个单元 进程P 已分配 资源管理程序 获得K个单元 可用 释放K个单元 资源X 分配K个单元
图6-1 资源管理程序示意图

6 资源的独占使用 进程对CPU的独占使用--通过确保其他进程无法打断一个进程的执行(除非那些其他进程比当前运行的进程更重要)
内存的独占使用--是通过硬件内存保护机制来保证的。这些机制禁止CPU访问那些没有分配给当前使用CPU的进程的内存。 设备的独占使用是通过以下方式来完成的:禁止CPU对一个设备执行I/O指令,除非是为已分配该设备的进程而执行。

7 管态与用户态 管态(supervisor mode) :内核执行时CPU处于管态,也称为内核态(kernel mode)
用户态(user mode) :所有其他操作系统部件执行时CPU处于用户态 通过CPU模式位区别 当CPU处于管态时,被认为正在执行信任软件,硬件将会执行在其指令表中的任何指令并且可以访问任何内存地址。 当CPU处于用户态时 ,它被认为在执行非信任软件,硬件将无法执行特权指令(privileged instruction)(例如I/O指令),并且只能访问对当前使用CPU的进程所分配的内存。

8 操作系统的功能划分 进程与资源管理 存储管理 设备管理 文件管理

9 6.2 内核的组织结构 单内核结构 一个模块是一个独立的软件单元 设备驱动程序接口 设备驱动程序 Linux内核 模块 接口 模块
6.2 内核的组织结构 单内核结构 一个模块是一个独立的软件单元 设备驱动程序 设备驱动程序接口 Linux内核 模块 接口 模块 图6-2 内核、设备驱动程序和模块

10 中断 中断是一个产生自外部部件(例如,设备)并由CPU硬件捕获的电子信号,它使CPU开始执行一个程序序列,而该程序序列与中断发生时CPU正在执行的程序无关。

11 使用内核服务 用户程序将内核看作一个大的抽象数据类型(ADT)(类似于一个对象),它保持状态并在其公共接口——系统调用接口上具有大量函数。 内核软件不具有任何内部执行线程或进程,它只是一组保持状态的函数和数据结构的集合。任何使用内核服务的进程——这种进程是一个活动实体——通过(在逻辑上)使用一个对POSIX的过程调用来产生内核请求。 即一个在内核之外执行的进程当它产生系统调用时开始执行内核代码。

12 内核作为ADT 用户空间程序调用系统 公共POSIX..1系统调用接口 内 核 私有实现 图6-4 内核作为ADT

13 管态与用户态之间的切换 陷阱指令(trap instruction)是一条用于将CPU转移到一个预定地址(有时作为一个指令操作数的函数)并将其自身切换为管态的指令。 陷阱指令并不是一条特权指令,因此任何程序都可以执行一条陷阱指令。 转移指令的目的地址是由一组地址预先决定的,它们存放在配置为指向内核代码的管理空间中。

14 陷阱指令操作 模式 1 S 转移表 陷阱 2 3 信任代码 用户态 管态 图6-5 陷阱指令操作

15 完成一个系统调用所需执行操作 1. 对于系统调用F,stub(代码存根)过程用于调用F(该stub也称为F)。
4. stub过程确认传递到内核过程的参数值。另外,在原则上它可以验证调用stub过程的进程。 5. stub过程执行一条陷阱指令转换CPU到管态,然后它(通过一张包含内核函数入口点的内核表间接)转移到目的内核函数的入口点。

16 串行执行 通常内核函数执行时处于临界区。也就是说,一旦进程调用一个系统函数,该函数通常要运行到结束并在CPU分配给不同的进程之前返回。这种类型的内核是单线程(single-threaded)的。 IRQ可以中断系统调用的执行来运行ISR。

17 守护进程 习惯上,守护进程执行名字以字符“d”结尾的程序。 典型守护进程 syslogd、klogd、crond

18 认识Linux内核

19 内核源程序目录结构

20 内核源程序目录结构 内核源程序代码安装在/usr/src/linux目录下 Documentation:文档 arch:体系结构相关的代码
Drivers:外围设备的软件驱动程序 Fs:Linux支持的所有文件系统 include:这个目录包含了Linux源程序树中大部分的C语言包含(.h)文件。 init:这个目录下面的两个文件中比较重要的一个是main.c,它包含了大部分协调内核初始化的代码 ipc:这个目录包含核心的进程间通讯的代码 Modules :只是一个用来存放建立好的模块的目录

21 内核源程序目录结构(续) Kernel:Linux中最重要的部分,实现平台独立的基本功能。这部分内容包括进程调度(kernel/sched.c)以及创建和撤销进程的代码(kernel/fork.c和kernel/exit.c) Lib:目录包含两部分的内容。lib/inflate.c中的函数能够在系统启动时展开经过压缩的内核(请参看第4章)。lib目录下剩余的其它文件实现一个标准C库的有用子集 mm:该目录包含了体系结构无关的内存管理代码 net:这个目录包含了Linux应用的网络协议代码 scripts:该目录包含了用来配置内核的脚本

22 6.3 进程与资源管理

23 进程抽象 地址空间 虚拟CPU 应用程序 CPU … 地址空间 虚拟CPU 进程管理程序 定时器、中断、… 保护 机制 IPC 调度 程序
内核数据结构 硬件 主存 CPU 图6-9 进程抽象

24 不同抽象级别的概要 硬件层。硬件从PC指定的内存地址中取出一条指令并执行它,然后再取下一条指令执行。只有硬件进程执行存储指令的概念。
进程管理程序层。进程管理程序创建一组理想化的虚拟机器(virtual machine)。进程管理程序通过使用定时器、中断、各种保护机制、进程间通信(IPC)、同步机制、调度计划以及一组数据结构来使用硬件层创建Linux进程。应用程序与进程管理程序(通过使用系统调用接口)进行交互。 应用程序层。应用程序层使用传统的Linux进程。

25 1、Linux进程

26 进程基本状态 基本状态间的转换: 新状态 终止状态 接收 退出 进程调度 就绪状态 执行状态 中断 I/O或事件发生 等待I/O或事件
阻塞状态 新状态 终止状态 接收 进程调度 退出 中断 等待I/O或事件 I/O或事件发生

27 Linux 进程概况 每个进程分为内核态(特权级0)和用户态(特权级3)两种级别。
2.4.0版本中,每个task_struct 结构占1680字节。 系统中的最大进程数由系统的物理内存大小决定。

28 Linux 进程状态 Linux进程有以下状态:       Running            进程处于运行(它是系统的当前进程)或者准备运行状态(它在等待系统将CPU分配给它)。       Waiting            进程在等待一个事件或者资源。Linux将等待进程分成两类;可中断与不可中断。可中断等待进程可以被信号中断;不可中断等待进程直接在硬件条件等待,并且任何情况下都不可中断。       Stopped            进程被停止,通常是通过接收一个信号。正在被调试的进程可能处于停止状态。       Zombie            这是由于某些原因被终止的进程,但是在task数据中仍然保留 task_struct结构。它象一个已经死亡的进程。 

29 Linux进程状态变迁图 停止态 僵死态 正在运行态 不可中断等待态 可中断等待态 就绪态 进程跟踪停止命令 进程终止 被 唤 醒
未申请到所需资源 未申请到所需资源 不可中断等待态 可中断等待态 所需资源被满足 所需资源被满足 就绪态

30 进程的结构 Linux下一个进程在内存里有三部分的数据,就是“代码段”、“堆栈段”和“数据段”。
“代码段”,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。 “堆栈段”存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。 数据段存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。 系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。

31 进程控制块 /usr/src/linux-2.4/include/linux/sched.h struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ unsigned long flags; /* per process flags, defined below */ int sigpending; mm_segment_t addr_limit; /* thread address space: 0-0xBFFFFFFF for user-thead xFFFFFFFF for kernel-thread */ struct exec_domain *exec_domain; volatile long need_resched; unsigned long ptrace; int lock_depth; /* Lock depth */ long counter; long nice; unsigned long policy; struct mm_struct *mm; int has_cpu, processor; unsigned long cpus_allowed;

32 进程ID(PIDs) 每个Unix进程的唯一标志符,范围在0到32,767之间的整数。PID 0和PID 1对于系统有特定的意义;其它的进程标识符都被认为是普通进程。 在Linux中,PID不一定非要唯一——虽然通常都是唯一的,但是两个任务也可以共享一个PID。这是Linux对线程支持的一个副作用,这些线程从概念上讲应该共享一个PID,因为它们是同一个进程的一部分。在Linux中,你可以创建两个任务,并且共享且仅共享它们的PID——从实际使用角度讲它们不会是线程,但是它们可以使用同一个PID。

33 state 进程的当前状态 TASK_RUNNING TASK_INTERRUPTIBLE TASK_UNINTERRUPTIBLE
TASK_ZOMBIE TASK_STOPPED TASK_SWAPPING

34 flags 进程标志 PF_ALIGNWARN 正在打印"对齐"警告信息 PF_STARTING 正在创建进程
PF_EXITING 进程正在退出 PF_FORKNOEXEC 进程刚创建,但还没执行 PF_SUPERPRIV 使用超级用户特权 PF_DUMPCORE dumped core PF_SIGNALED 进程被信号(Signal)终止 PF_MEMALLOC 正在分配内存 PF_VFORK 对于用vfork创建的进程,退出前正在唤醒父进程 PF_USEDFPU 该进程使用FPU(SMP only)

35 ptrace PF_DTRACE delayed trace (used on m68k) PF_TRACESYS 正在跟踪
PF_PTRACED 被ptrace系统调用监控

36 priority 进程优先级,priority的值给出进程每次获取CPU后,可使用的时间(按jiffies计)。优先级可通过系统调用sys_setpriority()改变(kernel/sys.c)

37 rt_priority rt_priority给出实时进程的优先级,rt_priority+1000给出进程每次获取CPU后,可使用的时间(同样按jiffies计)。实时进程的优先级可通过系统调用sys_sched_setscheduler()改变,不过实际的工作是由setscheduler()完成的,这两个函数均定义在kernel/sched.c中。

38 counter 在轮转法(round robin)调度时表示进程当前还可运行多久。在进程开始运行时被赋为priority的值,以后每隔一个tick(时钟中断)递减1,减到0时引起新一轮调度。重新调度将从run-queue队列选出counter值最大的就绪进程获得CPU,因此counter起到了进程的动态优先级的作用(priority则是静态优先级)。

39 policy 该进程的进程调度策略,可以通过系统调用sys_sched_setscheduler()更改(kernel/sched.c)。调度策略有: SCHED_OTHER 非实时进程,基于优先权的轮转法(round robin) SCHED_FIFO 实时进程,用先进先出算法 SCHED_RR 实时进程,用基于优先权的轮转法

40 进程队列指针 struct task_struct *next_task, *prev_task;
所有进程(以PCB的形式)组成一个双向链表。next_task和prev_task就是链表的前后向指针。链表的头和尾都是init_task(即0号进程)。通过宏for_each_task 可以很方便的搜索所有进程: include/linux/sched.h #define for_each_task(p) \ for (p = &init_task ; (p = p->next_task) != &init_task ; )

41 进程队列指针 struct task_struct *p_opptr, *p_pptr;
struct task_struct *p_cptr, *p_ysptr, *p_osptr; 以上分别是指向原始父进程(original parent)、父进程(parent)、子进程(youngest child)及新老兄弟进程(younger sibling, older sibling)的指针。相关的操作宏参见kernel/linux/sched.h。

42 父子进程间关系

43

44 2、进程管理的相关命令

45 ps 命令 命令格式:ps [options] [pids] 常用选项: -l 以长列表的形式列出 -a 显示其它用户的进程 -e 显示环境
-r     只显示正在运行的程序

46 ps 命令

47 ps 命令 栏目说明: UID 用户的标识号 PID 进程的标识号,是系统赋予每个正在执行的进程的唯一编号 PPID 父进程的标识号
PRI         进程优先级 SIZE       虚拟内存大小 RSS        驻留空间大小 STAT      进程状态。R—正在运行;S—睡眠;D—不可中断睡眠;T—停止或跟踪;Z—僵尸进程;W—没有驻留页 TTY       进程所在的虚拟终端号 TIME      该进程已经运行的时间 COMMAND          该进程的名称

48 ps 命令

49 ps 命令

50 pstree 显示系统进程树结构 常用选项 -a 显示各进程的命令行参数 -p 显示各进程的进程号 -h 对当前进程及其祖先高亮度显示

51 kill 命令 命令格式: kill [-s 信号 | -p ] [ -a ] 进程号 ... kill -l [信号] 常用选项:
-l   输出信号名列表,可以在/usr/include/linux/signal.h文件中找到 -p 指定kill命令只是显示进程的pid,并不真正送出结束信号 -s<信号名>  指出欲发出的信号,信号是以信号名或数字给出的 默认值为TERM信号

52 & 命令 功能:将进程放到后台运行 方法:在要运行的程序命令的最后加上“&”字符。 例子:(见上页)yes > /dev/null &
yes命令:向标准输出发送无穷尽的一串“y”。/dev/null 像一个黑洞,任何被送入这个黑洞的数据都会消失。 [1]——yes的作业号 7840——进程标识号

53 killall 命令 按名给进程发信号

54 nice 命令 功能:通过修改调度优先级来运行一个程序。 格式: nice [options][COMMAND[arg…]] 常用选项:
-n<adjustment>   加上由<adjustment>指定的优先级而不是默认值10。取值范围是0-19 --version           输出版本信息 nice值小的进程优先級高;nice值大的进程优先級低 

55 nice 命令

56 renice 命令 renice命令允许用户修改一个正在运行进程的优先权。 利用renice命令可以在命令执行时调整其优先权。其格式如下:
 $ renice -number PID  其中,参数number与nice命令的number意义相同。 注: (1) 用户只能对自己所有的进程使用renice命令。 (2) root用户可以在任何进程上使用renice命令。 (3) 只有root用户才能提高进程的优先权。

57 skill/snice 报告进程状态

58 nohup 命令 使进程在用户退出后仍继续执行 命令格式:nohup 命令
结果则会写到用户自己的目录下的nohup.out这个文件里(也可以使用输出重定向,让它输出到一个特定的文件)。

59 top 命令 功能:显示系统动态的进程控制和进程调度,还可察看内存动态使用的实时信息。 格式:top [options] 字段说明
Uptime   显示系统已运行时间和系统的三个加载均值,加载均值是指那些准备在1分、5分、15分内运行的进程的平均数。 process  显示在最近一次更新是运行的进程总数并作分类,可通过交互命令t来切换进程和状态显示。 CPUstates  显示再用户模式下的CPU利用的百分比、系统方式、优先级任务(优先级任务是指那些优先级为负的)和空闲任务。 Mem  显示内存使用的统计,包含总的可用内存、空闲内存、一用内存、共享内存和用于缓存的内存。可通过交互命令m来切换内存信息显示。

60 top 命令(续) 字段说明 PID 进程的标识号,是系统赋予每个正在执行的进程的唯一编号 PPID 父进程的标识号 UID 用户的标识号
字段说明         PID                   进程的标识号,是系统赋予每个正在执行的进程的唯一编号 PPID                 父进程的标识号 UID                   用户的标识号 USER                显示任务所有者的用户号 PRI                    进程优先级 NI                      显示任务的nice值 SIZE                  虚拟内存大小 TSIZE               显示任务的代码大小 DSIZE               显示数据加上堆栈的大小,但对ELF进程不显示。 TRS                   显示文本驻留空间的大小。

61 top 命令(续) 字段说明 SWAP 显示对交换空间的统计,包含总的交换空间,可用交换空间和一用交换空间。 D 显示脏页的大小
LIB       显示用用库页的大小,对ELF进程也无效。 RSS      显示被任务占用的物理内存的总量(以KB计) SHARE 先时任务占用的共享内存的量。 STAT     进程状态。R—进程正在运行;S—睡眠;D—不可中断睡眠;T—停止或跟踪;Z—僵尸进程;W—没有驻留页 TTY       进程所在的虚拟终端号 TIME     该进程已经运行的时间 COMMAND       该进程的名称

62 top 命令(续) 交互命令: 空格键 立即更新显示。 i 不显示空闲的或僵尸进程信息。 n 或 # 改变显示的进程数量。 q 退出top。
空格键                 立即更新显示。 i                            不显示空闲的或僵尸进程信息。 n 或 #                  改变显示的进程数量。 q                           退出top。 r                           重新调整一个指定进程的优先级。 f 或 F                  在显示时加上或移去字段。 o 或O                  改变显示字段的顺序。 m                         切换内存信息的显示。 t                           切换进程和CPU状态的显示。 

63 top 命令

64 uptime 显示系统已运行时间和系统的三个加载均值,加载均值是指那些准备在1分、5分、15分内运行的进程的平均数。

65

66 /proc文件系统

67 /proc文件系统简介 /proc文件系統是一个伪文件系統,它只存在內存当中,而不占用外存空间。

68 /proc目录列表

69 /proc下重要文件与子目录 /proc/1 关于进程1的信息目录。每个进程在/proc 下有一个名为其进程号的目录。
/proc/cpuinfo  处理器信息,如类型、制造商、型号和性能。 /proc/devices  当前运行的核心配置的设备驱动的列表。 /proc/dma  显示当前使用的DMA通道。 /proc/filesystems  核心配置的文件系统。 /proc/interrupts  显示使用的中断 /proc/ioports  当前使用的I/O端口。 /proc/kcore  系统物理内存映象。与物理内存大小完全一样,但不实际占用这么多内存 /proc/kmsg  核心输出的消息。也被送到syslog 。

70 /proc下重要文件与子目录(续) /proc/ksyms 核心符号表。
/proc/loadavg  系统"平均负载";3个没有意义的指示器指出系统当前的工作量。 /proc/meminfo  存储器使用信息,包括物理内存和swap。 /proc/modules  当前加载了哪些核心模块。 /proc/net  网络协议状态信息。 /proc/self  到查看/proc 的程序的进程目录的符号连接。当两个进程查看/proc 时,是不同的连接。这主要便于程序得到它自己的进程目录。 /proc/stat  系统的不同状态 /proc/uptime  系统启动的时间长度。 /proc/version  核心版本。

71 /proc文件系统

72 /proc文件系统

73 /proc进程目录的结构 目录名称  目录內容 Cmdline 命令行参數 Environ 环境变量值 Fd   一个包含所有文件描述符的目录 Mem   进程的內存被利用情況 Stat   进程狀态 Status  Process status in human readable form Cwd   当前工作目录的鏈接 Exe   Link to the executable of this process Maps  內存印象 Statm  进程內存狀态信息 Root    链接此进程的root目录

74

75 3、进程创建

76 创建一个进程 新的进程的创建是通过克隆当前的进程来实现的。一个新的任务是通过系统调用创建的( fork 或 clone ),克隆发生在核心的核心态。在系统调用的最后,产生一个新的进程,等待调度程序选择它运行。从系统的物理内存中为这个克隆进程的堆栈(用户和核心)分配一个或多个物理的页用于新的 task_struct 数据结构。一个进程标识符将会创建,在系统的进程标识符组中是唯一的。但是,也可能克隆的进程保留它的父进程的进程标识符。新的 task_struct 进入了 task 向量表中,旧的(当前的)进程的 task_struct 的内容拷贝到了克隆的 task_struct 。

77 do_fork系统调用流程

78 创建一个进程 克隆进程的时候, Linux 允许两个进程共享资源而不是拥有不同的拷贝。包括进程的文件,信号处理和虚拟内存。共享这些资源的时候,它们相应的 count 字段相应增减,这样 Linux 不会释放这些资源直到两个进程都停止使用。例如,如果克隆的进程要共享虚拟内存,它的 task_struct 会包括一个指向原来进程的 mm_struct 的指针, mm_struct 的 count 域增加,表示当前共享它的进程数目。

79 创建一个进程(续) 创建的子进程与父进程的区别: -进程号(PID) -父进程号(PPID) -所有的文件锁被重置 -收到信号量做的动作不同

80 一个创建进程的简单例子 #include <stdio.h> #include <sys/types.h>
#include <unistd.h> void main(void) { printf("Hello \n"); fork(); printf("bye\n"); }

81 fork fork: n.叉, 耙, 叉形物, 餐叉; 分岔;岔路 vt, vi 分岔;使成叉形 fork函数原型:
#include <unistd.h> pid_t fork(void); /* pid_t是一个unisigned int,是进程号对应的数据类型 */ fork函数创建一个新的进程 对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。 对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号即是fork函数对父进程的返回值。

82 另一个创建子进程的例子 main() { int i, pid; i = 0; pid = fork()
if ( pid == -1 ) { printf(“Fork error!\n”); exit(-1); } else if ( pid == 0 ) { /* 子进程程序 */ for ( i = 1; i <1000; i ++ ) printf(“BBB\n”); } else { /* 父进程程序*/ for ( i = 1; i <1000; i ++ ) printf(“AAA\n"); } }

83 创建子进程的例子

84 fork过程中父子进程的内存空间内容

85 一个程序,两个返回值? 子进程 程序 mov … i = 0 push … fork() int … … iret(fork) 系统调用返回
pop move 赋值 jz 判断 父进程 mov … push … 入栈 int … 系统调用 pop move 赋值 jz 判断 程序 i = 0 fork() iret(fork) 系统调用返回 pid=…

86 进程的终止 正常终止 从main函数返回 调用exit函数 调用_exit系统调用函数
进程通过调用系统调用exit自动退出,它在内核中是由sys_exit实现的(23322行)。当C程序从它的main部分返回时,就会潜在调用exit。 异常终止 调用abort函数 进程接收到某信号

87 进程的终止 当进程退出时,内核释放所有分配给这个进程的资源——内存、文件,等等
内核不能立即回收代表进程的struct task_struct结构,这是因为该进程的祖先必须能够使用wait系统调用查询其子孙进程的退出状态。wait返回它检测出的死亡状态的进程的PID。 处于这种在两种状态之间的进程——它既不是活动的,也没有真正死亡——被称为僵进程(zombies)。sys_exit的任务就是把活动进程转化为僵进程。

88 等待进程完成 子进程运行结束后(正常或异常),它并没有马上从系统的进程分配表中被删掉,而是进入僵死状态(Zombie),一直等到父进程来回收它的结束状态信息。 如果父进程没有回收走子进程的结束状态就已经退出,子进程将永远处于僵死状态;也有例外,如父进程先于子进程结束,子进程将被init进程继承,并回init进程回收其结束状态信息。

89 等待进程完成 回收子进程结束状态信息wait, waitpid 函数原型: #include <sys/wait.h>
pid_t wait(int *stat_loc); pid_t waitpid(pid_t pid, int *stat_loc, int options);

90 wait 当进程调用wait,它将进入睡眠状直到有一个子进程结束。wait函数返回子进程的进程id,stat_loc中返回子进程的退出状态。
waitpid的第一个参数pid的意义: pid > 0: 等待进程id为pid的子进程。 pid == 0: 等待与自己同组的任意子进程。 pid == -1: 等待任意一个子进程 pid < -1: 等待进程组号为-pid的任意子进程。 因此,wait(&stat)等价于waitpid(-1, &stat, 0)

91 wait的例子 int status; int pid; pid = fork();
if (pid == 0) /* child process */ {   printf("\n I'm the child!");   exit(0); } else /* parent process */ {   wait(&status);   printf("\n I'm the parent!")   printf("\n Child returned: %d\n", status) }

92 执行一个新程序 执行程序系统调用execve 函数原型: #include <unistd.h>
int execve(const char *path, const char *argv[], const char *envp[]); Linux还提供其它几个执行程序函数,execl,execlp,execle,execv,execvp都不是系统调用,依赖于execve。

93 执行一个新程序 path,执行的文件 argv,参数表 envp,环境变量表,一般直接用environ 如:
char *argv[] = {“gcc”, “-g”, “-c”, “rbtree.c”, NULL}; execve(“/usr/bin/gcc”, argv, environ);

94 执行一个新程序 execve启动一个新的程序,新的地址空间完全覆盖当前进程的地址空间,但当前进程把开的文件描述字(除非特别设置),当前工作目录等将被继承。 execve只返回负值表示调用失败,如果成功的话将永不返回。

95 EXEC的例子 /* using execvp to execute the contents of argv */
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char *argv[]) { execvp(argv[1], &argv[1]); perror("exec failure"); exit(1); }

96 思考 exec 与 fork 有何不同? Fork与exec合二为一好不好?

97 关于建立进程的典型代码段 if ((pid=fork())==-1) { /* 错误处理 */ … }
{ /* 错误处理 */ … } else if (pid >0) { /*父进程运行,例如wait(pid);*/ …. } else /*子进程运行,例如exec(“ap1”,…);*/ ….}

98

99

100 初始化Linux内核 start_kernel:内核初始化的工作
init:内核运行的第一个用户进程,它要负责触发其它必需的进程以使系统作为一个整体进入可用的状态。 init是系统中所有进程的祖先。 getty进程接受用户登录 getty进程产生login进程,login进程产生用户自己的shell,使用自己的shell,可以产生每一个用户运行的进程。

101 内核初始化详细过程 参见sys_init.gif

102 终端上的登录过程

103 终端登录方式下的UNIX进程层次

104 进程家族树中的登录过程   init(1)-+-crond(98)               |-emacs(387)               |-gpm(146)               |-inetd(110)               |-kerneld(18)               |-kflushd(2)               |-klogd(87)               |-kswapd(3)               |-login(160)---bash(192)---emacs(225)               |-lpd(121)               |-mingetty(161)               |-mingetty(162)                |-login(403)---bash(404)---pstree(594) |-xinetd---in.telnetd---login---bash              |-syslogd(78)               `-update(166) 

105 网络登录方式下的UNIX进程层次

106 UNIX shell工作过程

107 copy-on-write 写时复制 通过fork创建子进程系统开销很大,需要将每种资源都复制一个副本。这些开销并不是所有的情况下都是必须的,比如某进程fork出一个子进程后,其子进程仅仅是为了调用exec执行另一个执行文件,那么在fork过程中对于虚存空间的复制将是一个多余的过程 Linux中采取了copy-on-write技术,所以这一步骤的所做的工作只是虚存管理部分的复制以及页表的创建,而并没有包括物理也面的拷贝 父子进程以只读方式共享页框,当其中之一要修改页框时,内核才通过缺页异常处理程序分配一个新的页框,并将页框标记为可写。

108

109 4、线程

110 线程 操作系统中线程的概念 线程的分类。分为内核空间的线程和用户空间的线程 进程和线程的区别。
进程和线程之间的最大区别是一个进程的所有线程共享同一个内存空间并共享系统定义的“资源”。这些资源包括打开的文件句柄(文件描述符)、共享内存、进程同步原语和当前目录。因为共享全局内存并且几乎不必分配新内存,所以创建线程比创建进程更简单更快。

111 Linux线程 Linux内核使用的观点——线程只是偶然的共享相同的全局内存空间的进程。
轻量级进程 glibc 支持用户级线程

112 clone()系统调用 clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。
clone()调用最后会调用do_fork()实现 pid_t clone(int (*fn)(void * arg), void *stack, int flags, void * arg); 返回值同fork

113 clone()系统调用参数说明 参数:fn是进程所执行的过程,stack是进程所使用的堆栈,flags是CLONE_VM, CLONE_FS(fs记录了进程所在文件系统的根目录和当前目录信息), CLONE_FILES(进程可能的文件 ), CLONE_SIGHAND(自行定义的对信号的处理方式 ),CLONE_PID(Linux内核的clone()没有实现对CLONE_PID参数的支持)的组合。 fork 时 flag = SIGCHLD

114 clone()系统调用的例子 void * func(int arg) { . . . . . . } int main() {
    {         }   int main()   { int clone_flag, arg; clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES; stack = (char *)malloc(STACK_FRAME); stack += STACK_FRAME; retval = clone((void *)func, stack, clone_flag, arg); }

115 Linux下的用户空间线程 Linux系统下的多线程遵循POSIX线程接口,称为pthread。
头文件pthread.h,连接时需要使用库libpthread.a。 Linux下pthread的实现是通过系统调用clone()来实现的。 一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。

116 线程的创建和使用 线程的创建是用下面的几个函数来实现的. #include int pthread_create(pthread_t *thread,pthread_attr_t *attr, void *(*start_routine)(void *),void *arg); void pthread_exit(void *retval); int pthread_join(pthread *thread,void **thread_return);

117 参数说明 thread是用来表明创建线程的ID,attr指出线程创建时候的属性,用NULL表明使用缺省属性.start_routine函数指针是线程创建成功后开始执行的函数,arg是这个函数的唯一一个参数.表明传递给start_routine的参数. 通过从子例程返回或者调用 pthread_exit() 例程,Linux 线程就退出了。当父线程调用 pthread_join() 例程时,就回收线程使用的资源。pthread_join() 在本质上与进程的 wait() 函数很相似。pthread_join() 不回收线程分配的堆内存。需要由程序或线程来显式地释放全局分配的内存。

118 线程编程的简单例子 #include <stdio.h>
#include <pthread.h> void thread(void){ int i; for(i=0;i<3;i++) printf("This is a pthread.\n"); } int main(void){ pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0){ printf ("Create pthread error!\n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return (0); }

119 5、进程调度

120 实时进程与非实时进程 Linux把执行的任务比较紧迫的进程定义为实时进程,这些进程通常会比一般进程先得到机会运行。但这种实时只是软实时,不满足诸如中断等待时间等硬实时要求。两种调度策略:一种执行的任务比较短小,因此采用先进先出的策略;另一种则采用时间片轮转法。 非实时进程是相对实时进程而言的,Linux系统把执行的任务不怎么紧迫的进程统称为非实时进程。Linux通常对这类进程采用优先权算法进行调度。

121 Linux进程优先级 在Linux中,非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。

122 静态优先级 静态优先级——它不随时间而改变,只能由用户进行修改。它指明了在被迫和其它进程竞争CPU之前该进程所应该被允许的时间片的最大值(20)。

123 动态优先级 动态优先级——只要进程拥有CPU,它就随着时间不断减小;当它小于0时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量(最初为20) 。

124 实时优先级 实时优先级——值为1000。Linux把实时优先级与counter值相加作为实时进程的优先权值。较高权值的进程总是优先于较低权值的进程,如果一个进程不是实时进程,其优先权就远小于1000,所以实时进程总是优先。

125 Linux进程调度的策略 进程调度的策略主要考虑以下几个原则: * 高效 使处理器的利用率最高,空闲最小;
* 高效 使处理器的利用率最高,空闲最小; * 公平 使每一个申请处理器的进程都得到合理的处理器时间; * 周转时间短 使用户提交任务后得到结果的时间尽可能短; * 吞吐量大 使单位时间内处理的任务数量尽可能多; * 响应时间短 使对每个用户响应的时间尽可能短。

126 Linux进程的调度算法 时间片轮转调度算法:用于实时进程。 优先权调度算法
FIFO(先进先出) 调度算法 采用FIFO的实时进程必须是运行时间较短的进程,因为这种进程一旦获得CPU就只有等到它运行完或因等待资源主动放弃CPU时其它进程才能获得运行机会。

127 Linux进程的调度时机 进程状态转换时: 如进程终止,睡眠等; 可运行队列中增加新的进程时; 当前进程的时间片耗尽时;
进程从系统调用返回到用户态时; 内核处理完中断后,进程返回到用户态。

128 调度优先级 调度优先级:    (1)中断(硬件支持)    (2)当前的核心态进程    (3)其他核心态进程    (4)用户进程(软件管理)

129  用户进程的优先级  用户进程的优先级分类    (1)实时进程    (2)交互进程:视为IO bound进程    (3)批处理进程:视为CPU bound进程

130 具体描述 中断可以抢占核心进程,但必须保证在中断处理结束后返回它。-- 一个核心态进程可以禁用它所运行的处理器上的中断,从而保证不会被中断请求信号中断它的运行。 核心态进程不可抢占中断处理进程。--中断处理期间,仅仅是在中断处理进程处理完成或其他被允许的中断发生时候才发生任务切换。 核心态进程不可抢占其他核心态进程。--除非一个核心态进程自愿睡眠,否则不能切换到别的进程。这保证了核心代码块的对其他进程的整体性,大大的简化了核心保护机制。 特点:核心态进程是非抢占,用户态进程是抢占的。 优点:核心数据的同步和保护机制比较简单。 缺点:不能很好的支持响应速度要求高的实时应用。

131 普通进程调度算法 对于普通进程,Linux采用动态优先调度 选择进程主要依据counter的大小。
进程创建时,优先级priority被赋一个初值,一般为0~70之间的数字,这个数字同时也是计数器counter的初值。都表示进程的“时间片”。Priority代表分配给该进程的时间片,counter表示该进程剩余的时间片。 在进程运行过程中,counter不断减少,而priority保持不变,以便在counter变为0的时候(该进程用完了所分配的时间片)对counter重新赋值。 当一个普通进程的时间片用完以后,并不马上用priority对counter进行赋值,只有所有处于可运行状态的普通进程的时间片(p->counter==0)都用完了以后,才用priority对counter重新赋值,这个普通进程才有了再次被调度的机会。

132 Linux进程动态优先级变化过程

133 时间片 Linux的时间单位也是“时钟滴答”,(Linux为10ms)。
进程的时间片就是指多少个时钟滴答,比如,若priority为20,则分配给该进程的时间片就为20个时钟滴答,也就是20*10ms=200ms。 Linux中某个进程的调度策略(policy)、优先级(priority)等可以作为参数由用户自己决定,具有相当的灵活性。 内核创建新进程时分配给进程的时间片缺省为200ms,用户可以通过系统调用改变它。

134 实时进程调度策略 两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)。
实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准。每个进程有两个优先级,实时优先级就是用来衡量实时进程是否值得运行的。 Linux用函数goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数综合了上面提到的各个方面,给每个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值作为选择进程的唯一依据。

135 Policy的值 SCHED_OTHER:普通的用户进程,进程的缺省类型,采用动态优先调度策略,选择进程的依据主要是根据进程goodness值的大小。可以被高goodness值的进程抢先。 SCHED_FIFO:这是一种实时进程,遵守POSIX1.b标准的FIFO(先入先出)调度规则。它会一直运行,直到有一个进程因I/O阻塞,或者主动释放CPU,或者是CPU被另一个具有更高rt_priority的实时进程抢先。在Linux实现中,SCHED_FIFO进程仍然拥有时间片-只有当时间片用完时它们才被迫释放CPU。因此,如同POSIX1.b一样,这样的进程就象没有时间片(不是采用分时)一样运行。 SCHED_RR:这也是一种实时进程,遵守POSIX1.b标准的RR(循环round-robin)调度规则。除了时间片有些不同外,这种策略与SCHED_FIFO类似。当SCHED_RR进程的时间片用完后,就被放到SCHED_FIFO和SCHED_RR队列的末尾。

136 其他 只要系统中有一个实时进程在运行,则任何SCHED_OTHER进程都不能在任何CPU运行。每个实时进程有一个rt_priority,因此,可以按照rt_priority在所有SCHED_RR进程之间分配CPU。其作用与SCHED_OTHER进程的priority作用一样。只有root用户能够用系统调用sched_setscheduler,来改变当前进程的类型(sys_nice,sys_setpriority)。 此外,内核还定义了SCHED_YIELD,这并不是一种调度策略,而是截取调度策略的一个附加位。如同前面说明的一样,如果有其他进程需要CPU,它就提示调度程序释放CPU。特别要注意的就是这甚至会引起实时进程把CPU释放给非实时进程。

137 直接启动调度 直接启动:发生在当前进程因等待资源而需要进入被阻塞状态时。调度程序执行的步骤如下:     1、把当前进程(全局变量current指向的task_struct变量)放到适当的等待队列里;     2、把当前进程的state设为TASK_INTERRUPTIBEL或者TASK_UNINTERRUPTIBEL;     3、调用schedule(),准备让新的进程掌握CPU;     4、检查当前进程所需的资源是否可用,如果是,则把当前进程从等待队列里删除,否则回到第2步

138 被动调用调度 通过在当前进程的need_resched设为1,就可以实现另一种方式重新调度各个进程。因为每次调入一个用户态进程之前,这个变量的值都会被检查,因此schedule()又会很快被调用。 被动调用在以下情况下执行: (1)当当前进程用完了它的CPU时间片,update_process_times()重新进行计算。 (2)当一个进程被唤醒,而且它的优先级比当前进程高。wake_up_process()调用reschedule_idle(),设置当前进程的need_resched,使被唤醒的进程尽快掌握CPU。 (3)当sched_setschedler()或sched_yield()系统调用被调用的时候。

139 主要的进程调度的函数分析 schedule(void):真正执行调度的函数,它选择一个最合适的进程执行,并且真正进行上下文切换,使得选中的进程得以执行。 reschedule_idle(struct task_struct *p):为进程选择一个合适的CPU来执行,如果它选中了某个CPU,则将该CPU上当前运行进程的need_resched标志置为1,然后向它发出一个重新调度的处理机间中断,使得选中的CPU能够在中断处理返回时执行schedule函数,真正调度进程p在CPU上执行。 goodness()函数:用来衡量一个处于可运行状态的进程值得运行的程度。

140 goodness()函数分析 goodness()函数计算一个处于可运行状态的进程值得运行的程度。一个任务的goodness是以下因素的函数:正在运行的任务、想要运行的任务、当前的CPU。 goodness返回下面两类值中的一个:1000以下或者1000以上。1000或者1000以上的值只能赋给“实时”进程,从0到999的值只能赋给普通进程。实际上,在单处理器情况下,普通进程的goodness值只使用这个范围底部的一部分,从0到41。在SMP情况下,SMP模式会优先照顾等待同一个处理器的进程。 实时进程的goodness值的范围是从1001到1099。 源码在sched.c中。

141 goodness( ) 计算进程的权值 根据policy区分实时进程和普通进程。
若为实时进程返回权值 p->rt_priority。 若非实时进程,将进程剩余时间片加priority作为权值返回。如果进程是内核线程或用户空间与当前进程相同, 因而不必切换用户空间,则给予权值额外加一的"优惠"。

142 schedule()函数分析 作用:选择一个合适的进程在CPU上执行,它仅仅根据'goodness'来工作。 流程:
①将prev和next设置为schedule最感兴趣的两个进程:其中一个是在调用schedule时正在运行的进程(prev),另外一个应该是接着就給予CPU的进程(next)。注意:prev和next可能是相同的-schedule可以重新调度已经获得cpu的进程. ②中断处理程序运行"下半部分". ③内核实时系统部分的实现,循环调度程序(SCHED_RR)通过移动"耗尽的"RR进程-已经用完其时间片的进程-到队列末尾,这样具有相同优先级的其他RR进程就可以获得CPU了。同时,这补充了耗尽进程的时间片。 ④由于代码的其他部分已经决定了进程必须被移进或移出TASK_RUNNING状态,所以会经常使用schedule,例如,如果进程正在等待的硬件条件已经发生,所以如果必要,这个switch会改变进程的状态。如果进程已经处于TASK_RUNNING状态,它就无需处理了。如果它是可以中断的(等待信号),并且信号已经到达了进程,就返回TASK_RUNNING状态。在所以其他情况下(例如,进程已经处于TASK_UNINTERRUPTIBLE状态了),应该从运行队列中将进程移走。 ⑤将p初始化为运行队列的第一个任务;p会遍历队列中的所有任务。 ⑥c记录了运行队列中所有进程最好的

143 6、进程间通信(IPC)

144 基本概念 为什么需要 IPC 原子操作, 死锁和竞态 同步

145 管道 最常见的 IPC 机制 通过 pipe 系统调用 #include <unistd.h>
int pipe(int filedes[2]); 数据要经过内核传递, 效率较低 只能在关系进程之间进行, 比如父子进程之间 管道是单工的

146 使用pipe 系统调用的例子 #include <stdio.h> #include <sys/types.h>
#include <unistd.h> #define MAXLINE 256 void err_sys (const char* info) { perror (info); exit (1); } int main (void) int n, fd [2]; pid_t pid; char line [MAXLINE]; if (pipe (fd) < 0) err_sys ("pipe error"); if ( (pid = fork ()) < 0) err_sys ("fork error"); else if (pid > 0) { // parent close (fd [0]); write (fd [1], "hello world\n", 12); } else { // child close (fd [1]); n = read (fd[0], line, MAXLINE); write (STDOUT_FILENO, line, n); exit (0);

147 命名管道 命名管道是一种先进先出(FIFO)的数据结构,它允许两个进程通过管道联接实现信息交换。 在Unix系统中,命名管道是一种特殊类型的文件,因此可以对命名管道进行读写操作;当然,同样 也会有读写和执行等权限的限制。 FIFO 是存在于文件系统的对象, 用 "mknode <name> p", 或 mkfifo (1, 3) 命令或函数建立 通过 FIFO 的通讯可发生在任何两个进程之间, 只要对 FIFO 有适当的访问权限 对 FIFO 的读写操作与普通文件类似 用 FIFO 实现的 C/S 结构

148 System V 的 IPC 机制 消息传递 共享内存: 效率最高的 IPC 机制, 但没有同步机制
信号量: 实际是一种同步机制, 通常与共享内存一起使用

149 消息队列 消息队列(message queues)是进程之间互相发送消息的一种异步(asynchronously)方式
四种与队列相关的系统调用: msgget——得到唯一标识一个消息队列的标识号。 msgsnd——向一个消息队列发送一条消息。 msgrcv——从一个消息队列中接受一条消息。 msgctl——在消息队列上执行一组管理操作——检索关于它的限制的信息(比如队列所允许的最大消息数据量)、删除一个队列,等等。

150 信号量(Semaphores) Linux内核将信号量分为两类:
非实时的(Nonrealtime)——大部分是些传统的信号量,例如SIGSEGV,SIGHUP和SIGKILL。 实时的(realtime)——由POSIX b标准规定

151 共享内存 共享内存(shared memory):一块预留出的内存区域,而且一组进程均可对其进行访问。
共享内存是三种IPC机制里最快的一种,而且也是最简单的一种。 问题:互斥访问

152

153 7、进程编程

154 进程控制相关函数(1) 进程标识符 #include <unistd.h> pid_t getpid (void);
pid_t getppid (void); 用户和组标识符 #include <sys/types.h> uid_t getuid (void); uid_t geteuid (void); uid_t getgid (void); uid_t getegid (void);

155 进程控制相关函数(2) fork: 派生子进程 #include <unistd.h>
#include <sys/types.h> pid_t fork (void); vfork: 用于派生后立即 exec 的情况。 * 不复制父进程的全部地址空间. * vfork 保证子进程首先运行, 直到调用 exec 或 exit 之后, 父进程才能得到运行机会. pid_t vfork (void);

156 进程控制相关函数(3) exit 调用由 atexit 注册的清除函数, 并关闭所有的标准 I/O 流
exit 调用 _exit 处理与 UNIX 相关的内容, 比如文件描述符, 作业控制等 进程的退出状态非常重要. 一般以 0 值表示正常退出 #include <stdlib.h> void exit (int status); #include <unistd.h> void _exit (int status);

157 进程控制相关函数(4) 进程的退出状态必须用 wait 函数由父进程获取, 否则形成僵尸进程 wait/waitpid
* waitpid 可等待指定的子进程退出, 可指定 WNOHANG 选项而不阻塞调用进程的执行. #include <sys/types.h> #include <sys/wait.h> pid_t wait (int *status) pid_t waitpid (pid_t pid, int *status, int options);

158 进程控制相关函数(5) exec 函数组 #include <unistd.h> extern char **environ;
int execl ( const char *path, const char *arg, ...); int execlp ( const char *file, const char *arg, ...); int execle ( const char *path, const char *arg, ..., char * const envp[]); int execv ( const char *path, char *const argv[]); int execvp ( const char *file, char *const argv[]); int execve (const char *filename, char *const argv [], char *const envp[]); * 以可变参数形式传递命令行时, 最后一个参数应该为 NULL 指针. * execve 是实际的系统调用, 而其他的函数以库函数实现, 最终要调用 execve.

159 进程控制相关函数(6) system 函数 用户和组标识符 getlogin: 获取 login name
cuserid: 获取进程有效用户 ID 的关联名称 #include <unistd.h> char * getlogin ( void ); #include <stdio.h> char * cuserid ( char *string );

160 进程控制相关函数(7) times: 获取当前进程的时间信息, 包括用户态时间, 核心态时间等等
#include <unistd.h> #include <sys/times.h> clock_t times (struct tms *buf); struct tms { clock_t tms_utime; /* user time */ clock_t tms_stime; /* system time */ clock_t tms_cutime; /* user time of children */ clock_t tms_cstime; /* system time of children */ }; * times 返回的时间均以系统启动以来的时钟滴答计算.

161 源代码中和进程管理相关的文件 include/linux/ * tasks.h: 定义系统进程调度的常量
* sched.h interrupt.h: tqueue.h wait.h: locks.h spinlock.h smp_lock.h timer.h signal.h kernel/ * sched.c softirq.c fork.c itimer.c signal.c ipc/ msg.c shm.c sem.c util.c arch/i386/kernel process.c entry.s traps.c irq.c irq.h include/asm-i386/ processor.h irq.h hardirq.h softirq.h semaphore.h locks.h spinlock.h smplock.h ......

162

163 UNIX处理机管理总体实现机制

164 对该图的说明 1.每个进程的进程管理信息存放在proc结构和user结构中 2. ppda区
3.除了进程0外,每个进程都在两种状态之一运行:核心态和用户态 4.UNIX的CPU调度算法,采用优先级调度算法

165 LINUXCPU管理实现机制总瞰

166 Linux进程与任务的关系

167 Linux任务状态转换图

168 LINUX的CPU调度过程


Download ppt "第六章 Linux内核."

Similar presentations


Ads by Google