一、进程管理

一:进程和线程的概念:

进程:是执行期间的程序,不仅仅局限于一段可执行的代码(text section),还要包含其他的资源,eg.文件、挂起的信号量,数据,处理器状态等等。还有一个或者多个具有内存映射的内存地址空间或者一个或者多个执行线程(thread of execution)。全局变量的数据端。。。等等。进程也称做task

线程(thread):进程中活动的对象,每个线程都有一个独立的程序计数器、进程栈、进程寄存器。线程是内核调度的对象。在Linux中,并不严格的区分线程和进程,一般而言,把线程看作一种特殊的进程。

二、进程描述符&&任务结构

内核将进程列表放入任务队列中(task list),为一个双向循环列表,每一项都是进程描述符(process descriptor)结构,类型为task_struct,该结构的定义位于 <linux/sched.h>,包含进程运行时的所有信息,占据内存1.7KB

1、分配进程描述符

(1)、内容

Linux通过slab分配器分配task_struct结构,slab在于实现快速进程创建,关于slab的详细介绍,有篇文章,记录一下https://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/ 在<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 */

	struct exec_domain	*exec_domain;	/* execution domain */
	mm_segment_t		addr_limit;	/* thread address space */
	unsigned		cpu;		/* current CPU */
	int			preempt_count; /* 0 => preemptable, <0 => BUG */

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

	struct restart_block	restart_block;
};

slab分配器动态生成时,只需在(相应进程的内核进程栈)的栈顶(栈向上增长)或者栈底(栈向下增长)创建新的struct thread_info就可以。

e.g(向下增长)

___________________________________

|栈顶

|

|

|____________________________________     (当前堆栈指针)

|

|

|struct task_struct *task                                                            (current_thread_info)

(2)、存放

系统需保存当前进程的task_struct,以备适用,因此在系统中提供了 current的宏,用来获取当前运行进程的task_struct结构体,但current的具体实现是不一样的,在X86之类 Reg比较紧张的处理器上,是在内核栈尾端创建thread_info结构,通过计算偏移,查找出task,而在PowerPC中,是通过专门寄存器来实现。

PID:Process Identification Value,用来标识唯一进程,在<linux/threads.h>中定义最大的数值,一般为32768,

#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)

在使用中,可以通过修改 /proc/sys/kernel/pid_max来修改上限

2、进程的状态

(1)、定义

进程状态是task_struct中的一个定义,描述了该task的运行状态,定义如下:

volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */

关于具体的状态,有如下定义

/*
 * Task state bitmask. NOTE! These bits are also
 * encoded in fs/proc/array.c: get_task_state().
 *
 * We have two separate sets of flags: task->state
 * is about runnability, while task->exit_state are
 * about the task exiting. Confusing, but this way
 * modifying one set can't modify the other one by
 * mistake.
 */
#define TASK_RUNNING		0
#define TASK_INTERRUPTIBLE	1
#define TASK_UNINTERRUPTIBLE	2
#define __TASK_STOPPED		4
#define __TASK_TRACED		8
/* in tsk->exit_state */
#define EXIT_ZOMBIE		16
#define EXIT_DEAD		32
/* in tsk->state again */
#define TASK_DEAD		64
#define TASK_WAKEKILL		128
#define TASK_WAKING		256
#define TASK_STATE_MAX		512

一一对应来看,Linux使用一个long类型的state数据来标识状态,如果为0,则说明当前的task是可执行的,如果state的值大于0,说明当前进程是停止的,通过和不同的状态相与就能找到具体是什么状态。其中,每一层的含义为:

TASK_RUNNING /*进程是可执行的:即程序或许正在执行,或者在运行队列中等待调度,是进程在用户空间中唯一可能的状态*/
TASK_INTERRUPTIBLE /*进程被阻塞,正在睡眠,并且是可中断的。当接收到相应的信号,就可以被唤醒并随时准备投入运行*/
TASK_UNINTERRUPTIBLE /*与TASK_INTERRUPTIBLE状态类似,但该种状态的时候,进程是不会对信号进行相应的,此时进程可能在进行必要的任务,必须等待*/

