《linux系统内核设计与实现》第三章-进程管理

3.1 进程

进程就是处于执行期的程序(目标码存放在某种存储介质上)。但进程并不仅仅局限于一段可执行程序代码(Unix称其为代码段,text section)。通常进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程( thread of execution),当然还包括用来存放全局变量的数据段等。实际上,进程就是正在执行的程序代码的实时结果。内核需要有效而又透明地管理所有细节。

执行线程,简称线程(thread),是在进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。内核调度的对象是线程,而不是进程。在传统的Unix系统中,一个进程只包含一个线程,但现在的系统中,包含多个线程的多线程程序司空见惯。稍后你会看到,Linux系统的线程实现非常特别:它对线程和进程并不特别区分。对Linux而言,线程只不过是一种特殊的进程罢了。

在现代操作系统中,进程提供两种虚拟机制:虚拟处理器和虚拟内存虽然实际上可能是许多进程正在分享一个处理器,但虚拟处理器给进程一种假象,让这些进程觉得自己在独享处理器。第4章将详细描述这种虚拟机制。而虚拟内存让进程在分配和管理内存时觉得自己拥有整个系统的所有内存资源。第12章将描述虚拟内存机制。有趣的是,注意在线程之间°可以共享虚拟内存,但每个都拥有各自的虚拟处理器。

程序本身并不是进程,进程是处于执行期的程序以及相关的资源的总称。实际上,完全可能存在两个或多个不同的进程执行的是同一个程序。并且两个或两个以上并存的进程还可以共享许多诸如打开的文件、地址空间之类的资源。

无疑,进程在创建它的时刻开始存活。在Linux系统中,这通常是调用fork()系统的结果,该系统调用通过复制一个现有进程来创建一个全新的进程。调用fork()的进程称为父进程,新产生的进程称为子进程。在该调用结束时,在返回点这个相同位置上,父进程恢复执行,子进程开始执行。fork()系统调用从内核返回两次:一次回到父进程,另一次回到新产生的子进程。

通常,创建新的进程都是为了立即执行新的、不同的程序,而接着调用exec()这组函数就可以创建新的地址空间,并把新的程序载入其中。在现代Linux内核中,fork()实际上是由clone(系统调用实现的,后者将在后面讨论。

最终,程序通过exit()系统企用工讲程是否终结,这其实使得进程拥有了等待特定进在父进程可以通过wait4)系统调用查询子进程是否终结,这其
执行完毕的能力。进程退出执行后被设置为僵死状态,直到它的父进程调用wait()或waitpid()为止。

进程和线程区别详情可参考:进程和线程的详解和区别_如何理解进程和线程-CSDN博客

线程:linux之线程_linux下线程空间独立吗-CSDN博客

3.2 进程描述符及任务结构

内核把进程的列表存放在叫做任务队列( task list)的双向循环链表中。链表中的每一项都是类型为task_struct、称为进程描述符(process descriptor)的结构,该结构定义在<linux/sched.h>文件中。进程描述符中包含一个具体进程的所有信息。

笔者这里使用的是linux5.0.1版本,<linux/sched.h>已经被更新为<linux/.h.h>。

task_struct相对较大,在32位机器上,它大约有1.7KB。但如果考虑到该结构内包含了内核管理一个进程所需的所有信息,那么它的大小也算相当小了。进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态,还有其他更多信息。

3.2.1 分配进程描述符

Linux通过slab分配器分配task_struct结构,这样能达到对象复用和缓存着色( cache coloring)(参见第12章)的目的。在2.6以前的内核中,各个进在的task_struct存放在它们内核栈的尾端。这样做是为了让那些像x86那样寄存器较少的硬件体系给构只要通过指针能计算出它的位置,而避免使用额外的寄存器专门记录。由于现在用slab分配器动态生成task_struct,所以只需在栈底(对于向下增长的栈来说)或栈顶(对于向上增长的栈来说)创建一个新的结构struct thread_info。

找到两个thread_info:

(1)路径:arch/arc/include/asm/thread_info.h

struct thread_info {
	unsigned long flags;		/* low level flags */
	int preempt_count;		/* 0 => preemptable, <0 => BUG */
	struct task_struct *task;	/* main task structure */
	mm_segment_t addr_limit;	/* thread address space */
	__u32 cpu;			/* current CPU */
	unsigned long thr_ptr;		/* TLS ptr */
};

(2)路径:arch/alpha/include/asm/thread_info.h

struct thread_info {
	struct pcb_struct	pcb;		/* palcode state */

	struct task_struct	*task;		/* main task structure */
	unsigned int		flags;		/* low level flags */
	unsigned int		ieee_state;	/* see fpu.h */

	mm_segment_t		addr_limit;	/* thread address space */
	unsigned		cpu;		/* current CPU */
	int			preempt_count; /* 0 => preemptable, <0 => BUG */
	unsigned int		status;		/* thread-synchronous flags */

	int bpt_nsaved;
	unsigned long bpt_addr[2];		/* breakpoint handling  */
	unsigned int bpt_insn[2];
};

比书中的属性少一些

每个任务的thread_info结构在它的内核栈的尾端分配。结构中task域中存放的是指向该任务实际task_struct的指针。

3.2.2 进程描述符的存放

内核通过一个唯一的进程标识值〈process identification value)或PID来标识每个进程。PID是一个数,表示为pid_t隐含类型,实际上就是一个int类型。为了与老版本的Unix和Linux兼容,PID的最大值默认设置为32768 ( short int短整型的最大值〉,尽管这个值也可以增加到高达400万(这受<linux/threads.h>中所定义PID最大值的限制)。内核把每个进程的PID存放在它们各自的进程描述符中。


 

参考:

[1] 《linux系统内核设计与实现》第三版

  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值