创建进程

来源:互联网 发布:鲜活的数据 pdf 下载 编辑:程序博客网 时间:2024/06/05 08:49

进程:处于执行期的程序 相当于“进程=程序+执行” 但是进程不局限于一段可执行代码(代码段) 还包括进程需要的其他资源 例如打开的文件 挂起的信号量 内存管理 处理器状态 一个或者多个线程 数据段之类的 进程控制块就是task_struct

进程和线程的区别 :线程是操作系统调度的最小单位 进程拥有独立的资源空间而线程则共享进程的资源空间 但是linux内核并没有特别的调度算法或定义特别的数据结构来标识线程 线程和进程都是用相同的进程pcb数据结构 内核使用clone来创建线程

init进程 所有进程的祖先 在linux系统启动的时候 start_kernel()函数时 静态创建
所有的核心数据结构都预先静态赋值 通过INIT_TASK宏来赋值

fork系统调用

创建进程的时候 会使用fork或者vfork等系统调用来实现 线程是使用clone来实现 但是这个三个系统调用函数都是使用同一个函数do_fork()来实现的 在fork.c中实现

long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
return _do_fork(clone_flags, stack_start, stack_size,
parent_tidptr, child_tidptr, 0);
}

应用程序在用户空间创建进程有两种场景:
1、创建的子进程和父进程共用一个elf文件。这种情况,elf文件中的正文段中的部分代码是父进程和子进程共享,部分代码是属于父进程,部分代码属于子进程。这种情况适合于大多数的网络服务程序。
2、创建的子进程需要加载自己的elf文件。例如shell。

对于有MMU的处理器 可以使用fork创建
ifdef __ARCH_WANT_SYS_FORK
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
}
#endif

对于没有MMU的处理器 也就没有虚拟地址、页表这些概念,也就无法实现COW版本的fork。
内核采用的vfork + exec来实现fork

fork和vfork的区别
完全复制父进程的资源的开销非常大,特别是对于场景2,所有的开销都是完全的没有任何意义,因为系统load新的elf文件后,会重建text、data等segment。不过,在引入COW(copy-on-write)技术后,fork的开销其实也不算特别大,大部分的copy都是通过share完成的,主要的开销集中在复制父进程的页表上。在某些特定的场合下,如果程序想把复制父进程页表这一点开销也节省掉,那么linux还提供了vfork函数。Vfork和fork是类似的,除了下面两点:
1、阻塞父进程
2、不复制父进程的页表
之所以vfork要阻塞父进程是因为vfork后父子进程使用的是完全相同的memory descriptor, 也就是说使用的是完全相同的虚拟内存空间, 包括栈也相同。所以两个进程不能同时运行, 否则栈就乱掉了。所以vfork后, 父进程是阻塞的,直到调用了exec系列函数或者exit函数后。这时候,子进程的mm(old_mm)需要释放掉,不再与父进程共用了,这时候就可以解 除父进程的阻塞状态。

#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
0, NULL, NULL, 0);
}
#endif

CLONE_VFORK 表示父进程被挂起 直到子进程释放掉虚拟内存
CLONE_VM 表示和父进程共享虚拟内存空间

clone创建的代码
#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
unsigned long, tls,
int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
int, stack_size,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
#endif
{
return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}
#endif

可以见到 这几个函数都是通过_do_fork函数实现的 下面我们来分析这个函数到底是怎么实现的
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)
{
struct task_struct *p;
int trace = 0;
long nr;

/* * 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. */

/* 设置ptrace这样的系统调用 创建进程是否向tracer上报信号 上报哪些信号 */
if (!(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;}

/* 调用copy_process创建一个进程 */
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls);
/*
* 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;
struct pid *pid;

    trace_sched_process_fork(current, p);    pid = get_task_pid(p, PIDTYPE_PID);    nr = pid_vnr(pid);    if (clone_flags & CLONE_PARENT_SETTID)        put_user(nr, parent_tidptr);    if (clone_flags & CLONE_VFORK) {        p->vfork_done = &vfork;        init_completion(&vfork);        get_task_struct(p);    }    wake_up_new_task(p);    /* forking complete and child started to run, tell ptracer */    if (unlikely(trace))        ptrace_event_pid(trace, pid);    if (clone_flags & CLONE_VFORK) {        if (!wait_for_vfork_done(p, &vfork))            ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);    }    put_pid(pid);} else {    nr = PTR_ERR(p);}return nr;

}