设置进程的状态,可以使用set_task_state(task,state)命令来实现。

注意:一般的程序在用户空间中执行,当程序执行了系统调用或者触发异常之后,该进程就陷入内核空间,称 内核“代表进程执行”并处于进程上下文中,在此时current宏是有效的,除非调度器调度了优先级更高的进程,否则内核退出时,会回复在用户空间继续执行。

(2)、进程家族树

系统中,所有的进程都是PID为1的init进程的后代。系统中的每一个进程都有一个相应的父进程,同时,有可以拥有零个或者多个子进程。在<linux/sched.h>中的struct task_struct结构体的定义中,有这样一部分

	/* 
	 * pointers to (original) parent process, youngest child, younger sibling,
	 * older sibling, respectively.  (p->father can be replaced with 
	 * p->real_parent->pid)
	 */
	struct task_struct *real_parent; /* real parent process */
	struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */
	/*
	 * children/sibling forms the list of my natural children
	 */
	struct list_head children;	/* list of my children */
	struct list_head sibling;	/* linkage in my parent's children list */
	struct task_struct *group_leader;	/* threadgroup leader */

因此我们可以使用

struct task_struct *my_parent = current->parent;

来获取其父进程的进程描述符。

3、进程的创建

1、总述:

Unix进程的创建,将整个进程分解为fork()和exec()两个步骤,fork()通过拷贝当前的进程创建一个子进程。其与父进程的区别仅仅在于PID,PPID,一些资源和统计量(挂起的信号等等)。exec负责读取可执行文件并将其在于地址空间中运行,

2、写时拷贝

传统的fork()是将所有的资源复制给新创建的进程。效率太低,比方说在子进程立刻创建新进程的时候,拷贝的这些数据都没有意义。Linux的fork()使用了写时拷贝的技术,所谓的写时拷贝,就是说,在复制的时候,内核不会复制整个进程的地址空间,在开始时候,数据都是只读方式共享的,只有在数据需要写入的时候,数据才会被复制到子进程当中,从而实现各个进程拥有自己的拷贝。这样以来,fork()函数的实际开销就在于复制父进程的页表,以及给子线程分配唯一 的进程描述符。

3、fork()

Linux通过clone()系统调用去实现fork(),包括vfork(),__clone()也是如此,然后clone()去调用do_fork(),do_fork()负责完成权限的检查,然后调用copy_process()函数,再然后,开始运行程序。do_fork()代码如下

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      struct pt_regs *regs,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr)
{
	struct task_struct *p;
	int trace = 0;
	long nr;

	/*
	 * Do some preliminary argument and permissions checking before we
	 * actually start allocating stuff
	 */
	if (clone_flags & CLONE_NEWUSER) {
		if (clone_flags & CLONE_THREAD)
			return -EINVAL;
		/* hopefully this check will go away when userns support is
		 * complete
		 */
		if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) ||
				!capable(CAP_SETGID))
			return -EPERM;
	}

	/*
	 * Determine whether and which event to report to ptracer.  When
	 * called from kernel_thread or CLONE_UNTRACED is explicitly
	 * requested, no event is reported; otherwise, report if the event
	 * for the type of forking is enabled.
	 */
	if (likely(user_mode(regs)) && !(clone_flags & CLONE_UNTRACED)) {
		if (clone_flags & CLONE_VFORK)
			trace = PTRACE_EVENT_VFORK;
		else if ((clone_flags & CSIGNAL) != SIGCHLD)
			trace = PTRACE_EVENT_CLONE;
		else
			trace = PTRACE_EVENT_FORK;

		if (likely(!ptrace_event_enabled(current, trace)))
			trace = 0;
	}

	p = copy_process(clone_flags, stack_start, regs, stack_size,
			 child_tidptr, NULL, trace);
	/*
	 * Do this prior waking up the new thread - the thread pointer
	 * might get invalid after that point, if the thread exits quickly.
	 */
	if (!IS_ERR(p)) {
		struct completion vfork;

		trace_sched_process_fork(current, p);

		nr = task_pid_vnr(p);

		if (clone_flags & CLONE_PARENT_SETTID)
			put_user(nr, parent_tidptr);

		if (clone_flags & CLONE_VFORK) {
			p->vfork_done = &vfork;
			init_completion(&vfork);
		}

		audit_finish_fork(p);

		/*
		 * We set PF_STARTING at creation in case tracing wants to
		 * use this to distinguish a fully live task from one that
		 * hasn't finished SIGSTOP raising yet.  Now we clear it
		 * and set the child going.
		 */
		p->flags &= ~PF_STARTING;

		wake_up_new_task(p);

		/* forking complete and child started to run, tell ptracer */
		if (unlikely(trace))
			ptrace_event(trace, nr);

		if (clone_flags & CLONE_VFORK) {
			freezer_do_not_count();
			wait_for_completion(&vfork);
			freezer_count();
			ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);
		}
	} else {
		nr = PTR_ERR(p);
	}
	return nr;
}

