Linux内核学习006——进程管理(二)

Linux内核学习006——进程管理(二)

之前提到在Linux内核中把进程(process)叫做任务(task),因此我会交替使用这两个术语,本质上它们指的是同一个东西。

进程描述符及任务结构

内核中把进程的列表存放在任务队列(一个双向循环链表)中,链表中的的每一项都是类型为task_struct的结构体,叫做进程描述符。该结构体描述了一个进程的所有信息,其定义位于linux-2.6.34/include/linux/sched.h中的1170行。可以在之前安装的虚拟机CentOS6上下载对应源码查看,或者使用在线网站查看:https://elixir.bootlin.com/linux/v2.6.34/source/include/linux/sched.h#L1170

如下所示:

5656674-30c0b724a76d4b0c.png
2019-02-01_201527.png

task_struct结构体比较大,在32位机器上,大概1.7KB。

任务列表大致如下所示:

5656674-258186e4dc16afb9.png
figure_3.1.png

分配进程描述符

内核通过slab分配器分配task_struct结构体,这样可以达到对象复用和缓存着色的目的。

这里的slab分配器是内核分配内存的一种方法,内核内存的分配通常是从空闲的内存池中获取的,主要有两种方式:Buddy系统和slab分配。

slab其实就是一个或多个物理上连续的内存页。每个内核数据结构都有一个高速缓存(cache),比如进程描述符。每个cache都含有一个或多个slab。其关系如下所示:

5656674-ea09afab319acd01.png
2019-02-01_203426.png

在2.6以前的内核中,每个进程的task_struct存放在其内核栈的尾端,这样可以方便类似于x86这样的寄存器较少的硬件体系结构可以通过栈指针直接计算出其位置,而避免使用额外的寄存器记录。在使用slab分配器动态生成task_struct后,只需要在栈底(向下增长的栈)或栈顶(向上增长的栈)创建一个struct thread_info记录即可。

5656674-865b83c10b0ef911.png
figure_3.2.png

对于x86体系,struct thread_info定义在linux-2.6.34/arch/x86/include/asm/thread_info.h:26行。其具体定义如下:

struct thread_info {
        struct task_struct      *task;          /* main task structure */
        struct exec_domain      *exec_domain;   /* execution domain */
        __u32                   flags;          /* low level flags */
        __u32                   status;         /* thread synchronous flags */
        __u32                   cpu;            /* current CPU */
        int                     preempt_count;  /* 0 => preemptable,
                                                   <0 => BUG */
        mm_segment_t            addr_limit;
        struct restart_block    restart_block;
        void __user             *sysenter_return;
#ifdef CONFIG_X86_32
        unsigned long           previous_esp;   /* ESP of the previous stack in
                                                   case of nested (IRQ) stacks
                                                */
        __u8                    supervisor_stack[0];
#endif
        int                     uaccess_err;
};

这样,每个任务的thread_info结构在其内核栈的尾端分配,thread_info中的task指针指向实际task_struct。

进程描述符的存放

内核通过一个唯一的进程标识符PID来标识每个进程,PID是类型位pid_t类型的整数(对于x86而言,就是int类型的别名)。为了与老版本的系统兼容,PID最大值位short int的最大值32768(可以增加到最大400万左右)。pid存放在task_struct结构体中。

这个最大值实际上就是系统中允许同时存在的进程的最大数目,如果需要更大的值可以修改/proc/sys/kernel/pid_max提高。

在内核中,访问任务通常使用指向其task_struct的指针完成的。而实际上,大多数内核代码直接通过处理struct task_struct完成的。因此,能够快速查找当前正在执行的任务的进程描述符是很有用的,这是通过current宏完成的。这个宏的实现必须独立于硬件体系结构:

  • 某些体系结构在寄存器中保存指向当前正在运行的进程的task_struct结构的指针,从而实现高效访问
  • 其他体系结构,例如x86,利用了存储在内核堆栈上的struct thread_info以计算thread_info的位置以及随后的task_struct。

在PowerPC上,当前的task_struct是保存在一个寄存器上的。PowerPC有足够多的寄存器,可以这样加快访问速度。

在x86系统上,current把栈指针的后13个有效位屏蔽,以此来计算thread_info的偏移。 该操作通过current_thread_info()函数完成。该函数位于linux2.6.34//arch/x86/include/asm/thread_info.h#L184

具体代码如下:

/* how to get the thread information struct from C */
static inline struct thread_info *current_thread_info(void)
{
    return (struct thread_info *)
        (current_stack_pointer & ~(THREAD_SIZE - 1));
}

其汇编代码为(这里假设栈大小为8KB,若为4KB,则应用4096代替8192):

movl $-8192, %eax
andl %esp, %eax

之后通过thread_info的task指针就可以获取task_struct的地址了,current_thread_info()->task。

猜你喜欢

转载自blog.csdn.net/weixin_34413065/article/details/86821390