Linux进程管理

Linux进程管理(一)进程数据结构

Linux进程管理(二)进程调度

Linux进程管理(三)进程调度之主动调度

Linux进程管理(四)进程调度之抢占式调度

Linux进程管理(一)进程数据结构

文章目录

  • Linux进程管理(一)进程数据结构
    • 双向链表
    • 任务ID
    • 信号处理
    • 进程状态
    • 进程调度
    • 运行统计信息
    • 进程亲缘关系
    • 内存管理
    • 文件与文件系统
    • 进程内核栈
      • 栈结构
      • current宏

Linux内核中使用 task_struct 结构来表示一个进程,这个结构体保存了进程的所有信息,所以它非常庞大,在讲解Linux内核的进程管理,我们有必要先分析这个 task_struct 中的各项成员

双向链表

struct list_head tasks;

Linux将所有的 task_struct 串联成一个双向循环链表

任务ID

pid_t pid;
pid_t tgid;struct task_struct *group_leader;
  • pid:每个进程都有自己的 pid,它在内核中是唯一的,在Linux中,我们可以使用 ps -ef查看所有的进程,其中 PID 就是进程号。pid可以给用户查看指定进程的信息,可以通过pid给指定的进程发送信号
  • tgid:tigd 是 thread group ID,表示线程组id。thread group 是线程组的意思,所谓的线程组是什么意思呢?内核中不管是线程或者是进程都是使用 task_struct 来表示,一个进程也可以称为主线程,由它创建多个线程,这些线程和进程的主线程就称为一个线程组。每个线程都有自己的pid,而 tgid 则等于进程的主线程的 pid,这样也就可以区分谁是主线程,谁是被主线程创建出来的
  • group_leader:指向线程组主线程的进程描述符

通过 getpid 返回的是 tgid,也就是说同一个线程组共享一个 pid

信号处理


/* 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;
  • blocked:sigset_t 是一个位图,每个位都表示一个信号。blocked 表示的是该进程的哪些信号被阻塞暂不处理
  • pending:表示进程接收到了哪些信号,需要被处理
  • sighand:用户可以定义相应信号的处理方法,其信息保存在这里
  • sas_ss_xxx:信号的处理默认使用的是进程用户空间的函数栈,也可以开辟新的栈专门用于信号处理,这三个变量就是用户维护这个栈信息
    在 signal 中,定义了 struct sigpending shared_pending,这个 shared_pending 和 pending 的区别是,pending 表示该 task_struct 收到的信号,而 shared_pending 是整个线程组共享的。也就是说,对于 pending 中接收到的信号,只能由这个 task_struct 来处理,而 shared_pending 中收到的信号,可以由线程组中的任意一个线程处理

进程状态

在 task_struct 中,定义了这样几个变量,与进程的状态有关

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

state 和 exit_state 的定义如下

/* 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)

state相关

  • TASK_RUNNING:运行态或就绪态,表示进程正在运行,或者进程处于就绪态位于运行队列中
  • TASK_INTERRUPTIBLE:可中断的睡眠态,表示进程正在睡眠等待,睡眠过程中可以被信号唤醒
  • TASK_UNINTERRUPTIBLE:不可中断的睡眠态,表示进程正在睡眠等待,并且睡眠过程中不可被信号唤醒。这就意味着,如果有一个进程一直处于这种状态,我们无法使用信号将其杀死,唯一的办法就是重启,所以这个状态较少使用
  • __TASK_STOPPED:在进程收到 SIGSTOP、SIGTTIN、SIGTSTP 或者 SIGTTO 等信号的时候,进入该状态
  • __TASK_TRACED:进程被另一个进程跟踪的时候,进入此状态

exit_state相关

  • EXIT_ZOMBIE:僵尸态,如果一个进程已经死亡,但在内核中的 task_struct 还未被父进程回收,那么进程就会变成僵尸进程
  • EXIT_DEAD:最终态,进程的资源被父进程回收后,会从 EXIT_ZOMBIE 变成 EXIT_DEAD

进程的状态转换如下

flags的某些定义如下

#define PF_EXITING    0x00000004
#define PF_VCPU      0x00000010
#define PF_FORKNOEXEC    0x00000040
  • PF_EXITING:表示正在退出
  • PF_VCPU:表示运行在虚拟CPU上
  • PF_FORKNOEXEC:表示 fork 完,还没有调用 exec

进程调度

进程调度相关的变量如下


//是否在运行队列上
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;
  • on_rq:表明进程是否在运行队列上
  • prio、static_prio、normal_prio、rt_priority:优先级相关的变量
  • sched_class:调度类,也就是这个进程采用的调度策略
  • se、rt、dl:调度实体,调度类操作的单位
  • policy:调度策略,与 sched_class 对应
  • nr_cpus_allowed、cpus_allowed:表明进程可以在哪些CPU上运行

运行统计信息

u64        utime;//用户态消耗的CPU时间
u64        stime;//内核态消耗的CPU时间
unsigned long      nvcsw;//自愿(voluntary)上下文切换计数
unsigned long      nivcsw;//非自愿(involuntary)上下文切换计数
u64        start_time;//进程启动时间,不包含睡眠时间
u64        real_start_time;//进程启动时间,包含睡眠时间

进程亲缘关系

struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
struct list_head children;      /* list of my children */
struct list_head sibling;       /* linkage in my parent's children list */

