Linux源码-进程描述符

Linux操作系统引入了PCB(Process Control Block,进程控制块)结构。PCB是Linux操作系统识别进程的通道。

创建进程时,首先会创建PCB,根据PCB中的信息对进程实施有效管理。当进程终止后,Linux操作系统会释放对应的PCB资源。

PCB的数据结构是struct task_struct。包含四个方面,描述信息、控制信息、CPU上下文和资源管理。

task_struct位于源码<include/linux/sched.h>

首行的代码注释如下,sched.h用于定义task_struct结构体并提供主要的调度API,比如schedule(), wakeup等

/*

 * Define 'struct task_struct' and provide the main scheduler

 * APIs (schedule(), wakeup variants, etc.)

 */

task_struct总计有约700行代码。

1 描述信息

1.1 进程描述符

每个进程都有唯一的进程标识符pid。是一个32位正整型数。即单个系统内pid的上限为2的31次方*(2147483648),约21亿个PID

struct task_struct{

    ...(796行)

    pid_t               pid;  //全局进程号

    pid_t               tgid; //全局线程组标识符

    ...

}

1.2 用户标识符

每个进程都属于系统中的某一个用户,因此在数据结构体中会包含用户uid信息,变量为kuid_t

struct task_struct{

    ...(940行)

    kuid_t              loginuid;

    unsigned int            sessionid;

    ...

}

kuid_t在<include/linux/uidgid.h>中有定义,为val变量,是一个无符号整数,代表用户标识号。

typedef struct {

    uid_t val;

} kuid_t;

1.3 家族关系

进程并不会独立存在,互相之间存在依赖性,会形成依赖关系,主要为父子关系。除了0号进程之外,其他进程都有父进程。

父进程的父进程叫做祖先进程(隔代),同一个父进程中的多个子进程之间构成兄弟关系(同代)。

Linux系统启动时,0号进程进行内核初始化工作,并创建出1号进程。

1号进程完成用户空间初始化后成为init进程,为后续该系统内创建进程的共同祖先进程。

如下代码中real_parent和parent在正常情况下都指向父进程pid,但是在特殊情况下,比如因为程序缺陷,父进程在子进程结束前自行退出了,子进程就变成了孤儿进程,也就是僵尸进程,此时,该子进程就会被挂到init 1号进程下。这是real_parent不会变化,但是parent变成了1。

sibling用于指向兄弟进程。

children用于指向子进程。

struct task_struct{

    ...(809行)

    /*

     * Pointers to the (original) parent process, youngest child, younger sibling,

     * older sibling, respectively.  (p->father can be replaced with

     * p->real_parent->pid)

     */

    /* Real parent process: */

    struct task_struct __rcu    *real_parent; //指向真实的父进程

    /* Recipient of SIGCHLD, wait4() reports: */

    struct task_struct __rcu    *parent; //指向父进程

    /*

     * Children/sibling form the list of natural children:

     */

    struct list_head        children;  //指向子进程

    struct list_head        sibling;   //指向兄弟进程