这里主要分析copy_process函数
static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid,
int trace,
unsigned long tls)
{
int retval;
struct task_struct *p;
void *cgrp_ss_priv[CGROUP_CANFORK_COUNT] = {};

if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))    return ERR_PTR(-EINVAL);if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))    return ERR_PTR(-EINVAL);/* * Thread groups must share signals as well, and detached threads * can only be started up within the thread group. */if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))    return ERR_PTR(-EINVAL);/* * Shared signal handlers imply shared VM. By way of the above, * thread groups also imply shared VM. Blocking this case allows * for various simplifications in other code. */if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))    return ERR_PTR(-EINVAL);/* * Siblings of global init remain as zombies on exit since they are * not reaped by their parent (swapper). To solve this and to avoid * multi-rooted process trees, prevent global and container-inits * from creating siblings. */if ((clone_flags & CLONE_PARENT) &&            current->signal->flags & SIGNAL_UNKILLABLE)    return ERR_PTR(-EINVAL);/* * If the new process will be in a different pid or user namespace * do not allow it to share a thread group with the forking task. */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);}retval = security_task_create(clone_flags);if (retval)    goto fork_out;retval = -ENOMEM;p = dup_task_struct(current);if (!p)    goto fork_out;

最先是判断是否有同时设置CLONE_NEWNS和CLONE_FS这个两个宏
CLONE_NEWNS是指父进程和子进程不共享mount namespace 设置了这个宏的话就是表示子进程要创建自己的mountnamespace (注意:子进程的这个private mount namespace仍然用父进程的mount namespace来初始化,只是之后,子进程和父进程的mount namespace就分道扬镳了,这时候,子进程的mount或者umount的动作将不会影响到父进程)
而CLONE_FS表示父子进程共享文件系统信息 ,如果设定了该flag,那么父子进程共享文件系统信息,如 果不设定该flag,那么子进程则copy父进程的文件系统信息,之后,子进程调用chroot,chdir,umask来改变文件系统信息将不会影响到 父进程。
在内核中,CLONE_NEWNS和CLONE_FS是排他的。一个进程的文件系统信息在内核中是用struct fs_struct来抽象,这个结构中就有mount namespace的信息,因此如果想共享文件系统信息,其前提条件就是要处于同一个mount namespace中。
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);

然后是判断CLONE_NEWUSER 和CLONE_FS是否同时设置
CLONE_NEWUSER这个flag是和user namespace相关的标识,在通过clone函数fork进程的时候,我们可以选择clone之前的user namespace,当然也可以通过传递该标识来创建新的user namespace。user namespace是linux kernel支持虚拟化之后引入的一个机制,可以允许系统创建不同的user namespace(之前系统只有一个user namespace)。user namespace用来管理user ID和group ID的映射。一个user namespace形成一个container,该user namespace的user ID和group ID的权限被限定在container内部。也就是说,某一个user namespace中的root(UID等于0)并非具备任意的权限,他仅仅是在该user namespace中是privileges的,在该user namespace之外,该user并非是特权用户。
CLONE_NEWUSER|CLONE_FS的组合会导致一个系统漏洞,可以让一个普通用户窃取到root的权限
if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
return ERR_PTR(-EINVAL);

