LINUX内核设计思想之进程管理

本文详细介绍了Linux下进程的特性,包括进程的出生、执行、死亡及其内部结构。解释了如何使用fork()和exec()函数创建进程,并探讨了进程的生命周期、状态转换以及线程的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   

什么是进程?

进程是处于执行期的程序以及它所包含的资源的总称.

 

LINUX下进程的特性:

1.进程的出生:

fork()系统调用.其实现机制是通过复制一个现有的进程来创建一个全新的进程.

 

2.进程的执行:

exec*()函数族创建新地址空间并把新的程序载入执行.

 

3.进程的死亡:

exit()

 

3.1 进程描述符及任务结构

LINUX内核里面用结构体struck task_struck来表征一个进程.32位机器上,其大小约1.7K字节.内核里面往往存在多个进程,这时候当然需要一种类型数组或链表的组织里面被组织起来.组织进程的叫做任务队列,它是一个双向链表,表中每一项都是类型为task_struck.因此,我们以后和进程打交道,就意味着和struck task_struck结构体打交道.

 

3.1.1 进程的三态:

运行态、停止态、休眠态.

 

3.1.2 进程上下文

当用户空间进行了系统调用或触发某中断陷入到内核态.这时候我们称内核态"代表进程执行",此时的场景我们谓之为进程上下文.

 

3.1.3 进程家族树

所有的进程都是PID1init进程的后代.内核在系统启动的最后阶段启动init进程.

我们常常听到类似的字眼,"父进程""子进程""兄弟进程".那么,作为进程的结构体表征--task_struck必定会有相关"血缘关系"的说明.如下,每个task_struck都包含一个指向其父进程的task_struck,叫做parent指针,还包含一个称为children的子进程链表.

获取当前进程的父进程示意代码:

Struck task_struck *my_parent = current->parent;

访问子进程的示意代码:

Struck task_struck *task;

Struck list_head *list;

List_for_each(list,¤t->children)

{

Task = list_entry(list,struck task_struck,sibling);

}

Init的进程描述符是作为init_task静态分配的.下面的代码可以很好的演示所有进程之间的关系:

Struct task_struck *task;

For(task = current; task != &init_task; task = task->parent)

获取下一个进程:

List_entry(task->tasks.next,struct task_struct,tasks);

Next_task(task);

获取前一个进程:

List_entry(task->tasks.prev,struct task_struck,tasks);

Prev_task(task);

访问整个任务队列:

Struct task_struct *task;

 

For_each_process(task)

{

/*打印每个进程的名称和PID*/

Printk("%s[%d]\n",task->comm,task->pid);

}

 

3.2 进程的创建

LINUX中进程的创建分两步走:fork()exec()两函数.首先,fork()通过拷贝当前进程创建一个子进程.子进程与父进程的区别仅在于PID(每个进程唯一)PPID(父进程的PID)和某些资源和统计量;然后,exec()函数负责读取可执行文件并将其载入地址空间运行.

 

3.2.1 写时拷贝

Linux fork()使用写进拷贝(copy-on-write)页实现.写时拷贝是一种推迟甚至免除拷贝数据的技术.内核此时并不复制整个进程地址空间,而是让父进程和子进程共享一个拷贝.只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝.这样避免了大量根本不会被用到的数据的拷贝.

3.2.2 fork()函数调用过程

调用流程:

fork()/vfork()/__clone() --> do_fork() --> copy_process().

copy_process()主要工作如下:

A.调用dup_task_struck()为新进程创建内核栈、thread_info结构和task_struck;

B.检查新进程相关合法性,如系统的进程数据是否超出给定的资源限制;

C.对进程描述符进程清0动作;

D.标识新进程状态为TASK_UNINTERRUPTIBLE保证新进程不会投入运行;

E.调用copy_flags()更新task_struckflags成员.表明进程是否拥有超级用户权限的PF_SUPERPRIV标识被清0.表明没调用exec()函数的PF_FORKNOEXEC标识被设置;

F.调用get_pid()为新进程获取上个有效的PID;

G.初始化进程描述符;

H.让父进程了新进程平分剩余的时间片;

I.打完收工.

 

3.3 线程

LINUX中的线程只是一种较为特殊的进程而已.内核线程--独立运行于内核空间的标准进程,它没有独立的地址空间(mm指针被赋为NULL).创建内核线程如下:

int kernel_thead(int (*fn)(void*),void *arg,unsigned long flags);

各参数意义如下:

fn:内核线程的运行体;

arg: 内核线程的运行体的参数;

flags:clone标志,常用的内核线程标志有CLONE_FSCLONE_FILESCLONE_SIGHAND.

一般而言,内核线程在创建的时候一直运行下去,直到重启或死机.

clone()参数标识罗列如下:

CLONE_FILES 父子进程共享打开的文件

CLONE_FS 父子进程共享文件系统信息

CLONE_IDLETASK 将PID设置为0(只供idle进程使用)

... ...

<<LINUX内核设计与实现 第2>>P25.

 

3.4 进程的终结

LINUX中进程的终结主要分两部分来完成.一部分是自身调用exit()函数(大部分工作靠do_exit()函数来完成);另一部分是父进程得知此子进程已经终结了,释放子进程的task_struck结构体.

 

3.4.1 孤儿进程

孤儿进程,是指那些其依赖的父进程先于其退出而产生的进程.由上述知道,一个进程退出分两部分,后部分是通过其父进程来回收task_struck结构体.孤儿进程并没有父进程,其退出时通过自身调用exit()处于僵死状态,其所占用的task_struck并没有父进程去回收,会导致内存的白白耗费.因此,LINUX必须提供种机制来避免这种情况.为孤儿进程找一个新的父进程.解决方案是给孤儿进程在当前线程组内找一个线程作为父进程,如果不行,就让init做它们的父进程.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值