linux内核进程详解
一、什么是进程?
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 make -C /lib/modules/
(shelluname−r)/buildM=
<script type="math/tex" id="MathJax-Element-27">(shell uname -r)/build M=</script>(PWD) clean`
clear:
END!
PS:本文翻译修改自http://linuxgazette.net/133/saha.html。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)