您的位置:首页 > 运维架构 > Linux

Linux进程管理

2015-08-28 21:38 399 查看
Linux系统中进程的四要素:

①有一段程序供其执行。这段程序不一定是某个进程所专有,可以与其他进程共用。

②有进程专用的内核空间堆栈。

③在内核中有一个task_struct数据结构,即通常所说的“进程控制块”。有了这个数据结构,进程才能成为内核调度的一个基本单位接受内核的调度。

④有独立的用户空间。(有独立的用户空间的是进程,没有独立的用户空间但有共享的用户空间的是用户线程,既没有独立的用户空间也没有共享的用户空间的是内核线程)

Linux中进程的状态不仅仅有就绪、执行、阻塞这三大基本状态,如图:



比较重要的几个状态是:

①TASK_RUNNING

进程正在被CPU执行,或者已经准备就绪,随时可以执行。当一个进程刚被创建时,就处于TASK_RUNNING状态。

②TASK_INTERRUPTIBLE

处于等待中的进程,待等待条件为真时被唤醒,也可以被信号或者中断唤醒。

③TASK_UNINTERRUPTIBLE

处于等待中的进程,待资源有效时唤醒,但不可以由其它进程通过信号(signal)或中断唤醒。

④TASK_KILLABLE

Linux2.6.25新引入的进程睡眠状态,原理类似于TASK_UNINTERRUPTIBLE,但是可以被致命信号(SIGKILL)唤醒。

⑤TASK_TRACED

正处于被调试状态的进程。

⑥TASK_DEAD

进程退出时(调用do_exit),所处的状态。

在Linux内核代码中,线程、进程都使用结构task_struct来表示,该结构定义在sched.h。它包含了大量描述进程/线程的信息,其中比较重要的有:

pid_t pid; //进程号

vlong state; //进程状态

vint prio; //进程优先级

进程调度

进程调度就是从就绪的进程中选一个进程出来执行。主要的知识点有三个:

①调度策略

②调度时机

③调度步骤

Linux支持丰富的调度策略,常见的有:

SCHED_NORMAL(SCHED_OTHER):普通的分时进程

vSCHED_FIFO :先入先出的实时进程

vSCHED_RR:时间片轮转的实时进程

vSCHED_BATCH:批处理进程

vSCHED_IDLE: 只在系统空闲时才能够被调度执行的进程

实时进程一般优先级较高。

调度时机,也就是什么时候进行进程调度,更具体的说法是什么时候调用schedule()函数。调度时机分为主动式和被动式。

主动式:当进程需要等待资源等而暂时停止运行时,会把自己的状态置于挂起(睡眠),并主动请求调度,让出CPU。

current->state = TASK_INTERRUPTIBLE;
schedule();


current是一个task_struct结构的指针,该指针指向当前正在运行的进程的进程控制块(task_struct),通过该指针可以修改进程控制块中进程的状态。

被动式调度又被称为抢占式调度,分为用户态抢占(Linux2.4、Linux2.6支持)和内核态抢占(Linux2.6支持)。

用户态抢占可能发生在:

①从系统调用返回用户空间。

②从中断处理程序返回用户空间。

这并不是表示一定会发生用户抢占。从内核返回用户空间的时候,它会检查need_resched标志的值,此时如果need_resched标志为1,会导致schedule()被调用,即发生用户抢占。need_resched被设置的时机为:

①当进程耗尽它的时间片时,会设置need_resched标志为1

②当一个优先级更高的进程进入可执行状态的时候,也会设置need_resched标志。

用户态抢占存在缺陷。当进程/线程一旦运行到内核态,就可以一直执行,直到它主动放弃或时间片耗尽为止。这样会导致一些非常紧急的进程或线程将长时间得不到运行,降低整个系统的实时性。改进方式是允许系统在内核态也支持抢占,更高优先级的进程/线程可以抢占正在内核态运行的低优先级进程/线程,这就是内核抢占。

内核抢占可能发生在:

①中断处理程序完成,返回内核空间之前。

②当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等。

但在支持内核抢占的系统中,某些特例下是不允许抢占的:

①内核正在运行中断处理。

②内核正在进行中断上下文的Bottom Half(中断的底半部)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。

③进程正持有spinlock自旋锁、writelock/readlock读写锁等,当持有这些锁时,不应该被抢占,否则由于抢占将可能导致其它进程长期得不到锁,而让系统处于死锁状态。

④内核正在执行调度程序Scheduler。

为保证Linux内核在以上情况下不会被抢占,抢占式内核使用了一个变量preempt_count,称为内核抢占计数。这一变量被设置在进程的thread_info结构中。每当内核要进入以上几种状态时,变量preempt_count就加1,指示内核不允许抢占。每当内核从以上几种状态退出时,变量preempt_count就减1,同时进行可抢占的判断与调度。

调度步骤,即schedule()函数的工作流程如下:

①保存程序计数器以及其他寄存器;

②更新当前处于“执行态”的进程的进程控制块,把进程状态改为相应状态,更新其他相关域;

③把被切换进程的进程控制块移到相关状态的队列;

④选择另外一个进程开始执行,把该进程进程控制块的状态改为“执行态”;

⑤恢复被选择进程在最近一次被切换出执行态时的上下文,比如载入程序计数器以及其他处理器的值。

在Linux里面,线程就是轻量级的进程,只不过进程里面的线程共享一部分资源,如地址空间、文件句柄、信号量等。线程的调度也按照进程的调度方式进行。

Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和 fork (),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境;而使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的”进程”拥有共享的运行环境,只有栈是独立的,由 __clone()传入。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: