一、什么是进程?
Robert Love’s 在《linux内核开发》一书中写道,“在UNIX开源系统中,进程是基本抽象之一,其他基本抽象是文件”。简单了说,进程是程序运行时,它包含了程序运行代码、一组资源(如一组打开的文件)、相关内核数据、一个地址空间、执行和含有全局变量数据的一个或者多个线程。

二、进程描述符
每一个进程都有自己的进程描述符,是在存储器中用于跟踪进程的信息,其中包括进程的各种信息:PID,状态,父进程,子进程,兄弟进程,存储器和列表等。
linux内核使用一个循环双向链表(struct task_structs)来存储进程描述符,这种结构定义在linux/sched.h中。下面是linux内核 2.6.15-1.2054_FC5版本的701行后的部分数据:

701 struct task_struct {
702     volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
703         struct thread_info *thread_info;

767         /* PID/PID hash table linkage. */
768         struct pid pids[PIDTYPE_MAX];

798         char comm[TASK_COMM_LEN]; /* executable name excluding path

以上第一行字段状态定义为volatile long,该变量用于跟踪过程中执行状态,通过以下宏定义:

#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

关键字volatile是值得研究的,可以查看http://www.kcomputing.com/volatile.html查看更多信息。

三、链表
在我们讨论进程在内核中如何存储之前,需要了解内核如何循环链表。以下链表实现是所有内核源代码的一个标准,链表在linux/list.h中声明,数据结构非常简单:

struct list_head {
         struct list_head *next, *prev;
 };

该文件还定义了几个现成的宏和函数,可以用于操作链表。此链表定义实现标准确保

不会出现用户异想天开造成bug的情况。关于链表详细信息可以产看Suman Adak的博客http://sumanadak.blogspot.com/2006/09/linux-kernel-linked-list.html。

四、内核任务列表
那么,linux内核是如何使用循环双向链表存储进程的记录呢?struct list_head 为我们定义了内部结构:

struct list_head tasks;

这个结构书名内核采用循环链表存储进程,这意味着可以使用标准内核链表、宏和函数遍历所有进程列表。
linux系统中,初始化从“所有进程母进程”开始,因此,它被放在链表的开头,尽管循环链表没有真正意义的头部。初始化进程描述符是静态分配:

extern struct task_struct init_task;

下图表示了内存中的进程链表:
这里写图片描述
其他几个宏和函数能用于遍历链表:for_each_process()是遍历整个任务列表的宏。它被定义在linux/ sched.h中,如下所示:

#define for_each_process(p) \
        for (p = &init_task ; (p = next_task(p)) != &init_task ;)

next_task()是在linux/ sched.h中定义的一个实现返回列表下一个任务的宏:

#define next_task(p)    list_entry((p)->tasks.next, struct task_struct, tasks)

list_entry() 是在linux/ list.h定义的宏:

/*
 * list_entry - get the struct for this entry
 * @ptr:        the &struct list_head pointer.
 * @type:       the type of the struct this is embedded in.
 * @member:     the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
        container_of(ptr, type, member)
The macro container_of() is defined as follows:

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

因此,通过整个进程列表的遍历,我们可以查看所有系统上运行的进程。这需要宏for_each_process(task)来完成,其中,task是结构task_struct的指针。下面是一个linux内核中的例子:

/* ProcessList.c 
    Robert Love Chapter 3
    */
    #include < linux/kernel.h >
    #include < linux/sched.h >
    #include < linux/module.h >

    int init_module(void)
    {
    struct task_struct *task;
    for_each_process(task)
    {
    printk("%s [%d]\n",task->comm , task->pid);
    }

    return 0;
    }

    void cleanup_module(void)
    {
    printk(KERN_INFO "Cleaning Up.\n");
    }

当前的宏是正在执行的进程的进程描述符(一个task_struct的指针),在x86中这是由asm/thread_info.h中的函数current_thread_info()完成:

/* how to get the thread information struct from C */
   static inline struct thread_info *current_thread_info(void)
   {
           struct thread_info *ti;
           __asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));
           return ti;
   }

最后,是当前取消引用的任务成员thread_info结构部件,它由asm/thread_info.h转载current_thread_info()->task:

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 */
           __u32                   cpu;            /* current CPU */
           int                     preempt_count;  /* 0 => preemptable, <0 => BUG */


           mm_segment_t            addr_limit;     /* thread address space:
                                                      0-0xBFFFFFFF for user-thread
                                                      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];
   };
Using the current macro and init_task we can write a kernel module to trace from the current process back to init.

/*
Traceroute to init
   traceinit.c
Robert Love Chapter 3
*/
  #include < linux/kernel.h >
  #include < linux/sched.h >
  #include < linux/module.h >

  int init_module(void)
  {
  struct task_struct *task;

   for(task=current;task!=&init_task;task=task->parent)
   //current is a macro which points to the current task / process
   {
   printk("%s [%d]\n",task->comm , task->pid);
   }

   return 0;
   }

   void cleanup_module(void)
   {
   printk(KERN_INFO "Cleaning up 1.\n");
   }

另外,Makefile 文件用来编译内核模块:

obj-m +=ProcessList.o
obj-m +=traceinit.o

all:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clear:
make -C /lib/modules/ (shelluname−r)/buildM= <script type="math/tex" id="MathJax-Element-27">(shell uname -r)/build M=</script>(PWD) clean`

END!
PS:本文翻译修改自http://linuxgazette.net/133/saha.html。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