进程之间有亲缘关系,所以所有的进程实际上是一棵树,上面说过,进程组成一个双向循环链表,这并不冲突,因为既是双向循环链表,又是一棵树

  • parent:指向父进程
  • children:所有子进程的组成的链表的链表头
  • sibling:兄弟链表,又相当于父进程的 children 链表中的一个节点

所有进程组成的关系如下

real_parent 和 parent 在大多数情况下是一样的,只有在某些特殊情况下才会不一样

内存管理

struct mm_struct                *mm;
struct mm_struct                *active_mm;

每个进程都有自己独立的地址空间,内核使用了 mm_struct 结构体来管理进程的地址空间

文件与文件系统

/* Filesystem information: */
struct fs_struct                *fs;
/* Open file information: */
struct files_struct             *files;

每个进程都有一个文件系统的数据结构,就是 fs_struct

每个进程还要维护它所打开的文件,这些信息在 files_struct 中

进程内核栈

栈结构

上面讲解了 task_struct 的大部分成员,下面来讲解进程的内核栈

程序的运行需要使用到栈,所以不管进程是在内核态运行还是在用户态运行都需要用到栈

Linux将进程地址空间分为内核空间和用户空间,它们之间是不能直接访问的,而一个进程某些时候可能在用户态运行,某些时候可能在内核态运行(发生系统调用时),所以一个进程既需要用户栈又需要内核栈

下面就来讲解内核给进程分配的栈结构

在 task_struct 中,有一个变量指向该进程的内核栈,如下

struct task_struct {...void *stack;...
};

内核栈的大小在内核中的定义如下

#define THREAD_SIZE_ORDER  1
#define THREAD_SIZE    (PAGE_SIZE << THREAD_SIZE_ORDER)

一个 PAGE_SIZE 是4K,左移一位就是乘以2,所以 THREAD_SIZE 就是8K,所以大体j结构如下

接下来我们看这8K的空间的结构分布

在这段空间的最底部,存放着一个 struct thread_info 结构体,何以证明呢?

在Linux中有一个 union thread_union 共用体,其定义如下

union thread_union {#ifndef CONFIG_THREAD_INFO_IN_TASKstruct thread_info thread_info;
#endifunsigned long stack[THREAD_SIZE/sizeof(long)];
};

其中的 stack 表示栈空间,大小为 THREAD_SIZE 个字节

union 表示是一个共用体,可以看出,thread_info 在位于这个栈的最底部,如下图所示

Linux中发生系统调用时,会从用户态变成内核态,然后执行内核代码,当内核代码执行完之后,又会回到用户态执行用户代码