POSIX规定一个进程内部的多个thread要共享一个PID,但是,在linux kernel中,不论是进程还是线程,都是会分配一个task struct并且分配一个唯一的PID(这时候,PID其实就是thread ID)。这样,为了满足POSIX的线程规定,linux引入了线程组的概念,一个进程中的所有线程所共享的那个PID被称为线程组ID,也就是task struct中的tgid成员。因此,在linux kernel中,线程组ID(tgid,thread group id)就是传统意义的进程ID。对于sys_getpid系统调用,linux内核返回了tgid。对于sys_gettid系统调用,本意是要求返回线 程ID,在linux内核中,返回了task struct的pid成员。一言以蔽之,POSIX的进程ID就是linux中的线程组ID。POSIX的线程ID也就是linux中的pid。
在了解了线程组ID和线程ID之后,我们来看一看CLONE_THREAD这个flag。这个flag被设定的话,则表示被创建的子进程与父进程在一个线程组中。否则会创建一个新的线程组。
如果设定CLONE_SIGHAND这个flag,则表示创建的子进程与父进程共享相同的信号处理(signal handler)表。线程组应该共享signal handler(POSIX规定),因此,当设定了CLONE_THREAD后必须同时设定CLONE_SIGHAND
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);

设定了CLONE_SIGHAND表示共享signal handler,前提条件就是要共享地址空间(也就是说必须设定CLONE_VM),否则,无法共享signal handler。因为如果不共享地址空间,即便是同样地址的handler,其物理地址都是不一样的。
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);

CLONE_PARENT这个flag表示新fork的进程想要和创建该进程的cloner拥有同样的父进程。
SIGNAL_UNKILLABLE这个flag是for init进程的,其他进程不会设定这个flag。
Linux kernel会静态定义一个init task,该task的pid是0,被称作swapper(其实就是idle进程,在系统没有任何进程可调度的时候会执行该进程)。系统中的所有进程(包括内核线程)由此开始。对于用户空间进程,内核会首先创建init进程,所有其他用户空间的进程都是由init进程派生出来的。因此init进程要负责为所有用户空间的进程处理后事(否则会变成僵 尸进程)。但是如果init进程想要创建兄弟进程(其父亲是swapper),那么该进程无法由init进程回收,其父亲swapper进程也不会收养用户空间创建的init的兄弟进程,这种情况下,这类进程退出都会变成zombie,因此要杜绝。
if ((clone_flags & CLONE_PARENT) &&
current->signal->flags & SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL);

这一段代码是和LinuxSecurity Modules相关的。LinuxSecurity Modules是一个安全框架,允许各种安全模型插入到内核。大家熟知的一个计算机安全模型就是selinux。具体这里就不再描述。如果本次操作通过了 安全校验,那么后续的操作可以顺利进行
retval = security_task_create(clone_flags);
if (retval)
goto fork_out;

然后就调用dup_task_struct()函数创建一个task_struct
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
/* 分配task_struct 和thread_info 结构体 */
struct task_struct *tsk;
struct thread_info *ti;
int node = tsk_fork_get_node(orig);
int err;

tsk = alloc_task_struct_node(node);if (!tsk)    return NULL;ti = alloc_thread_info_node(tsk, node);if (!ti)    goto free_tsk;

/* 将父进程的task_struct数据结构的内容复制到子进程中 */
err = arch_dup_task_struct(tsk, orig);
if (err)
goto free_ti;

tsk->stack = ti;

#ifdef CONFIG_SECCOMP
/*
* We must handle setting up seccomp filters once we’re under
* the sighand lock in case orig has changed between now and
* then. Until then, filter must be NULL to avoid messing up
* the usage counts on the error path calling free_task.
*/
tsk->seccomp.filter = NULL;
#endif
/* 将父进程的thread_info 数据结构内容复制到子进程的thread_info */
setup_thread_stack(tsk, orig);
clear_user_return_notifier(tsk);
/* 清除thread_info->flags中的TIF_NEED_RESCHED标志位 */
clear_tsk_need_resched(tsk);
set_task_stack_end_magic(tsk);

#ifdef CONFIG_CC_STACKPROTECTOR
tsk->stack_canary = get_random_int();
#endif

/* * One for us, one for whoever does the "release_task()" (usually * parent) */atomic_set(&tsk->usage, 2);

#ifdef CONFIG_BLK_DEV_IO_TRACE
tsk->btrace_seq = 0;
#endif
tsk->splice_pipe = NULL;
tsk->task_frag.page = NULL;
tsk->wake_q.next = NULL;