    struct task_struct      *group_leader;  //通过进程描述符,指向线程组的组长

2控制信息

PCB中进程控制信息主要包括进程的状态信息、优先级信息和记账信息。

2.1 状态信息

进程的状态有就绪、运行、阻塞、终止等状态,其中在终止状态中又分为了僵尸和死亡两种状态。

内核代码中对进程状态有如下几种类型定义:

<include/linux/sched.h>

....(82行)

/* Used in tsk->state: */

#define TASK_RUNNING            0x0000 //运行状态,int 0

#define TASK_INTERRUPTIBLE      0x0001 //阻塞状态(可中断) int 1

#define TASK_UNINTERRUPTIBLE        0x0002 //阻塞状态(不可中断) int 2

#define __TASK_STOPPED          0x0004 //终止状态 int 4

#define __TASK_TRACED           0x0008 // 跟踪状态 int 8

/* Used in tsk->exit_state: */

#define EXIT_DEAD           0x0010 //终止状态中的死亡状态,属于exit_state

#define EXIT_ZOMBIE         0x0020//终止状态中的僵尸状态,属于exit_state

#define EXIT_TRACE          (EXIT_ZOMBIE | EXIT_DEAD)

在操作系统上可以看到的进程的状态描述:

static const char * const task_state_array[] = {

    /* states in TASK_REPORT: */

    "R (running)",      /* 0x00 */

    "S (sleeping)",     /* 0x01 */

    "D (disk sleep)",   /* 0x02 */

    "T (stopped)",      /* 0x04 */

    "t (tracing stop)", /* 0x08 */

    "X (dead)",     /* 0x10 */

    "Z (zombie)",       /* 0x20 */

    "P (parked)",       /* 0x40 */

    /* states beyond TASK_REPORT: */

    "I (idle)",     /* 0x80 */

};

定义一个宏TASK_REPORT, 将所有进程的状态进行合并。

/* get_task_state(): */

#define TASK_REPORT         (TASK_RUNNING | TASK_INTERRUPTIBLE | \

                     TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \

                     __TASK_TRACED | EXIT_DEAD | EXIT_ZOMBIE | \

                     TASK_PARKED)

task_state_index为内联函数,用于计算给定任务结构体task_struct的状态索引,明确一个进程的具体状态。

根据给定任务的运行状态 (tsk->state) 和退出状态 (tsk->exit_state),计算出一个状态索引。通过按位操作和掩码 (TASK_REPORT),函数能够准确地表示任务的当前状态,并返回一个索引值,用于进一步的处理或决策。

static inline unsigned int task_state_index(struct task_struct *tsk)

{

    unsigned int tsk_state = READ_ONCE(tsk->state);

    unsigned int state = (tsk_state | tsk->exit_state) & TASK_REPORT;

    BUILD_BUG_ON_NOT_POWER_OF_2(TASK_REPORT_MAX);

    if (tsk_state == TASK_IDLE)

        state = TASK_REPORT_IDLE;

    return fls(state);

}

2.2 进程优先级信息

