1 张惠娟 副教授 Linux 进程管理
2 内容 进程组成 进程环境 进程管理内容 进程控制块 进程状态 进程调度 进程控制 进程通信
3 进程组成 Linux 是一个多任务多用户操作系统,采用进 程模型。 进程都具有一定的功能和权限,运行在各自独 立的虚拟地址空间,彼此独立,且通过通信 机制实现同步互斥,通过调度程序实现合理 调度。
4 进程组成 正文段 存放进程要运行的程序,描述了进程要完成的功能 用户数据段 存放正文段在执行时所需要的数据和工作区 系统数据段 存放了进程的控制信息,其中最重要的数据结构 是 task_struct 。 进程组成
5 进程环境 Linux 进程有两种状态:内核态和用户 态 核心态又称系统态 Linux 在执行内核程序时是处于核心态下 用户态是进程的普通执行状态 一个进程在运行过程中,总是在两种执行状态之 间不断地转换。
6 进程虚拟地址空间分为:用户空间和系统空间。 用户空间 用户进程本身的程序和数据(可执行映象) 进程运行用户程序时使用的堆栈,即进程堆栈。 系统对进程进行控制和管理的信息,如进程控制块等 系统空间 内核被映射到所有进程的系统空间中。 只允许进程在核心态下访问。进程运行在用户态下时, 不允许直接访问系统空间。 进程只能通过系统调用转换为核心态后,才能访问系统 空间 进程环境
7 进程上下文 系统提供给进程处于动态变化的运行环境总和称为进 程上下文 系统上下文 系统完成自身任务时的运行环境称为系统上下文 内核在系统上下文中执行时不会阻塞。
8
9 进程管理内容 进程管理由进程控制块、进程调度、中断处 理、任务队列、定时器, bottom half 队列、 系统调用、进程通信等部分组成。 进程管理是 Linux 存储管理,文件管理,设备 管理的基础。
10 进程控制块 进程控制块是 Linux 系统最复杂的数据 结构之一。 Linux 在内存空间中开辟了一个专门区域存 放所有进程的进程控制块。 系统初始化后,建立第一个 task_struct 数 据结构 INIT_TASK 。 新进程创建时,系统从内存分配新 task_struct ,占据 1680 个字节。
11 进程状态和标志 进程标识 进程控制块
12 进程的族亲关系 进程控制块
13 进程间链接信息 进程调度信息 进程控制块
14 进程的时间信息 进程的虚存信息 进程控制块
15 进程的文件信息 与进程间通信有关的信息 进程控制块
16 其它信息 进程控制块
17 进程状态 定义了六种状态进程状态 #define TASK_RUNNING0 #define TASK_INTERRUPTIBLE1 #define TASK_UNINTERRUPTIBLE2 #define TASK_ZOMBIE4 #define TASK_STOPPED8 #define TASK_SWAPPING16
18
19 进程调度 调度方法 调度策略 调度参数 调度方法 调度时机
20 调度方法 Linux 进程调度方式 采用抢占调度方式 (内核不抢占) 进程分为普通进程和实时进程,分别采 用不同的调度策略,实时进程的优先级 高于普通进程。 进程调度
21 调度策略 进程调度
22 调度参数 policy 进程调度策略,可通过系统调用 sys_sched_setscheduler() 更改 ( kernel/sched.c )。 SCHED_OTHER 非实时进程,基于优先级的 轮转法 SCHED_FIFO 实时进程,用先进先出算法 SCHED_RR 实时进程,用基于优先权的轮转 法 进程调度
23 priority 进程优先级(静态),给出进程每次获取 cpu 后可使用的时间(按 jitty 计算)。通过系统调 用 sys_setpriority() 改变。 Linux 的基准时间( kernel/timer.c )。系统初 始化时清零,以后每隔 10ms 由时钟中断服务 程序, do_timer 增 1 。 进程调度
24 rt_priority 实时进程的优先级,可通过系统调用 sys_sched_setscheduler() 改变. Counter 进程动态优先级表示进程当前还可运行多久 进程开始运行时被赋为 priority 值,以后,每隔 一个 tick (时钟中断)递减 1 ,减到 0 时引起新 一轮调度。 重新调度将从 run-queue 队列中选出 counter 值 最大的就绪进程获得 cpu 。 进程调度
25 进程调度 调度方法 采用动态优先级法,调度对象是可运行队列。 进程在运行中, counter 代表动态优先级。 Linux 采取了加权的方法来保证实时进程优先于 普通进程。普通进程的权值就是它的 counter 的 值,实时进程的权值是它的 rt_priority 的值加 1000 。 调度过程中,调度程序检查可运行队列中所有进 程的权值,选择其中权值最大的进程做为下一个 运行进程。
26 static inline int goodness(struct task_struct * p, struct task_struct * prev, int this_cpu) { int weight; if (p->policy != SCHED_OTHER) return p->rt_priority; weight = p->counter; …… return weight; } 进程调度
27 调度时机 时机 1 :进程状态发生变化时 处于运行态下的进程要等待某种资源 运行态下的进程在程序执行完毕后,通过 调用内核函数 do_exit() 终止运行并转入僵 死态。 处于等待态的进程被唤醒后,将加入到可 运行队列中时 进程从运行态转入暂停态时 进程从暂停态成为可运行态时 进程调度
28 时机 2 当前进程时间片用完时 时机 3 进程从系统调用返回到用户态时 时机 4 中断处理后,进程返回到用户态时。 进程调度
29 进程控制 进程创建过程 进程状态间转换
30 进程创建过程 为新进程分配任务结构体内存空间 把父进程任务结构体拷贝到子进程任务结构体 为新进程在其虚拟内存建立内核堆栈 对子进程任务结构体中部分进行初始化设置 把父进程有关信息拷贝给子进程,建立共享关系 把子进程的 counter 设为父进程 counter 值的一半 把子进程加入到可运行队列中 结束 do_fork() 函数返回 PID 值 进程控制
31 创建过程具体描述 系统启动时创建第一个进程( 0 号进程) 此时,系统只有这一个进程:初始化进程,运行 在核心态 初始化结束时,初始进程启动一个核心进程: init 进程,也称为 1 号进程,然后执行空闲循环。 系统空闲时,调度程序运行这个空闲进程。这个 空闲进程的 task_struct 是唯一一个不是动态分配 而是在核心连接时静态定义的,为了不至于混淆, 叫做 init_task 。 进程控制
32 进程由 do_fork() 函数创建 int do_fork(unsigned long clone_flags, unsigned long usp, struct pt_regs *regs) { 为新进程申请 PCB 空间 ; if ( 申请不到 ) 返回错误, 退出 ; 为新进程申请核心堆栈 ; if ( 核心堆栈申请不到 ) 返回错误, 退出 ; 为新进程在 Task 向量表中找到空闲位置 ; /* 复制父进程 current PCB 中的信息,继承资源 */ ; p = current; 进程控制
33 /* 为防止信号、定时中断误唤醒未创建完毕的进 程,将子进程的状态设成不可中断的 */ p->state = TASK_UNINTERRUPTIBLE; /* 跟踪状态和超级用户特权是没有继承性的,因为在 root 用户为 普通用户创建进程时,出于安全考虑这个普通用户的进程不允 许拥有超级用户特权。 */ p->flags &= ~ (PF_PTRACED|PF_TRACESYS|PF_SUPERPRIV); /* 将进程标志设成初建,但暂时不能运行,在进程第一次获得 CPU 时,内核将根据此标志进行一定操作 */ p->flags |= PF_FORKNOEXEC; 进程控制
34 开始 Task_struct 的初始化工作,如初始化进程时钟、 信号、时间等数据 ; 继承父进程所有资源 : 拷贝父进程当前打开的文件 ; 拷贝父进程在 VFS 的位置 ; 拷贝父进程的信号量 ; 拷贝父进程运行的内存 ; 拷贝父进程的线程 ; 初始化工作结束,父进程将其将其唤醒, 挂入 running 队列中,返回子进程的 pid; 进程控制
35
36 进程状态间转换
37 转换说明 sleep_on(): TASK_RUNNING->TASK_UNINTERRUPTIBLE 拥有 CPU 的进程申请资源无效时,通过 sleep_on() ,将 进程从 TASK_RUNNING 切换到 TASK_UNINTERRUPTIBLE 状态。 sleep_on() 函数作用就是将 current 进程的状态置成 TASK_UNINTERRUPTIBLE, 并加到等待队列中。 一般来说引起状态变成 TASK_UNINTERRUPTIBLE 的资 源申请,都是对一些硬件资源的申请,如果得不到这些 资源,进程将不能执行下去,不能由 signal 信号或时钟 中断唤醒回到 TASK_RUNNING 状态。 进程状态间转换
38 interruptible_sleep_on() : TASK_RUNNING->TASK_INTERRUPTIBLE 拥有 CPU 的进程申请资源无效时,通过该函数将进程从 TASK_RUNNING 切换到 TASK_INTERRUPTIBLE 状态。 interruptible_sleep_on() 函数作用就是将 current 进程的状态 置成 TASK_INTERRUPTIBLE, 并加到等待队列中。 处于 TASK_INTERRUPTIBLE 状态的进程可在资源有效时被 wake_up() 、 wake_up_interruptible() 或 wake_up_process() 唤醒,或收到 signal 信号以及时间中断后被唤醒。 进程状态间转换
39 sleep_on_timeout() : TASK_RUNNING- >TASK_UNINTERRUPTIBLE interruptible_sleep_on_timeout (): TASK_RUNNING->TASK_INTERRUPTIBLE 虽然在申请资源或运行中出现了某种错误,但是 系统仍然给进程一次重新运行的机会。调用该函 数将进程从 TASK_RUNNING 切换到 TASK_INTERRUTIBLE 状态,并等待规定的时间 片长度, 再重新试一次。 进程状态间转换
40 wake_up() TASK_UNINTERRUPTIBLE-> TASK_RUNNING TASK_INTERRUPTIBLE-> TASK_RUNNING 处于 TASK_UNINTERRUPTIBLE 状态的进程不能由 signal 信号 或时钟中断唤醒,只能由 wake_up() 或 wake_up_process() 唤醒。 wake_up() 函数的作用是将 wait_queue 中所有状态为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 的 进程 状态置为 TASK_RUNNING, 并将它们都放 running 队列中去,即唤醒所有等待在该队列上的进程 。 进程状态间转换
41 进程通信机制 支持大量的进程通信机制 锁机制 信号 管道 消息队列 信号量 共享内存
42 信号 操作系统通过信号向进程发送异步事件信号。 当一个事件发生时,如果需要通知进程,则系 统就为其生成一个信号,进程在接受到信号后, 可采取适当动作来处理信号。 在 linux 系统中,内核用一个字代表所有信号, 信号种类的树目和具体平台有关,如 32 位、 64 位。 信号是内核不可分割的一部分,不象其他 ipc , 是可选的。 进程通信机制
43 进程对信号的操作 忽略信号 阻塞信号 由进程处理信号 由内核进行默认处理 进程通信机制
44 管道( pipe ) 有名管道 一般为系统特殊文件方式, 使用的进程之间不一定 要有父子关系或兄弟关系. 无名管道 一般为内存方式, 使用的进程之间一定要有父子关 系或兄弟关系. 无名管道实现方法 两个 file 数据结构指向同一个临时 VFS INODE 节点 (本身指向内存中的一个物理页)实现。 进程通信机制
45 f_mode … f_inode f_op … f_mode … f_inode f_op … 写操作 读操作 数据页面 VFS inode 进程 1 的 file 结构进程 2 的 file 结构 进程通信机制
46 pipe_write() \fs\pipe.c 中定义了 pipe_write 函数 把字节从进程地址空间拷贝到共享数据页 如果管道被读进程锁定或者空间不够,当前进程睡眠,放 在管道 INODE 节点等待队列中,并调用调度程序,运行 另外一个进程。 写过程可以中断,所以可以接收信号。当管道中有足够空 间写数据或者锁定解除,写进程就会被读进程唤醒。 数据写完之后, VFS INODE 节点锁定解除,管道 INODE 节点的等待队列中的所有读进程都会被唤醒。 进程通信机制
47 pipe_write() 和写过程相似。 允许进行非阻塞读,即如果没有数据可读或者 管道被锁定,返回一个错误。这意味着进程会 继续运行。 另一种方式是在管道的 INODE 节点的等待队列 中等待,直到写进程完成。 如果管道进程都完成操作,管道 INODE 节点和 相应的共享数据页被废弃。 进程通信机制