Linux内核分析(六)

Linux内核分析——【实验六:进程的描述与创建】

一 进程的概念
进程是程序执行的一个实例,它是最小的系统资源分配基本单元,在Linux内核代码中,常把进程称为任务(task)或线程(thread)。当然,一个进程可以包含多个线程,线程是系统调度最小的基本单元。

二 进程描述符
对于多任务处理的操作系统而言,通常系统上都会运行着多个进程(任务),为了管理进程,系统必须对每个进程所做的事情进行详细地描述。在window系统中,进程控制块(PCB)就是用来记录进程的相关信息,每一个进程都有一个进程控制块,它记录了pid,进程状态,堆栈大小,进程优先级,进程存储信息,pc等等。在linux系统中,也有一个类似的数据结构——进程描述符,它包含了进程的所有信息,进程描述符是由内核定义的一个task_struct类型数据结构,它在/include/linux/sched.h文件中定义,它的内容非常复杂,由四百多行的c代码组成,下面尽列出少量的代码:

struct task_struct {
    //进程状态
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    //进程内核堆栈,包括:thread_info和stack
    void *stack;
    //进程标志符
    unsigned int flags; /* per process flags, defined below */
    //优先级
    int prio, static_prio, normal_prio;
    unsigned int rt_priority;
    //进程链表,双向链表
    struct list_head tasks;
    //内存管理
    struct mm_struct *mm, *active_mm;
    //进程标识符
    pid_t pid;
    //线程组标识符
    pid_t tgid;
    //金丝雀,用于防止栈溢出
    unsigned long stack_canary;/* Canary value for the -fstack-protector gcc feature */
    //创建该进程的父进程
    struct task_struct __rcu *real_parent; /* real parent process */
    //当前父进程
    struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
    /* * children/sibling forms the list of my natural children */
     //子进程,是一个链表
    struct list_head children;  /* list of my children */
    //兄弟进程
    struct list_head sibling;   /* linkage in my parent's children list */
    //该进程中cpu相关寄存器信息
    struct thread_struct thread;/* CPU-specific state of this task */
    //文件系统,相关目录
    struct fs_struct *fs;/* filesystem information */
    //打开的文件信息--文件描述符
    struct files_struct *files;/* open file information */
    //相关信号
    struct signal_struct *signal;
    //信号处理
    struct sighand_struct *sighand;
    ... ...
};

1.进程状态
(1)TASK_RUNNING(可运行状态)
进程是可执行的;它或者正在执行,或者在运行队列中等待执行。
(2)TASK_INTERRUPTIBLE(可中断等待状态)
进程正在睡眠(被阻塞),等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒并随时准备投入运行。
(3)TASK_UNINTERRUPTIBLE(不可中断等待状态)
除了就算是接收到信号也不会被唤醒或准备投入运行外,这个状态与可打断状态相同。这个状态通常在进程必须在等待时不受干扰或等待事件很快就会发生时出现。由于处于此状态的任务对信号不做响应,
(4)TASK_TRACED(跟踪状态)
被其他进程跟踪的进程,例如通过ptrace对调试程序进行跟踪。
(5)TASK_STOPPED(停止状态)
进程停止执行;进程没有投入运行也不能投入运行。通常这种状态发生在接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号的时候。此外,在调试期间接收到任何信号,都会使进程进入这种状态。

2.进程描述符thread_info和内核栈
对于每个进程来说,系统都会为这两个数据结构分配8k的内存空间,他们与进程描述符关系如下:
Linux内核分析(六)_第1张图片
在这个图中,esp寄存器是CPU栈指针,用来存放栈顶单元的地址。在80x86系统中,栈起始于顶端,并朝着这个内存区开始的方向增长。从用户态刚切换到内核态以后,进程的内核栈总是空的。因此,esp寄存器指向这个栈的顶端。
一旦数据写入堆栈,esp的值就递减。在Linux3.5.4内核中,thread_info结构是72个字节长,因此内核栈能扩展到8120个字节。thread_info结构的定义如下:

Linux内核中使用一个联合体来表示一个进程的线程描述符和内核栈:

union thread_union {
    struct thread_info thread_info;
    unsigned long stack[2048];
};

通过esp栈指针很容易获取当前在CPU上正在运行进程的thread_info结构。实际上,thread_info结构和内核态堆栈是紧密结合在一起的,占据两个页框的物理内存空间。而且,这两个页框的起始起始地址是2的13次幂对齐的。所以,内核通过简单的屏蔽掉esp的低13位有效位就可以获得thread_info结构的基地址了。

3.进程间关系

4.进程链表
进程链表把所有进程的描述符连接起来。
Linux内核分析(六)_第2张图片

三 进程创建
在linux中,fork(),vfork(),clone都可以创建一个新进程,并且它们都是通过do_fork()函数(文件路径:linux-3.18.6/kernel/fork.c)实现的,如下图:
Linux内核分析(六)_第3张图片

下面,将进行实验,对fork()函数创建新进程的过程进行跟踪分析:
1.实验环境
与实验三相同,只不过把test.c文件换为test_fork.c文件,这里不再赘诉。
Linux内核分析(六)_第4张图片
2.调试并设置断点
(1)在gdb终端:

(gdb) b sys_clone
(gdb) c

(2)在QEMU模拟器中输入

MemuOS>>fork

(3)在gdb终端设置断点,如下
Linux内核分析(六)_第5张图片
3.跟踪执行过程
(1) 执行

    (gdb) c   

Linux内核分析(六)_第6张图片
(2)继续
Linux内核分析(六)_第7张图片
(3)继续

(4)继续

由此可见,fork()的执行过程大致可以描述为
Linux内核分析(六)_第8张图片
当然,上图中忽略了很多细节的东西,只是一个粗略的过程。

一个新的进程创建的时候,从用户态来看,fork语句的下一条语句即为子进程执行的开始;从内核态看,子进程的执行从ret_from_fork(文件路径:/arch/x86/kernel/entry_32.S)处开始。

附录
(1)函数arch_dup_task_struct

(2)数据结构struct pt_regs

(3)数据结构struct thread_info
Linux内核分析(六)_第9张图片
(4)数据结构struct thread_struct
Linux内核分析(六)_第10张图片

=========== 王杰 原创作品转载请注明出处==============
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

你可能感兴趣的:(linux,kernel,进程,fork,进程描述符)