当前位置: 首页 > 科技观察

从C语言源码分析,神秘的Linux系统是如何记录和描述过程的?

时间:2023-03-13 13:13:33 科技观察

上一节简单讨论了Linux操作系统中进程的概念。其实简单来说,一个进程无非是运行中的程序及其相关资源的总和。这里请读者注意“相关资源”二字。Linux在内核中是如何记录进程的资源的?Linux内核是如何记录进程的资源的?首先要明白Linux内核大多是用C语言编写的,所以要弄清楚内核是如何记录进程资源的,只需要查看相关的C语言代码即可。事实上,Linux内核使用task_struct结构来描述进程的资源。其C语言代码如下。请看:task_struct结构很长。task_struct结构很长。在我手上的Linux内核的C语言源代码中,需要280行。当然,这里涉及到很多条件编译,在32位机器上,task_struct占用的内存大约是1.7KB,但考虑到它可以管理一个完整的进程,1.7kB其实也不算大。由于task_struct结构体太长,这里无法将其成员介绍清楚。如果读者和我一样好奇,粗略浏览一下task_struct结构,应该可以找到一些熟悉的成员,例如:task_struct结构中熟悉的成员通过C语言的注释和成员变量名,可以看到task_structstructure体中包含文件系统、线程结构、进程打开的文件等信息,对应上篇内容。其他成员会在我后续的文章中介绍,这里不再赘述。Linux在创建进程时,通过slab分配器分配task_struct结构,可以避免动态分配和释放带来的开销,提高内存使用效率。那么内核创建后如何访问task_struct结构呢?根据我手上的内核C语言源码,Linux中也有一个结构体thread_info,它的一个成员任务指针正好适合索引task_struct结构体。在X86_64平台上,thread_info的相关C语言代码如下,请看:taskpointerLinux通常会在内核栈的底部或顶部保留thread_info结构,内核栈的大小通常是已知的,所以每个进程可以很方便的从自己的栈中找到thread_info结构,然后再找到task_struct结构。要找到当前进程的thread_info结构,可以调用current_thread_info()函数。其C语言代码如下:staticinlinestructthread_info*current_thread_info(void){registerunsignedlongspasm("sp");return(structthread_info*)(sp&~(THREAD_SIZE-1));}current_thread_info()函数可见。current_thread_info()函数实际上是通过进程栈计算出来的,所以它的实现与平台架构有关。上面的C语言代码其实只是arm平台的实现方法,其他平台的实现方法。各位读者可以自己查。此时,要获取当前进程的资源,可以使用current_thread_info()->task索引。进程PIDLinux内核为每个进程分配一个唯一的进程标识(processidentification,PID)以区分不同的进程。PID是一个整数,在内核的C语言源码中表示为pid_t类型(实际上是int类型)。在Linux命令行输入ps命令查看进程的PID。例如:查看进程的PID。task_struct结构体使用成员pid来记录进程的PID值。相关的C语言代码如下。请看:task_struct结构使用成员pid来记录进程在Linux系统中,PID的最大值是可以调整的。早期为了兼容老版本的Unix和Linux,默认最大值为32768(shortint类型所能表示的最大值)。这个值可以通过cat命令查看:#cat/proc/sys/kernel/pid_max32768PID的最大值对Linux系统的运行是有影响的,因为PID的值是唯一的,所以它的最大值实际上表示系统可以同时运行的最大进程数。对于普通个人用户,32768就够了,但是对于大型服务器,32768可能不够用,那么可以修改pid_max来解决这个问题。进程的状态现在我们知道了Linux内核是如何描述和记录进程资源的,以及如何区分不同进程的。那么进程的状态是什么?读者应该注意到,task_struct结构体的第一个成员state是用来记录进程的状态的。C语言源代码中进程的状态是使用几个宏定义的:C语言源代码中的进程状态是使用几个宏定义的。Linux系统中的进程必须处于这五种状态之一。从上到下,表示进程是:正在运行或者准备运行,正在休眠,但是可以被中断,收到信号会提前唤醒,正在休眠,不能被中断,即,即使收到信号也不会被其他进程唤醒跟踪时停止运行现在我们明白了,有时候处于D状态的进程是无法被kill命令杀死的,因为这些进程处于不响应信号的状态.kill命令本质上是发送SIGKILL信号,自然无法杀死进程。父进程和子进程进程的父进程和子进程也属于进程的资源,所以也记录在task_struct结构体中,请看相关C语言代码:方便访问父进程和当前进程的子进程,所以需要访问当前进程父进程和子进程方便,例如:structtask_struct*p=current->parent;structtask_stuck*c=current->children;稍微想一想,你应该能发现进程结构体task_struct中的parent指针和children指针其实构成了一个链表,通过这样的链表,我们可以方便的访问到父进程,祖父进程……,以及进程的子进程、孙子进程……等等。但是要明白,对于一个进程数量很多的系统来说,反复遍历所有进程的开销是非常大的。小结本节首先讨论Linux内核是如何记录和描述进程资源的。可以看出,内核管理进程实际管理的是task_struct结构体。然后,通过C语言源码,我们了解了内核是如何访问task_struct结构体的,以及如何区分进程的。最后,我们还讨论了进程的状态和家族树。由此可见,Linux内核的源代码并没有神秘到不可理解的地步。