进程管理(二)

进程描述符中包含的数据能完整地描述一个正在执行的程序:他打开的文件,进程的地址空间,挂起的信号,进程的状态等。

?1:分配进程描述符

linux通过使用slab分配器分配task_struct结构,这样能够达到对象复用和缓存着色的目的。现在只需在栈底或栈顶创建一个新的结构struct thread_info结构即可。

首先我们先看一下thread_info的结构:

struct thread_info {
    //所在的进程结构体
    struct task_struct  *task;      /* main task structure */
    //执行域   
    struct exec_domain  *exec_domain;   /* execution domain */
    //底层标志  
    unsigned long       flags;      /* low level flags */
    //线程同步标志    
    unsigned long       status;     /* thread-synchronous flags */
    //当前CPU 
    __u32           cpu;        /* current CPU */
    //0 : 表示可抢占,小于0 : bug   
    int         preempt_count;  /* 0 => preemptable, <0 => BUG */
    /*
     * 进程地址空间
     * 0-0xBFFFFFFF 用于用户进程
     * 0-0xFFFFFFFF 用于内核进程
     */
    mm_segment_t        addr_limit; /* thread address space:
                           0-0xBFFFFFFF for user-thead
                           0-0xFFFFFFFF for kernel-thread
                        */
    void            *sysenter_return;
    struct restart_block    restart_block;
    unsigned long           previous_esp;   /* ESP of the previous stack in case
                           of nested (IRQ) stacks
                        */
    __u8            supervisor_stack[0];  //放入栈顶
};

通过该结构就可以看出,主要知道thread_info结构体,就能获取需要的进程。

技术分享

每个任务的thread_info结构在他的内核栈的尾端分配。

?2:进程描述符的存放

内核通过一个唯一的进程标识值或PID来标识每个进程。该值有一个上限,存放在/proc/sys/kernel/pid_max中。我们可以来看一下:

技术分享

我们可以修改这个值,来改变系统中进程的最大数量。

下面我们来看一下current_thread_info()函数的使用。

获取当前进程的pid我们可以这样使用:

#include <asm/thread_info.h>
//....
 int pid = current_thread_info()->task->pid;
//....

3:进程状态

下面请看一下,我们的linux内核代码:

/*
 * Task state bitmask. NOTE! These bits are also
 * encoded in fs/proc/array.c: get_task_state().
 *
 * We have two separate sets of flags: task->state
 * is about runnability, while task->exit_state are
 * about the task exiting. Confusing, but this way
 * modifying one set can‘t modify the other one by
 * mistake.
 */
#define TASK_RUNNING        0
#define TASK_INTERRUPTIBLE  1
#define TASK_UNINTERRUPTIBLE    2
#define TASK_STOPPED        4
#define TASK_TRACED     8
/* in tsk->exit_state */
#define EXIT_ZOMBIE     16
#define EXIT_DEAD       32
/* in tsk->state again */
#define TASK_NONINTERACTIVE 64

这几行代码描述了一个进程的所有的状态。其中,在task_struct结构体中,保存进程状态的属性为state域。总的来说,进程一共有5中状态,下面我们整理一下。

TASK_RUNNING - 进程是可执行的。表示进程正在执行,或者在任务队列中等待执行。在用户空间中,这是进程的唯一可能的状态,在内核空间也是可以有这个状态的。

TASK_INTERRUPTIBLE - 进程可中断。进程正在睡眠,等待某个事件的达成。该事件一旦达成,进程状态就会变成可运行状态。当然,该状态还可以由于某个信号而提前唤醒运行。

TASK_UNINTERRUPTIBLE - 进程不可中断。该状态对信号不做反应。他等待特定的事件发生,别的事件信号不能将它唤醒.

TASK_TRACED - 被其他进程跟踪的进程。例如通过ptrace对调试程序进行跟踪。

TASK_STOPPED - 进程停止执行。

下面这个图是进程状态的转换。

技术分享

4:设置当前进程状态
内核在运行过程中,需要经常调整某个进程的状态,下面我们有一下几个方法来设置进程的状态

?1:set_task_state(task,state); /* 将任务task的状态设置为state
?2:如果没有内存屏障的话,也可以使用下面的方法来设置进程状态
?    ?task->state = state;

其中,函数set_current_state(state)和set_task_state(current,state)是等价的。

下面我们来看一下这里面一些函数的定义。

#define __set_task_state(tsk, state_value)      
    do { (tsk)->state = (state_value); } while (0)
#define set_task_state(tsk, state_value)        
    set_mb((tsk)->state, (state_value))
/*
 * set_current_state() includes a barrier so that the write of current->state
 * is correctly serialised wrt the caller‘s subsequent test of whether to
 * actually sleep:
 *
 *  set_current_state(TASK_UNINTERRUPTIBLE);
 *  if (do_i_need_to_sleep())
 *      schedule();
 *
 * If the caller does not need such serialisation then use __set_current_state()
 */
#define __set_current_state(state_value)            
    do { current->state = (state_value); } while (0)
#define set_current_state(state_value)      
    set_mb(current->state, (state_value))

在其中就会牵扯到SMP的内存屏障的问题了,在这里先不讨论。

在上面的代码当中,我们发现,在宏定义中,使用了一个do{}while()的结构,在linux内核源码中,有很多这样的结构,下面我们就来分析一下使用这个结构的好处。

假设我们有这样一个例子:

#define fun(x) hello1(x);hello2(x)

如果我们有这样一段代码

    if(x)
    ?    ?fun(x);

这样的话,替换回来之后,我们就变成了这个样子:

if(x)
    hello1(x);
    hello2(x);

这样就失去了我们本来的目的。

如果我们使用{}括起宏定义。这样来定义:

#define fun(x) {hello1(x);hello2(x);}

如果我们的代码是这样子的:

if(x)
    fun(x);
else
   test(x);

则我们替换之后,就变成这个样子的:

if(x){
    hello1(x);
    hello2(x);
};
else
    test(x);

这样也是错误的,所以,我们使用do{}while()结构函数很有用的。

5:进程家族树

在linux中,所有的进程都是pid为1的init进程的后代。内核在系统启动的最后阶段启动init进程。系统中的每个进程都有一个父进程,相应的,每一个进程都有零个或多个子进程。拥有同一个父进程的所有进程被成为兄弟进程。同样,进程之间的关系也是存放在进程描述符中。每一个进程都有一个父进程的指针和一个子进程的链表。

在进程描述符中代码如下:

    struct task_struct *parent; /* parent process */
    /*
     * children/sibling forms the list of my children plus the
     * tasks I‘m ptracing.
     */
    struct list_head children;  /* list of my children */

1:获取当前进程的父进程
? ?struct task_struct *myparent = current->parent;

2:遍历子进程

?struct task_struct *task;
    ?struct list_head *list;
    ?
    ?list_for_each(list,&current->children){
    ?    ?task = list_entry(list,struct task_struct,slibinig);
    ?    ?//task指向当前的某个子进程
    ?}

?3:获取init进程,init进程的进程描述符是作为init_task静态分配的。

  ?struct task_struct *task;
    ?for(task = current;task != &init_task;task = task->parent);
    ?/*指向init进程 */

4:遍历进程的另一个方法

    ?struct task_struct *task;
    ?for_each_process(task){
    ?    ?//进行操作
    ?}    ?

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。