Linux进程管理

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()传入。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值