在进程从用户态变成内核态的时候,内核需要将用户态运行时寄存器的值保存下来,然后修改寄存器,当内核代码执行完之后,又将寄存器的值恢复,这些寄存器的值保存在哪里呢?

在内核栈的最高端,存放着一个 pt_regs 结构,这个结构包含了相关寄存器的定义,用户态寄存器的值就保存在这里,对于X86 32 位其定义如下

struct pt_regs {unsigned long bx;unsigned long cx;unsigned long dx;unsigned long si;unsigned long di;unsigned long bp;unsigned long ax;unsigned long ds;unsigned long es;unsigned long fs;unsigned long gs;unsigned long orig_ax;unsigned long ip;unsigned long cs;unsigned long flags;unsigned long sp;unsigned long ss;
};

此外剩余的空间才是用作函数栈,栈是向下生长的,所以进程的内核栈就变成下面这个样子

接下来看 thread_info 的定义,如下

struct thread_info {struct task_struct  *task;    /* main task structure */__u32      flags;    /* low level flags */__u32      status;    /* thread synchronous flags */__u32      cpu;    /* current CPU */mm_segment_t    addr_limit;unsigned int    sig_on_uaccess_error:1;unsigned int    uaccess_err:1;  /* uaccess failed */
};

thread_info 中有一个变量 task_struct,指向拥有这个内核栈的进程,如下所示

current宏

Linux内核中可以通过 current 宏来获取当前正在运行的进程,它的实现十分巧妙,下面我们一起来看一看

#define get_current() (current_thread_info()->task)
#define current get_current()

current 通过 get_current(),进而调用 current_thread_info()->task

我们看一看 current_thread_info 的定义

static inline struct thread_info *current_thread_info(void)
{return (struct thread_info *)(current_stack_pointer & ~(THREAD_SIZE - 1));
}

current_stack_pointer 表示当前栈顶寄存器的值,对于 X86 就是 esp,在内核态的时候,current_stack_pointer 表示内核栈中的某一个位置

THREAD_SIZE 我们上面说过是8K,THREAD_SIZE - 1 就是8K剩下的所有位,如下

那么这个操作是什么意思呢?

