您的位置:首页 > 运维架构 > Linux

linux内核进程详解

2015-12-23 10:03 691 查看
一、什么是进程?

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=(PWD) clean`

END!

PS:本文翻译修改自http://linuxgazette.net/133/saha.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux kernel unix