account_kernel_stack(ti, 1);return tsk;

free_ti:
free_thread_info(ti);
free_tsk:
free_task_struct(tsk);
return NULL;
}

接着copy_process
retval = -EAGAIN;
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;
}
current->flags &= ~PF_NPROC_EXCEEDED;
/* 复制父进程的证书 */
retval = copy_creds(p, clone_flags);
if (retval < 0)
goto bad_fork_free;

/* * If multiple threads are within copy_process(), then this check * triggers too late. This doesn't hurt, the check is only there * to stop root fork bombs. */retval = -EAGAIN;

/* 如果超过系统最多可以拥有的进程个数 就分配失败 */
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;

delayacct_tsk_init(p);  /* Must remain after dup_task_struct() */

//取消使用超级用户权限 并且不是一个worker线程 worker线程是由工作队列机制创建
p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
//表示这个进程还不能执行
p->flags |= PF_FORKNOEXEC;
//初始化新进程的子进程链表和兄弟进程链表
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
//对PREEMPT_RCU 和 TASKS_RCU进行初始化
rcu_copy_process(p);
//然后进行一些新进程task_struct的成员初始化
p->vfork_done = NULL;
spin_lock_init(&p->alloc_lock);

init_sigpending(&p->pending);p->utime = p->stime = p->gtime = 0;p->utimescaled = p->stimescaled = 0;prev_cputime_init(&p->prev_cputime);

#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
seqlock_init(&p->vtime_seqlock);
p->vtime_snap = 0;
p->vtime_snap_whence = VTIME_SLEEPING;
#endif

#if defined(SPLIT_RSS_COUNTING)
memset(&p->rss_stat, 0, sizeof(p->rss_stat));
#endif

p->default_timer_slack_ns = current->timer_slack_ns;task_io_accounting_init(&p->ioac);acct_clear_integrals(p);posix_cpu_timers_init(p);p->start_time = ktime_get_ns();p->real_start_time = ktime_get_boot_ns();p->io_context = NULL;p->audit_context = NULL;threadgroup_change_begin(current);cgroup_fork(p);

#ifdef CONFIG_NUMA
p->mempolicy = mpol_dup(p->mempolicy);
if (IS_ERR(p->mempolicy)) {
retval = PTR_ERR(p->mempolicy);
p->mempolicy = NULL;
goto bad_fork_cleanup_threadgroup_lock;
}
#endif
#ifdef CONFIG_CPUSETS
p->cpuset_mem_spread_rotor = NUMA_NO_NODE;
p->cpuset_slab_spread_rotor = NUMA_NO_NODE;
seqcount_init(&p->mems_allowed_seq);
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
p->irq_events = 0;
p->hardirqs_enabled = 0;
p->hardirq_enable_ip = 0;
p->hardirq_enable_event = 0;
p->hardirq_disable_ip = THIS_IP;
p->hardirq_disable_event = 0;
p->softirqs_enabled = 1;
p->softirq_enable_ip = THIS_IP;
p->softirq_enable_event = 0;
p->softirq_disable_ip = 0;
p->softirq_disable_event = 0;
p->hardirq_context = 0;
p->softirq_context = 0;
#endif

p->pagefault_disabled = 0;

#ifdef CONFIG_LOCKDEP
p->lockdep_depth = 0; /* no locks held yet */
p->curr_chain_key = 0;
p->lockdep_recursion = 0;
#endif

#ifdef CONFIG_DEBUG_MUTEXES
p->blocked_on = NULL; /* not blocked yet */
#endif
#ifdef CONFIG_BCACHE
p->sequential_io = 0;
p->sequential_io_avg = 0;
#endif

接着看
retval = sched_fork(clone_flags, p);
if (retval)
goto bad_fork_cleanup_policy;

int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
unsigned long flags;
int cpu = get_cpu();
//初始化调度实体 sched_entity
__sched_fork(clone_flags, p);
/*
* We mark the process as running here. This guarantees that
* nobody will actually run it, and a signal or other external
* event cannot wake it up and insert it on the runqueue either.
*/
//设置进程状态为TASK_RUNNING
p->state = TASK_RUNNING;

