Linux内核学习——进程管理模块



进程

进程是处于执行期的程序及相关资源的总称,通常还包括其他资源,如打开的文件,挂起的信号,内核内部数据,处理器状态,具有内存映射的内存地址空间,执行线程,存放全局变量的数据段等。

线程 是在进程中活动的对象,拥有独立的程序计数器、进程栈和一组进程寄存器。内核调度对象是线程,而非进程。在Linux系统中,线程和进程没有特别区分。

现代操作系统中,进程提供两种虚拟机制:虚拟处理器(进程调度)和虚拟内存(内存管理)

在Linux系统中,通常进行 fork()系统调用复制现有进程来创建全新进程,调用 fork() 的进程称为父进程,新产生的进程称为子进程。fork() 系统调用从内核返回两次,分别到父进程和子进程。

通常创建新的进程都是为了执行新的程序,因此调用 exce() 这组函数创建新的地址空间,并载入新的程序。

程序通过 exit() 系统调用退出执行,此函数会将其占用的资源进行释放,父进程可以通过 wait4() 系统调用查询子系统是否终结。进程退出执行后被设置为僵死状态,直到其父进程调用 wait() 或 waitpid()。

进程的另一个名字是任务 (task), Linux 内核通常把进程也叫做任务



进程描述符及任务结构

任务列表 是用于存放进程的双向循环链表,其中每一项的类型都是文件描述符(task_struct),结构定义于 <linux/sched.h>中。

struct task_struct 包含了具体进程的所有信息,相对较大,包含了 打开的文件、进程的地址空间、挂起的信号、进程的状态等信息。


分配进程描述符
Linux 通过slab分配器(内存管理)分配 task_struct 结构,达到对象复用和缓存着色的目的。

在某些体系中,有专门的寄存器存放 task_struct 指针。在x86体系中,由于寄存器不富裕,在内核栈尾端使用 struct thread_info 用于存放 task_struct 的位置,因为 task_struct 目前由 slab 回收与分配。

内核通过唯一的进程标识值 (PID)标识每一个进程,存放于task_struct 中。PID的最大默认值是32768(short int,为了与老版本的Unix兼容,可以修改 /proc/sys/kernel/pid_max 来提高上限)


进程状态

进程描述符中的state域描述进程当前的状态,共有一下五种状态:
TASK_RUNNING(运行) :正在执行或者处于运行队列等待执行
TASK_INTERRUPITIBLE(可中断) : 正在睡眠(被阻塞),等待某些条件达成,转为运行
TASK_UNINTERRUPITIBLE(不可中断) : 与(可中断)类似,但是对信号不做响应,等待时不被干扰
__TASK_TRACED(追踪) :被其他进程跟踪的进程
__TASK_STOPPED(停止) : 进程停止执行,通常发生在接收到 SIGSTOP SIGTSTP SIGTTIN SIGTTOU 等信号

通常通过 set_task_state(task, state)设置指定进程的状态

进程上下文 :一般进程运行在用户空间,当一个程序执行了系统调或者触发异常,即陷入内核空间,此时称内核“代表进程执行”并处于进程上下文中。


进程家族树
Linux 所有的进程都是 PID 为1的 init 进程的后代,内核在系统启动的最后阶段启动 init 进程。

每个进程必有一个父进程已经零个或多个子进程,进程间关系存放在进程描述符中,每个 task_struct 都包含一个指向父进程 task_struct 的指针 parent,和一个包含子进程链表的指针 children



进程创建

Linux 的 fork() 使用写时拷贝,内核此时不会为子进程复制整个内核空间,而且父子进程共享同一拷贝。只有在写入时,数据才会被复制,因此 fork() 之后马上调用 exec() 就无须复制了。

fork() 、 vfork() 、__clone() 都根据自身需要的参数标志调用 clone(), clone() 调用 do_fork()
do_fork() 调用了创建中的大部分工作,定义于 kernel/fork.c 文件中,该函数调用 copy_process()函数,然后让进程开始运行
-1- 调用 dup_task_struct() 为进程创建内核栈、thread_info 和 task_struct
-2- 子进程初始化自己进程描述符的部分数据,task_struct的大部分数据没有被初始化
-3- 子进程的状态被设置为TASK_UNINTERRUPTIBLE,确保其不被投入运行
-4- copy_process() 调用 copy_flags() 以更新 task_struct 的 flags 成员,PF_SUPERPRIV(表明进程是否有超级用户权限),PF_FORKNOEXEC(标识进程是否调用 exec() 函数)
-5- 调用alloc_pid()为新进程分配有效的 PID
-6- copy_process() 拷贝或共享打开的文件、文件系统信息、信号处理函数 、进程地址空间和命名空间
-7- 最后,返回一个指向子进程的指针



线程在Linux中的实现

线程机制提供了在同一程序内共享内存地址空间的一组线程。在多处理器上保证真正的并行处理。从Linux内核看线程仅仅被视为一个与其他进程共享某些资源的进程。

创建线程时在调用 clone() 时需要传递一些参数标志来指明需要共享的资源,如

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHABD, 0);

clone 用到的参数标志以及他们的作用,在 <linux/sched.h> 中定义

内核线程是独立运行在内核空间的标准进程,内核线程和普通进程的主要区别在于内核线程没有独立的地址空间(实际指向地址空间的mm指针被设置为NULL)。



进程终结

一般而言,进程的析构由自身引起,可能是显示或者隐式的进行 exit() 系统调用,该任务大部分要靠 do_exit() 来完成

在调用了 do_exit() 之后,线程僵死不再运行,但是系统还是保留了其进程描述符(用于让系统能获得其信息,进程终结时所需的清理工作和进程描述符的删除被分开执行)。在父进程获得以终结的子进程的信息后,或者通知内核并不关心相关信息之后,子进程的 task_struct 结构才被释放。

如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则孤儿进程会在退出时永远处于僵死状态。系统会给子进程在当前线程组内找到一个线程作为父亲,实在不行就让 init 做其父亲。 在 do_exit 中调用 exit_notify, 该函数调用 forget_original_parent, 最后调用 find_new_reaper 执行寻父过程。

在2.6内核前,其兄弟线程需要遍历系统的所有进程来寻找,现在通过 ptrace 保存被跟踪的进程的兄弟进程链表

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值