【linux】进程管理

进程管理

1. 进程与线程

进程和线程是程序运行时状态,是动态变化的,进程和线程的管理操作(比如,创建,销毁等)都是有内核来实现的。Linux中的进程于Windows相比是很轻量级的,而且不严格区分进程和线程,线程不过是一种特殊的进程。

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

2. 进程在Linux中的表示

内核中进程的信息主要保存在struct task_struct中(include/linux/sched.h),该结构体部分截图如下:

在这里插入图片描述

task_struct相对较大,该结构体完整地描述一个正在执行的程序:打开的文件、进程的地址空间、挂起的信号、进程的状态等等。

描述信息说明
标识符与进程相关的唯一标识符,用来区别正在执行的进程和其他进程。
状态描述进程的状态,因为进程有挂起,阻塞,运行等好几个状态,所以都有个标识符来记录进程的执行状态。
优先级如果有好几个进程正在执行,就涉及到进程被执行的先后顺序的问题,这和进程优先级这个标识符有关。
程序计数器程序中即将被执行的下一条指令的地址。
内存指针程序代码和进程相关数据的指针。
上下文数据进程执行时处理器的寄存器中的数据。
I/O状态信息包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表等。
记账信息包括处理器的时间总和,记账号等等。

而若干个这种描述程序的结构体(我们称为进程描述符——struct task_struck),放在一个双向的循环链表中,这个双向循环链表我们称为任务队列

进程描述符与任务队列的表示图如下表示:

在这里插入图片描述

3. 进程描述符的存放与分配

在内核中,访问任务通常需要获得指向其task_struct的指针。实际上,内核中大部分处理进程的代码都是直接通过task_struct进行的。内核中current宏指向当前进程,因此通过current宏查找到当前正在运行的进程的进程描述符的速度就显得尤为重要。硬件体系结构不同哦,该宏的实现也不同,它必须针对专门的硬件体系结构做处理。有的硬件体系结构可以拿出一个专门寄存器来存放指向当前进程task_struct的指针,用于加快访问速度。而像x86这样的体系结构,其寄存器并不富余,就只能在内核栈的尾端创建thread_info结构,通过计算偏移间接地查找task_struct结构。其中关系如图所示:

在这里插入图片描述

关于内核栈的定义在include/linux/sched.h

在这里插入图片描述
struct thread_info结构在<asm/thread_info.h>中

在这里插入图片描述

结构体中的task域存放的是指向该任务实际task_struct指针.

arch/x86/include/asm/thread_info.h文件,current_thread_info获得当前thread_info,再:current_thread_info()->task,找到task_struct的地址。

具体可以看一下current宏中的描述:

在这里插入图片描述

即current调用current_thread_info从而获得指向当前进进程的指针

4. 进程的状态

进程的五种状态:

TASK_RUNNING:表示进程要么正在执行,要么正要准备执行。

TASK_INTERRUPTIBLE:表示进程被阻塞(睡眠),直到某个条件变为真。条件一旦达成,进程的状态就被设置为TASK_RUNNING。

TASK_UNINTERRUPTIBLE:意义与TASK_INTERRUPTIBLE类似,除了不能通过接受一个信号来唤醒以外。

__TASK_STOPPED:表示进程被停止执行。

__TASK_TRACED:表示进程被debugger等进程监视。(被另一个进程跟踪)

设置当前进程状态函数:set_task_state(task,state)

在<linxu/sched.h>中对进程状态设置函数的定义如下:

在这里插入图片描述

可以看出来set_current_state(state_value)与set_task_state(tsk,state_value)的含义是一样的。

五种状态的转换如下图所示:

在这里插入图片描述

5. 进程的组织

首先声明三点:

所有的进程都是PID为1的init进程的后代

系统中每个进程必有一个父进程

每个进程拥有零个或多个子进程

如下图:

在这里插入图片描述

其中init_task静态分配的init进程的进程描述符

一个进程可以有多个子进程,子进程之间称为兄弟进程,这些子进程每个task_struct都包含一个指向其父进程的task_struct,叫做parent的指针。

当然task_struct也包含一个children的子进程链表

5.1 子进程的访问方法

可以使用以下方法访问子进程:

struct task_struct *task; 

struct list_head *list;

 list_for_each(list,&current->children){    

       task = list_entry(list,struct task_struct,sibling);      

 }

可以看到,这里使用的是链表相关的操作来访问子进程。我们知道, task_struct 是存放在一个双向循环链表 task_list(任务队列)中的,而一个 task_struct 包含了一个具体进程的所有信息,因此,我们只需要找到子进程的 task_struct 即可以访问子进程了,上面代码就是这么做的。那么,具体是如何找到子进程的进程描述符 task_struct的呢?下面对上面的代码进行详细分析:

list_head:在Linux/list.h中定义

在这里插入图片描述

list_head 其实就是一个双向链表

list_for_each: 在linux/list.h 中定义

在这里插入图片描述

这是一个宏定义。其中,pos 是指向 list_head 的指针,而 head 是双链表 list_head 中的指针,定义了从哪里开始遍历这个链表。这个宏的作用就是对一个双向循环链表进行遍历

list_entry: 在 linux/list.h 中定义,也是一个宏定义

在这里插入图片描述

container_of : 在 linux/kernel.h 中定义

在这里插入图片描述

container_of 实现了根据一个结构中的一个成员变量的指针来获取指向整个结构的指针的功能。