/* * Make sure we do not leak PI boosting priority to the child. */

//设置进程的优先级
p->prio = current->normal_prio;

/* * Revert to default priority/policy on fork if requested. */

//如果父进程使用sched_setscheduler设置了sched_reset_on_fork 就会恢复优先级和调度策略
if (unlikely(p->sched_reset_on_fork)) {
if (task_has_dl_policy(p) || task_has_rt_policy(p)) {
p->policy = SCHED_NORMAL;
p->static_prio = NICE_TO_PRIO(0);
p->rt_priority = 0;
} else if (PRIO_TO_NICE(p->static_prio) < 0)
p->static_prio = NICE_TO_PRIO(0);

    p->prio = p->normal_prio = __normal_prio(p);    set_load_weight(p);    /*     * We don't need the reset flag anymore after the fork. It has     * fulfilled its duty:     */    p->sched_reset_on_fork = 0;}if (dl_prio(p->prio)) {    put_cpu();    return -EAGAIN;} else if (rt_prio(p->prio)) {    p->sched_class = &rt_sched_class;} else {    p->sched_class = &fair_sched_class;}if (p->sched_class->task_fork)    p->sched_class->task_fork(p);/* * The child is not yet in the pid-hash so no cgroup attach races, * and the cgroup is pinned to this child due to cgroup_fork() * is ran before sched_fork(). * * Silence PROVE_RCU. */

//将cpu写进thread_info中的cpu成员中
raw_spin_lock_irqsave(&p->pi_lock, flags);
set_task_cpu(p, cpu);
raw_spin_unlock_irqrestore(&p->pi_lock, flags);

#ifdef CONFIG_SCHED_INFO
if (likely(sched_info_on()))
memset(&p->sched_info, 0, sizeof(p->sched_info));
#endif
#if defined(CONFIG_SMP)
p->on_cpu = 0;
#endif
init_task_preempt_count(p);
#ifdef CONFIG_SMP
plist_node_init(&p->pushable_tasks, MAX_PRIO);
RB_CLEAR_NODE(&p->pushable_dl_tasks);
#endif

put_cpu();return 0;

}
get_cpu函数实现如下 先关闭内核抢占
#define get_cpu() ({ preempt_disable(); smp_processor_id(); })

#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
关闭内核抢占主要是设置preempt_count的值为非0 就开启了抢占
如果是开启了内核抢占就是
#ifdef CONFIG_PREEMPT
#define preempt_enable() \
do { \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \
__preempt_schedule(); \
} while (0)
先将preempt_count值减1 并且判断是否为0 何检查thread_info中的TIF_NEED_RESCHED标志位 如果被置位就调用schedule()完成调度抢占

回到cpopy_process函数
retval = perf_event_init_task(p);
if (retval)
goto bad_fork_cleanup_policy;
retval = audit_alloc(p);
if (retval)
goto bad_fork_cleanup_perf;
/* copy all the process information */
shm_init_task(p);
retval = copy_semundo(clone_flags, p);
if (retval)
goto bad_fork_cleanup_audit;
retval = copy_files(clone_flags, p);
if (retval)
goto bad_fork_cleanup_semundo;
retval = copy_fs(clone_flags, p);
if (retval)
goto bad_fork_cleanup_files;
retval = copy_sighand(clone_flags, p);
if (retval)
goto bad_fork_cleanup_fs;
retval = copy_signal(clone_flags, p);
if (retval)
goto bad_fork_cleanup_sighand;
retval = copy_mm(clone_flags, p);
if (retval)
goto bad_fork_cleanup_signal;
retval = copy_namespaces(clone_flags, p);
if (retval)
goto bad_fork_cleanup_mm;
retval = copy_io(clone_flags, p);
if (retval)
goto bad_fork_cleanup_namespaces;
retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
if (retval)
goto bad_fork_cleanup_io;
然后就是拷贝父进程的打开的文件信息 信号系统等等