Linux 对进程的描述

系统 Linux
进程是操作系统种调度的实体,对进程拥有资源的描述称为进程控制块(PCB, Process Contrl Block)。

[[406262]]

进程是操作系统种调度的实体,对进程拥有资源的描述称为进程控制块(PCB, Process Contrl Block)。

通过 task_struct 描述进程

内核里,通过 task_struct 结构体来描述一个进程,称为进程描述符 (process descriptor),它保存着支撑一个进程正常运行的所有信息。task_struct 结构体内容太多,这里只列出部分成员变量,感兴趣的读者可以去源码 include/linux/sched.h头文件查看。

struct task_struct { 
 
#ifdef CONFIG_THREAD_INFO_IN_TASK 
  /* 
   * For reasons of header soup (see current_thread_info()), this 
   * must be the first element of task_struct. 
   */ 
  struct thread_info        thread_info; 
#endif 
  volatile long state; 
  void *stack; 
  ...... 
  struct mm_struct *mm; 
  ...... 
  pid_t pid; 
  ...... 
  struct task_struct *parent; 
  ...... 
  char comm[TASK_COMM_LEN]; 
  ...... 
  struct files_struct *files; 
  ...... 
  struct signal_struct *signal; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

task_struct 中的主要信息分类:

1.标示符:描述本进程的唯一标识符 pid,用来区别其他进程。

2.状态:任务状态,退出代码,退出信号等

3.优先级:相对于其他进程的优先级

4.程序计数器:程序中即将被执行的下一条指令的地址

5.内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

6.上下文数据:进程执行时处理器的寄存器中的数据

7.I/O状态信息:包括显示的I/O请求,分配的进程I/O设备和进程使用的文件列表

8.记账信息:可能包括处理器时间总和,使用的时钟总和,时间限制,记帐号等

  • struct thread_info thread_info: 进程被调度执行的信息
  • volatile long state:-1是不运行的,=0是运行状态,>0是停止状态。下面是几个比较重要的进程状态以及它们之间的转换流程。

  1. void *stack:指向内核栈的指针,内核通过 dup_task_struct 为每个进程都分配内核栈空间,并记录在此。
  2. struct mm_struct *mm: 与进程地址空间相关的信息。

  • pid_t pid: 进程标识符
  • char comm[TASK_COMM_LEN]: 进程的名称
  • struct files_struct *files: 打开的文件表
  • struct signal_struct *signal: 信号处理相关

task_struct, thread_info 和内核栈 sp 的关系

接着看下 thread_info 结构:

struct thread_info { 
        unsigned long           flags;          /* low level flags */ 
        mm_segment_t            addr_limit;     /* address limit */ 
#ifdef CONFIG_ARM64_SW_TTBR0_PAN 
        u64                     ttbr0;          /* saved TTBR0_EL1 */ 
#endif 
        union { 
                u64             preempt_count;  /* 0 => preemptible, <0 => bug */ 
                struct { 
#ifdef CONFIG_CPU_BIG_ENDIAN 
                        u32     need_resched; 
                        u32     count
#else 
                        u32     count
                        u32     need_resched; 
#endif 
                } preempt; 
        }; 
#ifdef CONFIG_SHADOW_CALL_STACK 
        void                    *scs_base; 
        void                    *scs_sp; 
#endif 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

接着再来看下内核栈的定义:

union thread_union { 
#ifndef CONFIG_ARCH_TASK_STRUCT_ON_STACK 
        struct task_struct task; 
#endif 
#ifndef CONFIG_THREAD_INFO_IN_TASK 
        struct thread_info thread_info; 
#endif 
        unsigned long stack[THREAD_SIZE/sizeof(long)]; 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

当 CONFIG_THREAD_INFO_IN_TASK 这个配置打开的时候,则 thread_union 结构中只存在 stask 成员了。

内核在启动的时候会在 head.S 里通过 __primary_switched 来做内核栈的初始化:

SYM_FUNC_START_LOCAL(__primary_switched) 
        adrp    x4, init_thread_union 
        add     sp, x4, #THREAD_SIZE 
        adr_l   x5, init_task 
        msr     sp_el0, x5                      // Save thread_info 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

将 init_thread_union 的地址保存到 x4,然后偏移 THREAD_SIZE 栈大小,用于初始化 sp。将 init_task 进程描述符地址赋值给 x5,并保存到 sp_el0。

下面再看下 init_thread_union 和 init_task 的定义:

#include/linux/sched/task.h 
extern union thread_union init_thread_union; 
 
#init/init_task.c 
struct task_struct init_task 
        __aligned(L1_CACHE_BYTES) 
= { 
#ifdef CONFIG_THREAD_INFO_IN_TASK 
        .thread_info    = INIT_THREAD_INFO(init_task), 
        .stack_refcount = REFCOUNT_INIT(1), 
#endif 
..... 
 }; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

故这三者的关系可以通过下图描述:

如何获取当前进程

内核中经常通过 current 宏来获得当前进程对应的 struct task_sturct 结构,我们借助 current,结合上面介绍的内容,看下具体的实现。

static __always_inline struct task_struct *get_current(void) 

    unsigned long sp_el0; 
  
    asm ("mrs %0, sp_el0" : "=r" (sp_el0)); 
  
    return (struct task_struct *)sp_el0; 

  
#define current get_current() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

代码比较简单,可以看出通过读取用户空间栈指针寄存器 sp_el0 的值,然后将此值强转成 task_struct 结构就可以获得当前进程。(sp_el0 里存放的是 init_task,即 thread_info 地址,thread_info 又是在 task_sturct 的开始处,从而找到当前进程。)

 

责任编辑:武晓燕 来源: 人人都是极客
相关推荐

2023-04-06 15:22:15

Linux进程系统

2009-12-17 14:43:47

红旗Linux

2020-02-07 18:16:01

进程线程底层原理

2010-05-26 15:03:51

2010-09-15 09:58:20

2009-11-13 09:28:35

2010-07-19 18:04:23

SQL Server锁

2009-08-06 15:52:11

C#调用外部进程

2010-09-30 16:17:13

2010-09-08 15:10:48

2010-05-28 11:14:39

MySQL全文索引限制

2010-04-27 16:18:26

Oracle数据字典

2010-09-08 13:31:24

2010-07-19 16:17:41

SQL Server聚

2010-05-26 13:50:15

MySQL备份

2010-09-29 12:52:14

2010-09-26 16:25:17

网络钓鱼成功

2010-03-29 13:55:26

Oracle Mult

2011-03-30 11:28:31

SQL Server聚集索引

2019-08-02 09:53:38

LinuxC语言操作系统
点赞
收藏

51CTO技术栈公众号