5.2 父进程的访问

前面说过子进程每个task_struct都包含一个指向其父进程的task_struct——parent指针,所以访问父进程是非常容易的:

struct task_struct *my_parent=current->parent

6. 进程的创建过程

首先,fork()通过拷贝当前进程创建一个子进程。exex()函数负责读取可执行文件并载入地址空间开始运行。调用fork()的进程为父进程,调用结束后,父进程恢复执行,子进程开始执行。

6.1 fork()与vfork()

讨论fork()与vfork()之前,先说一下“写时拷贝”。

也就是当fork发生时,子进程根本不会去拷贝父进程的内存页面,而是与父进程共享。当子进程或父进程需要修改一个内存页面时,Linux就将这个内存页面复制一份给修改者,然后再去修改,这样从用户的角度看,父子进程根本就没有共享什么内存。COW也就是进程要写共享的内存页面,先复制再改写。

下面看一下创建整个进程的流程:

在这里插入图片描述

先看fork()的实现,最关键的是copy_process()函数,下面看一下函数的实现流程:
在这里插入图片描述

而vfork()与fork()系统调用功能相同,区别在于vfork()不拷贝父进程的页表项。

7. 进程的终止

进程终止两个重大任务:

释放资源

删除进程描述符

释放资源是调用exit()系统调用,然后大部分的任务交给do_exit()完成。

在这里插入图片描述

删除进程描述符主要是通过wait()系统调用,然后大部分任务交给release_task()完成

在这里插入图片描述

7.1 do_exit()

在这里插入图片描述

do_exit()的这些任务完成后,进程处于僵死状态且进程的所有资源被释放掉了,该进程目前所占用的内存有:内核栈、thread_info结构体、task_struct结构体。

7.2 release_task()

release_task()调用后进行以下工作:

1、__exit_singnal()->_unhash_process()->detach_pid(),_exit_signal()释放目前僵死进程所使用的剩余资源,并进行统计和记录。detach_pid()从pidhash上删除进程并从任务列表也删除。

2、release_task()调用put_task_struct()释放进程内核栈和thread_info结构所占的页,并释放进程描述符所占的缓存。

8. 子进程寻父过程

/*
 * When we die, we re-parent all our children.
 * Try to give them to another thread in our thread
 * group, and if no such member exists, give it to
 * the child reaper process (ie "init") in our pid
 * space.
 */
static struct task_struct *find_new_reaper(struct task_struct *father)
{
	struct pid_namespace *pid_ns = task_active_pid_ns(father);
	struct task_struct *thread;

	thread = father;
	while_each_thread(father, thread) {
		//遍历该结束的进程所在线程组的下一个进程
		if (thread->flags & PF_EXITING)//如果得到的下一个进程被标记了 PF_EXITING ,就不符合要求,需要继续遍历
			continue;
		if (unlikely(pid_ns->child_reaper == father))
			/*
			child_reaper 表示进程结束后,需要这个child_reaper指向的进程对这个结束的进程进行托管,
			其中的一个目的是对孤儿进程进行回收。
			若该托管进程是该结束进程本身,就需要重新设置托管进程,
			设置为该结束进程所在线程组的下一个符合要求的进程即可。
			*/
			pid_ns->child_reaper = thread;
		return thread;//在该结束进程所在的线程组中找到符合要求的进程,返回即可
	}

	/*
	如果该结束进程所在的线程组中没有其他的进程,
	函数就返回该结束进程所在命名空间的 child_reaper 指向的托管进程
	(前提是该托管进程不是该结束进程本身)
	*/
	if (unlikely(pid_ns->child_reaper == father)) {
		/*
		如果该结束进程所在命名空间的 child_reaper 指向的托管进程就是该结束进程本身,
		而程序运行至此,说明在该线程组中已经找不到符合要求的进程,
		此时,需要将托管进程设置为 init 进程,供函数返回
		*/
		write_unlock_irq(&tasklist_lock);
		if (unlikely(pid_ns == &init_pid_ns))
			panic("Attempted to kill init!");

		zap_pid_ns_processes(pid_ns);
		write_lock_irq(&tasklist_lock);
		/*
		 * We can not clear ->child_reaper or leave it alone.
		 * There may by stealth EXIT_DEAD tasks on ->children,
		 * forget_original_parent() must move them somewhere.
		 */
		pid_ns->child_reaper = init_pid_ns.child_reaper;
	}

	return pid_ns->child_reaper;
}

总结一下,在 find_new_reaper 中有可能返回以下3种进程作为新父进程:

1、原结束进程所在线程组中的一个符合要求的进程

2、原结束进程所在进程命名空间中 child_reaper指向的托管进程

3、init进程

现在,给子进程找到合适的新父进程了,只需要遍历所有的子进程并为他们设置新的父进程

reaper = find_new_reaper(father);

	list_for_each_entry_safe(p, n, &father->children, sibling) {
		p->real_parent = reaper;
		if (p->parent == father) {
			BUG_ON(task_ptrace(p));
			p->parent = p->real_parent;
		}
		reparent_thread(father, p, &dead_children);
	}

9. 总结

本文围绕进程的概念、进程的结构、进程的表示、进程的创建与终结进行总结和归纳。其中涉及到一些函数调用关系用流程图方式进行展开,而对每一个函数暂未进行详细的描述。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值