Linux进程和线程在内核中的管理

内核中是如何调度进程、线程的

在内核中无论是进程的处理方式,还是线程的处理方式,都需要一个统一的管理体系来对其进行管理。
在Linux里面,无论是进程,还是线程,到了内核里面,我们统一都叫任务(Task),由一个统一的结构task_struct进行管理。这个结构非常复杂。首先需要一个链表,将所有的task_struct串起来。

struct list_head    tasks;

下面讨论下每个任务包含的字段

任务ID

每一个任务都应该有一个任务ID,作为任务的唯一标识,task_struct里面涉及的任务ID,有下面几个:

pid_t pid;
pid_t tgid;
struct task_struct *group_leader; 

为什么不能只用一种ID号就解决问题呢?
因为在内核中进程和线程都被看作是任务,但是我们必须对进程和线程进行区别
问题一:任务展示的时候,用到的PS命令,到了内核按照上面的任务列表把命令都显示出来,这完全是没必要的,我只关心自己想要知道的进程的情况,不需要把其余的无关的线程打印出来。
问题二:给任务下发指令。当一个项目要终止的时候,这时候应该发命令给整个项目组,而不是某一个线程。kill用来给进程发信号,通知进程退出;如果发给其中的一个线程的话,整个进程都会退出。
pid–process id
tgid–thread group ID
任何一个进程,如果只有主线程,那么pid,tgid是自己,group_leader指向的还是自己 。如果一个进程创建了其他线程,那么线程有自己的pid,tgid就是进程的主线程的pid,group_leader指向的就是线程的主进程。
如何判断一个task_struct是进程还是线程?判断它的pid和tgid是否相等?相等则为进程,不相等则为线程

信号处理

task_struct中关于信号处理的字段:

/* Signal handlers: */
struct signal_struct    *signal;//里面还有一个线程共享的结构
struct sighand_struct    *sighand;//通过信号处理函数处理的信号
sigset_t      blocked;//被阻塞暂不处理的信号
sigset_t      real_blocked;
sigset_t      saved_sigmask;
struct sigpending    pending;//尚等待处理的信号(本任务的)
unsigned long      sas_ss_sp;
size_t        sas_ss_size;
unsigned int      sas_ss_flags;

信号处理函数默认使用用户态的函数栈,当然也可以开辟新的栈专门用于信号处理,这就是 sas_ss_xxx 这三个变量的作用。

任务状态

在task_struct里面,涉及任务状态的是下面几个变量:

 volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
 int exit_state;
 unsigned int flags;

state(状态)可以取的值定义在 include/linux/sched.h 头文件中。


/* Used in tsk->state: */
#define TASK_RUNNING                    0
#define TASK_INTERRUPTIBLE              1
#define TASK_UNINTERRUPTIBLE            2
#define __TASK_STOPPED                  4
#define __TASK_TRACED                   8
/* Used in tsk->exit_state: */
#define EXIT_DEAD                       16
#define EXIT_ZOMBIE                     32
#define EXIT_TRACE                      (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_DEAD                       64
#define TASK_WAKEKILL                   128
#define TASK_WAKING                     256
#define TASK_PARKED                     512
#define TASK_NOLOAD                     1024
#define TASK_NEW                        2048
#define TASK_STATE_MAX                  4096

从定义的数值就可看出来,state是通过bitset的方式设置的,就是说当前的状态是啥,对应的位就置1.

  • TASK_RUNNING 表示进程在时刻准备运行的状态,获得时间片就是在运行中;没有获得时间片,在等待再次分配时间片。

在运行中的进程,一旦要进行一些IO操作,需要等待IO完毕,这个时候会释放CPU进入睡眠状态。

  • TASK_INTERRUPPTIBLE,可中断的睡眠状态 虽然在睡眠,等待 I/O
    完成,但是这个时候一个信号来的时候,进程还是要被唤醒。只不过唤醒后,不是继续刚才的操作,而是进行信号处理。
  • TASK_UNINTERRUPPTIBLE,不可中断的睡眠状态这是一种深度睡眠状态,不可被信号唤醒,只能死等 I/O操作完成。这是一个很危险的东西
  • 补救上面的状态,TASK_KILLABLE,可以终止的新睡眠状态,运行原理和上面相似,只不过可以响应致命响应。是TASK_WAKEKILL (接收致命信号时唤醒进程)和TASK_UNINTERRUPTIBLE的组合
  • TASK_STOPPED 是在进程接收到 SIGSTOP、SIGTTIN、SIGTSTP 或者 SIGTTOU 信号之后进入该状态。
  • TASK_TRACED 表示进程被 debugger 等进程监视,进程执行被调试程序所停止。当一个进程被另外的进程所监视,每一个信号都会让进程进入该状态。
  • 一旦一个进程要结束,先进入的是 EXIT_ZOMBIE 状态,但是这个时候它的父进程还没有使用 wait() 等系统调用来获知它的终止信息,此时进程就成了僵尸进程。
  • EXIT_DEAD 是进程的最终状态。

还有一些常见的宏:

#define PF_EXITING    0x00000004
#define PF_VCPU      0x00000010
#define PF_FORKNOEXEC    0x00000040
进程调度

相关的数据成员:


//是否在运行队列上
int        on_rq;
//优先级
int        prio;
int        static_prio;
int        normal_prio;
unsigned int      rt_priority;
//调度器类
const struct sched_class  *sched_class;
//调度实体
struct sched_entity    se;
struct sched_rt_entity    rt;
struct sched_dl_entity    dl;
//调度策略
unsigned int      policy;
//可以使用哪些CPU
int        nr_cpus_allowed;
cpumask_t      cpus_allowed;
struct sched_info    sched_info;

总结自《趣谈Linux操作系统》

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值