进程优先级用于确定进程被调度到CPU上执行的优先程度如内核代码定义分为动态优先级、静态优先级、普通优先级和试试优先级

  struct task_struct {

......(732行)

    int             prio;         //动态优先级

    int             static_prio;  //静态优先级

    int             normal_prio;  //普通优先级,取决于静态优先级和调度策略

    unsigned int            rt_priority; //realtime,实时优先级

静态优先级:数值越小,优先级越高,可用nice调整

动态优先级和普通优先级默认等于静态优先级,但动态优先级会被临时修改。同样是数值越小,优先级越高。

进程可分为实时进程和普通进程,实时优先级作用于实时进程,数值越大,优先级越高。

实时进程的调度优先于普通进程。

3 CPU上下文

CPU上下文是指进程执行到某时刻时CPU各寄存器中的数值,这些数值就代表着当前进程活动的状态信息。

什么是上下文切换?

在单CPU多进程并发的场景下,单位时间内一个CPU同一时刻只会执行一个进程的代码,比如在1秒中内,CPU的时间周期被切割成100个分片,同时有10个进程需要执行,且每个进程之间的优先级都是相同的情况下,每个进程被分配到的时间就在10个时间分片,同时这10个时间分片不一定是连续的。因此在CPU进行两个进程处理切换期间,需要保留上一个进程在CPU内寄存器的数值,同时要加载下一个进程在上一个时间片内执行的寄存器数值。这个过程叫做上下文切换。

进程切换时,CPU上下文保存调用为task_struct --> thread_struct --> cpu_context

结构体 thread_struct,用于存储特定于进程的状态信息,特别是在处理器上下文、浮点寄存器状态和调试信息方面的详细数据。以下是结构体的主要成员及其作用的解释:

<arch/arm64/include/asm/processor.h>

...

struct thread_struct {

    struct cpu_context  cpu_context;    /* cpu context */

...

    unsigned long       fault_address;  /* fault info */

    unsigned long       fault_code; /* 寄存器ESR_EL1 value */

    struct debug_info   debug;      /* debugging */

    ...

    }

对应引用cpu_context结构体,主要代码如下,包含了通用寄存器x19--x28, 栈帧寄存器FP,堆栈指针寄存器SP和程序计数器PC

<arch/arm64/include/asm/processor.h>

struct cpu_context {

    unsigned long x19;

    unsigned long x20;

    unsigned long x21;

    unsigned long x22;

    unsigned long x23;

    unsigned long x24;

    unsigned long x25;

    unsigned long x26;

    unsigned long x27;

    unsigned long x28;

    unsigned long fp;

    unsigned long sp;

    unsigned long pc;

};

4 资源管理信息

PCB中包含资源管理信息,包含存储器、文件系统等等,多个代码调用关系如下

在sched.h中定义了对应的内存、文件、打开文件的结构体。

    <include/linux/sched.h>

    ...

    void *stack; //指向进程的内核栈

    ...

    struct mm_struct        *mm;

    struct mm_struct        *active_mm; //进程的用户控件描述符

    struct fs_struct        *fs;  //进程相关联的文件系统信息

    struct files_struct     *files;  //指向打开的文件列表

4.1 mm_types.h

mm_struct结构体记录了进程的内存布局

<include/linux/mm_types.h>

struct mm_struct {  //内存描述符

    struct {

        struct vm_area_struct *mmap;        /* list of VMAs */

        struct rb_root mm_rb;

        ...

        spinlock_t arg_lock; //自旋锁,保护如下字段

        //内存空间中各段起始、结束地址

         //包括堆、栈 、映射段、BSS段、代码段、数据段等等

        unsigned long start_code, end_code, start_data, end_data;       

        unsigned long start_brk, brk, start_stack;

        unsigned long arg_start, arg_end, env_start, env_end;

        ...

4.2 fs_struct.h

fs_struct结构体记录与进程相关联的文件系统信息,包括当前目录和根目录。

<include/linux/fs_struct.h>

struct fs_struct {

    int users;

    spinlock_t lock;  //自旋锁

    seqcount_spinlock_t seq;

    int umask;

    int in_exec;

    //根目录与当前目录

    struct path root, pwd;

} __randomize_layout;

通过t内联函数get_fs_roo获取根目录信息

通过内联函数get_fs_pwd获取当前目录信息

//通过get_fs_root内联函数获取根目录信息

static inline void get_fs_root(struct fs_struct *fs, struct path *root)

{

    spin_lock(&fs->lock);

    *root = fs->root;

    path_get(root);

    spin_unlock(&fs->lock);

}

//通过内联函数get_fs_pwd获取当前目录信息

static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd)

{

    spin_lock(&fs->lock);

    *pwd = fs->pwd;

    path_get(pwd);

    spin_unlock(&fs->lock);

}

4.3 fdtable.h

files_struct结构是进程正打开的所有文件的列表(包含输入/输出设备也是以文件形式存在的)

<include/linux/fdtable.h>

struct files_struct {

    atomic_t count;   //引用计数器

    bool resize_in_progress;

    wait_queue_head_t resize_wait;

    struct fdtable __rcu *fdt; //默认指向fdtab,用于动态申请内存

    struct fdtable fdtab;  //为fdt提供初始值

    spinlock_t file_lock ____cacheline_aligned_in_smp;

    unsigned int next_fd;

    unsigned long close_on_exec_init[1];

    unsigned long open_fds_init[1];

    unsigned long full_fds_bits_init[1];

    struct file __rcu * fd_array[NR_OPEN_DEFAULT];

    struct files_cgroup *files_cgroup;

};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值