在copy_process()中, 完成以下的工作:

(1)调用dup_task_struct()创建新的内核栈、thread_info、task_struct结构,并且与父进程相同

(2)各种检查数据范围

(3)设置子进程状态为TASK_UNINTERRUPTIBLE;

(4)copy——process更新task_struct的flags成员

(5)alloc_pid为新进程分配有效PID

(7)传递clone参数标志,扫尾工作,并返回指向子进程的指针。

4、vfork()

除了不拷贝父进程的页表项,其他与fork相同

4、线程

Linux中,没有单独的线程的概念,所有的线程都是特殊的进程

1、线程的创建

其创建与进程一样,同样是调用clone系统调用来实现,只是在调用的时候,会通过传入一些标志参数来指明要共享的资源。

其系统调用为

clone(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND,0)

而对比下的普通的fork()的实现是:

clone(SIGCHLD,0)

vfork()的实现是

clone(CLONE_VFORK|CLONE_VM|SIGCHLD,0)

所有的参数说明为。

/*
 * cloning flags:
 */
#define CSIGNAL		0x000000ff	/* signal mask to be sent at exit */
#define CLONE_VM	0x00000100	/* set if VM shared between processes */
#define CLONE_FS	0x00000200	/* set if fs info shared between processes */
#define CLONE_FILES	0x00000400	/* set if open files shared between processes */
#define CLONE_SIGHAND	0x00000800	/* set if signal handlers and blocked signals shared */
#define CLONE_PTRACE	0x00002000	/* set if we want to let tracing continue on the child too */
#define CLONE_VFORK	0x00004000	/* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT	0x00008000	/* set if we want to have the same parent as the cloner */
#define CLONE_THREAD	0x00010000	/* Same thread group? */
#define CLONE_NEWNS	0x00020000	/* New namespace group? */
#define CLONE_SYSVSEM	0x00040000	/* share system V SEM_UNDO semantics */
#define CLONE_SETTLS	0x00080000	/* create a new TLS for the child */
#define CLONE_PARENT_SETTID	0x00100000	/* set the TID in the parent */
#define CLONE_CHILD_CLEARTID	0x00200000	/* clear the TID in the child */
#define CLONE_DETACHED		0x00400000	/* Unused, ignored */
#define CLONE_UNTRACED		0x00800000	/* set if the tracing process can't force CLONE_PTRACE on this clone */
#define CLONE_CHILD_SETTID	0x01000000	/* set the TID in the child */
/* 0x02000000 was previously the unused CLONE_STOPPED (Start in stopped state)
   and is now available for re-use. */
#define CLONE_NEWUTS		0x04000000	/* New utsname group? */
#define CLONE_NEWIPC		0x08000000	/* New ipcs */
#define CLONE_NEWUSER		0x10000000	/* New user namespace */
#define CLONE_NEWPID		0x20000000	/* New pid namespace */
#define CLONE_NEWNET		0x40000000	/* New network namespace */
#define CLONE_IO		0x80000000	/* Clone io context */

 2、内核线程

(1)、创建内核线程

#define kthread_create(threadfn, data, namefmt, arg...) \
kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)

(2)、启动内核线程

#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})

 

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注