(current_stack_pointer & ~(THREAD_SIZE - 1)

意思就是将 current_stack_pointer 的低12位清空

我们从这个 current_thread_info 函数可以看出,通过这个操作就可以获得 thread_info 对象,这是为什么呢?

这是因为,内核栈在申请的时候,总是 8K 对齐的,也就是说地址的低12位肯定为0

当进程在内核态运行的时候,栈顶指针总是指向这块申请的内核栈中的某一个区域,内核栈的大小最大也就8K,所以将当前栈顶指针的低12位置零就可以得到内核栈的基址

而 thread_info 存在于内核栈的栈底处,所以也就获取到了该进程对应的 thread_info 结构

thread_info 结构中有一个 task_struct* 成员,指向该进程的 task_struct,所以也就可以获得该进程的 task_struct 结构

不禁感叹,Linux内核的实现真是巧妙啊

好了,关于Linux进程的数据结构就介绍到这里了,后面的文章将讲解Linux的进程调度

Linux进程管理(一)进程数据结构相关推荐

  1. linux ps 进程组,linux进程管理(2)---进程的组织结构

    一.目的 linux为了不同的进程管理目的,使用了不同的方法组织进程之间的关系,为了体现父子关系,使用了"树形"图:为了对同一信号量统一处理,使用了进程组:为了快速查找某个进程,使 ...

  2. Linux内核进程管理:进程的“内核栈”、current宏、进程描述符

    目录 linux 进程内核栈 概念 thread_info 有什么用? thread_info .内核栈.task_struct 关联 current 宏 1.arm 2.ARM64 3.x86 SY ...

  3. Linux进程管理:进程和线程基础知识

    <Linux进程管理:进程和线程基础知识> <Linux-进程管理> <C语言进程的内存地址空间分配> <进程和线程模型> <(1)Linux进程 ...

  4. Linux 进程管理之进程的终结

    当一个进程终结时,内核必须释放掉它所占有的资源并把这一终结事件告知父进程. 进程的终结大部分都要靠 exit() 来完成的,最终的系统调用为 do_exit(). asmlinkage long sy ...

  5. [转]QNX进程管理器-进程调度策略

    如果你认为本系列文章对你有所帮助,请大家有钱的捧个钱场,点击此处赞助,赞助额0.1元起步,多少随意 声明:本文只用于个人学习交流,若不慎造成侵权,请及时联系我,立即予以改正 锋影 email:1741 ...

  6. 【k8s】理解Docker容器的进程管理(PID1进程(容器内kill命令无法杀死)、进程信号处理、僵尸进程)

    文章目录 概述 1. 容器的PID namespace(名空间) 2. 如何指明容器PID1进程 3. 进程信号处理 4. 孤儿进程与僵尸进程管理 5. 进程监控 6. 总结 参考 概述 简介: Do ...

  7. 广州大学2020操作系统实验一:进程管理与进程通信

    相关资料 广州大学2020操作系统实验一:进程管理与进程通信 广州大学2020操作系统实验二:银行家算法 广州大学2020操作系统实验三:内存管理 广州大学2020操作系统实验四:文件系统 广州大学2 ...

  8. Linux 内核进程管理之进程ID

    Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是围绕该数据结构建立的,是内核中最重要的数据结构之一.该数据结构 ...

  9. linux进程管理之进程创建

    所谓进程就是程序执行时的一个实例. 它是现代操作系统中一个很重要的抽象,我们从进程的生命周期:创建,执行,消亡来分析一下Linux上的进程管理实现. 一:前言 进程管理结构; 在内核中,每一个进程对应 ...

  10. 【Linux 内核】进程管理 ( 进程状态 | 进程创建 | 进程终止 | 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 )

    文章目录 一.进程状态 二.进程创建 三.进程终止 ( 调用 exit 系统调用函数主动退出 | main 函数返回自动退出 | kill 杀死进程 | 执行异常退出 ) 一.进程状态 Linux 进 ...

最新文章

  1. 安全研究人员发现:Nanocore等多个远控木马滥用公有云服务传播
  2. win10安装misql8_Win10下免安装版MySQL8.0.16的安装和配置教程图解
  3. 百练OJ:3681与2796:数字求和
  4. Linux centosVMware zip压缩工具、tar打包、打包并压缩
  5. 开源,免费和跨平台 - MVP ComCamp 2015 KEYNOTE
  6. css3自适应布局单位vw,vh
  7. 修改字段类型_PostgreSQL 关于字段类型的修改 谣言与止谣
  8. 【IPM2020】一种处理多标签文本分类的新颖推理机制
  9. systemd和sysv服务管理和配置
  10. OpenCV通过cvFindContours与cvDrawCountours函数查找轮廓
  11. Ubuntu 16.04 安装QQ解决方案
  12. Android面试之J2SE基础
  13. Apache POI Excel固定(冻结)单元格
  14. PHP微信公众号文章编辑排版工具源码+采集功能/附教程
  15. 在校园网中进行无线路由器设置
  16. 做跨境电商的Anker的也回来“内卷”了?
  17. 大数据时代,我们需要“被遗忘权”(转)
  18. 云安全软件市场现状研究分析报告 -
  19. 写给情人,写给情人节,写给即将开始的新一年
  20. linux里进程状态为sl,Linux ps state sl+是什么意思

热门文章

  1. 京汉远程会诊背后的黑科技
  2. 4月26日,每日互动(个推)与您相约第六届数字中国建设峰会
  3. Golang 生成压缩包
  4. 【Java】pageHelper实现分页
  5. 备战托福有诀窍:征服阅读必备的能力
  6. 使用cadence仿真MOS管跨导gm
  7. 已知三点求圆心与半径
  8. JavaAPI第二章
  9. 台式计算机i7,I7台式计算机硬件配置
  10. 华为云核心java_java核心