《Linux内核设计与实现》学习【1】—— 进程管理

1 进程与线程

  进程是处于执行期的程序,线程是进程的执行单元。进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

进程线程
资源开销每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小
包含关系可包含多个线程进程的一部分
内存分配进程之间的地址空间和资源是相互独立的同一进程的线程共享本进程的地址空间和资源

2 描述符及结构

进程描述符:struct task_struct
位置:位于进程内核栈的栈底
获取:通过current

3 进程创建

  写时拷贝:调用fork时,不直接复制资源给新进程,以只读方式共享,在需要写入时,才被复制。
  fork与vfork

forkvfork
资源复制父进程资源与父进程共享
执行不确定子进程先运行,直到exit或exec

3.1 fork流程

  当应用层调用fork、vfork、__clone时,最终都会调用到内核的do_fork,根据传入的参数不同,进行不同的处理。

应用层:fork、vfork、__clone
---------------------------
内核:		do_fork
asmlinkage int sys_fork(struct pt_regs *regs)
{
	return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
}
asmlinkage int sys_vfork(struct pt_regs *regs)
{
	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
}
asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp,
			 int __user *parent_tidptr, int tls_val,
			 int __user *child_tidptr, struct pt_regs *regs)
{
	if (!newsp)
		newsp = regs->ARM_sp;

	return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr);
}

  do_fork中调用copy_process()进行创建工作

static struct task_struct *copy_process(unsigned long clone_flags,
					unsigned long stack_start,
					struct pt_regs *regs,
					unsigned long stack_size,
					int __user *child_tidptr,
					struct pid *pid,
					int trace)
{
	...
	p = dup_task_struct(current);//为新进程分配内核栈
	...
	//每个用户有且只有一个user_struct
	//rlim,对该进程占用的各种资源做限制
	//RLIMIT_NPROC,规定了该进程所属用户可以拥有的进程数
	if (atomic_read(&p->real_cred->user->processes) >=
			p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
		if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
		    p->real_cred->user != INIT_USER)
			goto bad_fork_free;
	}
	...
	copy_flags(clone_flags, p);//复制进程标志flags
	...
	if ((retval = copy_semundo(clone_flags, p)))
		goto bad_fork_cleanup_audit;
	if ((retval = copy_files(clone_flags, p)))//复制打开的文件描述符
		goto bad_fork_cleanup_semundo;
	if ((retval = copy_fs(clone_flags, p)))//复制文件路径
		goto bad_fork_cleanup_files;
	if ((retval = copy_sighand(clone_flags, p)))//复制信号处理
		goto bad_fork_cleanup_fs;
	if ((retval = copy_signal(clone_flags, p)))//复制信号
		goto bad_fork_cleanup_sighand;
	if ((retval = copy_mm(clone_flags, p)))//复制内存描述符
		goto bad_fork_cleanup_signal;
	if ((retval = copy_namespaces(clone_flags, p)))//复制命名空间
		goto bad_fork_cleanup_mm;
	if ((retval = copy_io(clone_flags, p)))
		goto bad_fork_cleanup_namespaces;
	retval = copy_thread(clone_flags, stack_start, stack_size, p, regs);//复制进程上下文相关信息
	if (retval)
		goto bad_fork_cleanup_io;

	if (pid != &init_struct_pid) {
		retval = -ENOMEM;
		pid = alloc_pid(p->nsproxy->pid_ns);//分配pid
		if (!pid)
			goto bad_fork_cleanup_io;

		if (clone_flags & CLONE_NEWPID) {
			retval = pid_ns_prepare_proc(p->nsproxy->pid_ns);
			if (retval < 0)
				goto bad_fork_free_pid;
		}
	}
	...
}

  copy_process()函数完成的工作:
  1)调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同。
  2)检查并确保当前用户的进程数目没有超出资源限制。
  3)子进程着手使自己和父进程区分开来。进程描述符内的不继承父进程的成员被清0或者设为初始值。
  4)子进程状态被置为TASK_UNINTERRUPTIBLE,以保证它不会投入运行。
  5)copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置。
  6)根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。
  7)调用alloc_pid()为新进程分配一个有效的PID。
  8)copy_process()做扫尾工作并返回一个指向子进程的指针。

4 线程

(1)创建线程
  通过clone传入不同的参数(linux/sched.h中定义),指明要共享的资源,例如

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

(2)内核线程
  与普通进程区别:没有独立的地址空间,共享内核地址空间。

5 终结

5.1 do_exit()

进程终结,大部分任务靠do_exit完成。

NORET_TYPE void do_exit(long code)
{
	struct task_struct *tsk = current;
	int group_dead;

	if (unlikely(in_interrupt()))//检查不应该在中断中调用
		panic("Aiee, killing interrupt handler!");
	if (unlikely(!tsk->pid))//0号进程,init不应该退出
		panic("Attempted to kill the idle task!");

	...
	exit_irq_thread();

	exit_signals(tsk);  /* sets PF_EXITING */
	...

	exit_mm(tsk);//释放进程占用的mm_struct
	...

	exit_sem(tsk);
	exit_files(tsk);
	exit_fs(tsk);
	check_stack_usage();
	exit_thread();
	cgroup_exit(tsk, 1);

	...
	exit_notify(tsk, group_dead);

	...
	tsk->state = TASK_DEAD;
	schedule();
	...
}

流程:
  1)将task_struct中的标识成员设置为PF_EXITING;
  2)调用exit_mm()释放进程占用的mm_struct;
  3)调用exit_sem(),使进程离开等待IPC信号的队列;
  4)调用exit_files()和exit_fs(),递减文件描述符、文件系统数据的引用计数。
  5)把task_struct的exit_code设置为进程的返回值,供父进程随时检索;
  6)调用exit_notify()向父进程发送信号;
  7)调用schedule()切换到新进程继续执行。

5.2 删除进程描述符

  do_exit后,进程资源并没有完全释放,当最终需要释放进程描述符时,release_task()会被调用。

void release_task(struct task_struct * p)
{
	struct task_struct *leader;
	int zap_leader;
repeat:
	tracehook_prepare_release_task(p);

	rcu_read_lock();
	atomic_dec(&__task_cred(p)->user->processes);
	rcu_read_unlock();

	proc_flush_task(p);

	write_lock_irq(&tasklist_lock);
	tracehook_finish_release_task(p);
	__exit_signal(p);


	zap_leader = 0;
	leader = p->group_leader;
	if (leader != p && thread_group_empty(leader) && leader->exit_state == EXIT_ZOMBIE) {
		BUG_ON(task_detached(leader));
		do_notify_parent(leader, leader->exit_signal);

		zap_leader = task_detached(leader);

		if (zap_leader)
			leader->exit_state = EXIT_DEAD;
	}

	write_unlock_irq(&tasklist_lock);
	release_thread(p);
	call_rcu(&p->rcu, delayed_put_task_struct);

	p = leader;
	if (unlikely(zap_leader))
		goto repeat;
}

完成以下工作:
  1)调用__exit_signal(),该函数调用_unhash_process(),后者又调用detach_pid()从pidhash上删除该进程,同时也要从任务列表中删除该进程。
  2)_exit_signal()释放目前僵死进程所使用的剩余资源,并进行最终统计和记录。
  3)如果这个进程是线程组的最后一个进程,并且领头进程已经死掉,那么release_task()就要通知僵死的领头进程的父进程。
  4)release_task()调用put_task_struct()释放进程内核栈和thread_info结构所占的页,并释放task_struct所占的slab高速缓存。
  至此,进程描述符和所有进程独享的资源就全部释放掉了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值