Linux内核-进程管理(进程复制)

在执行系统调用如fork/vfork/clone或者启动内核线程kernel_thread均会调用内核的_do_fork函数。下面具体来看下该函数做的具体内容(基于linux-4.19)。

 /*clone_flags: 标志集合,用来制定控制复制过程中的一些属性,最低字节CSIGNAL指定了
   在子进程终止时被发送给父进程的信号掩码;
  stack_start:用户状态下栈的起始地址
  stack_size: 用户状态下栈的大小
  parent_tidptr,child_tidptr分别指向了用户空间中父子进程的PID的两个指针;
*/
long _do_fork(unsigned long clone_flags, 
              unsigned long stack_start,
              unsigned long stack_size, 
              int __user *parent_tidptr,
              int __user *child_tidptr, 
              unsigned long tls)

特别的: fork系统调用使用的clone_flags参数是 SIGCHLD(arm体系定义值为17),由上面可知满足最低字节, 表示子进程终止时将会发送 SIGCHLD 信号给父进程

SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
	return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
	/* can not support in nommu mode */
	return -EINVAL;
#endif
}

另fork采用了写时复制技术,起初父子进程栈地址相同,若存在操作栈地址并写入数据,才会创建新的栈副本。

_do_fork流程图

_do_fork()
   |
   |--->copy_process 生成新进程的实际工作
   |
   |--->get_task_pid & pid_vnr 获取PID
   |
   |--->若存在CLONE_VFORK标志,init_completion初始化vfork完成处理函数
   |
   |--->wake_up_new_task 唤醒新创建的子进程
   |
   |--->若存在CLONE_VFORK标志,wait_for_vfork_done 等待vfork完成
    

复制进程copy_process 

copy_process
    |
    |--->检查标志
    |
    |--->挂起信号,并让接收到的信号延迟到fork后发送
    |
    |--->dup_task_struct(建立副本,复制父进程的task_struct和thread_info实例内容)
    |
    |--->检查资源限制
    |
    |--->初始化新创建的进程task_struct
    |
    |--->sched_fork
    |
    |--->复制/共享进程的各个部分
        |
        |---> copy_semundo,copy_files,copy_fs,copy_sighand,copy_signal,
              copy_mm,copy_namespaces,copy_io,copy_thread_tls

检查标志

//不允许与不同名称空间中的进程共享根目录
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
        return ERR_PTR(-EINVAL);

//在用CLONE_THREAD创建一个线程时,必须用CLONE_SIGHAND激活信号共享;
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
	return ERR_PTR(-EINVAL);


//只有在父子进程间共享虚拟地址空间时(CLONE_VM),才能提供共享的信号处理程序(CLONE_SIGHAND)
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
	return ERR_PTR(-EINVAL);

//全局init的兄弟在退出时仍然是僵尸,因为它们是没有被他们的parent回收。
//要解决这个问题并避免多根进程树,请防止全局初始化和容器初始化创建兄弟进程。
if ((clone_flags & CLONE_PARENT) &&
			current->signal->flags & SIGNAL_UNKILLABLE)
	return ERR_PTR(-EINVAL);

//如果新进程将在不同的pid或用户名称空间中,则不允许它与分支任务共享线程组。
if (clone_flags & CLONE_THREAD) {
	if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
		   (task_active_pid_ns(current) !=
			current->nsproxy->pid_ns_for_children))
		return ERR_PTR(-EINVAL);
}

dup_task_struct

arch_dup_task_struct,setup_thread_stack复制父进程副本

资源检查

if (atomic_read(&p->real_cred->user->processes) >=
		task_rlimit(p, RLIMIT_NPROC)) {
	if (p->real_cred->user != INIT_USER &&
	    !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
		goto bad_fork_free;
}

拥有当前进程的用户,其资源计数器保存在user_struct中,特定用户当前持有进程的数目保存在user_struct->processes中;除非当前用户是root用户或分配了CAP_SYS_RESOURCE/CAP_SYS_ADMIN特殊权限,否则放弃改进程的创建;

sched_fork

 执行与调度程序相关的设置。将此任务分配给CPU。

 copy_xxx

  • 若CLONE_SYSVSEM置位,则使用copy_semundo使用父进程的System V信号量。
  • 若CLONE_FILES置位,则copy_files使用父进程的文件描述符;否则创建新的files结构副本,其中包含的信息与父进程相同;
  • 若CLONE_FS置位,则copy_fs使用父进程的文件系统上下文。
  • 若CLONE_SIGHAND置位,则copy_sighand使用父进程的信号处理程序。
  • 若CLONE_THREAD置位,则copy_signal与父进程共同使用信号处理中不特定于处理程序的部分。
  • 若CLONE_VM置位,则copy_mm让父子进程共享同一地址空间;若CLONE_VM没有置位,并不意味着需要复制父进程的整个地址空间,内核会创建页表的一份副本,通过COW机制进程写时复制;
  • 若CLONE_NEWxyz没有置位,则与父进程共享相应的命名空间,否则创建一个新的;
  • 若CLONE_IO置位,与父对象共享io上下文;

内核线程

内核线程是直接有内核本身启动的进程;内核线程实际上是将内核函数委托为独立的进程,与系统中其他进程并行执行(内核线程也称为内核守护进程);基本上有两种类型的内核线程:

  • 线程启动后一直等待,知道内核请求线程执行某一特定的操作;
  • 线程启动后按周期性间隔运行,检测特定资源的使用,在用量超出或低于预置的限制值时采取行动。内核使用这类线程用于连续检测任务;
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

天未及海宽

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

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

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

打赏作者

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

抵扣说明:

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

余额充值