カーネルプロセスのレプリケーション

16243 ワード

forkシステムで作成プロセスを呼び出すと、最終的にカーネルのdo_に入ります.fork関数、この関数の大部分の仕事はすべてプロセスの複製で、大部分の仕事を関数copyに委託しますプロセス関数で完了します.このブログでは、プロセスのレプリケーションについて主に説明します.
次はいくつかのセグメントに分けられ、コードにはcopy全体が含まれています.プロセス関数
一、標識検査
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 retval;
	struct task_struct *p;
	int cgroup_callbacks_done = 0;

	if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|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);

	retval = security_task_create(clone_flags);
	if (retval)
		goto fork_out;

これは関数の最初の部分で、まず入力されたパラメータの検査を行います.主に:
  • プロセスを作成する場合、新しい名前空間(CLONE_NEWNS)を作成するとともに、親プロセスとすべてのファイルシステム情報(CLONE_FS)を共有することは許可されません.ファイルシステムの共有が要求されます.
  • でCLONEを使っていますTHREADフラグの場合はCLONE_を使用する必要がありますSIGHANDフラグであり、後者は同じ信号処理テーブルを共有することを示す.
  • CLONEを使用していますSIGHANDマークの場合は、CLONE_を使用する必要がありますVMフラグは、サブプロセスとあなたのプロセスが仮想アドレス空間を共有していることを示し、共有信号処理プログラムを提供するのはこの時だけです.

  • 二、コピーの作成
    	retval = -ENOMEM;
    	p = dup_task_struct(current);
    	if (!p)
    		goto fork_out;
    

    dup_task_structは、親プロセスのコピーを作成するために使用されます.関数は次のとおりです.
    static struct task_struct *dup_task_struct(struct task_struct *orig)
    {
    	struct task_struct *tsk;
    	struct thread_info *ti;
    	int err;
    
    	prepare_to_copy(orig);
    
    	tsk = alloc_task_struct();
    	if (!tsk)
    		return NULL;
    
    	ti = alloc_thread_info(tsk);
    	if (!ti) {
    		free_task_struct(tsk);
    		return NULL;
    	}
    
    	*tsk = *orig;//             
    	tsk->stack = ti;
    
    	err = prop_local_init_single(&tsk->dirties);
    	if (err) {
    		free_thread_info(ti);
    		free_task_struct(tsk);
    		return NULL;
    	}
    
    	setup_thread_stack(tsk, orig);
    
    #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);//       2,               。
    	atomic_set(&tsk->fs_excl, 0);
    #ifdef CONFIG_BLK_DEV_IO_TRACE
    	tsk->btrace_seq = 0;
    #endif
    	tsk->splice_pipe = NULL;
    	return tsk;
    }
  • alloc_を呼び出すtask_structは、新しいプロセスにプロセス構造を割り当て、tskポインタを返します.
  • は、新しいプロセスにコアステートスタック、すなわちtsk->stackを割り当てます.スタックとthread_infoは、統合構造に一緒に保存されます.thread_infoは、プロセスに必要なプロセッサ固有の最下位情報を保存するために使用され、
    struct thread_info {
    	struct task_struct	*task;		/*        */
    	unsigned long		flags;
    	struct exec_domain	*exec_domain;	/*      */
    	int			preempt_count;	/*             */
    	__u32 cpu; /*          CPU   */
    	struct restart_block    restart_block;//        
    };
    でプロセスのスタックとthread_を定義する.infoの連合体定義は、
    union thread_union {
    	struct thread_info thread_info;
    	unsigned long stack[THREAD_SIZE/sizeof(long)];
    };
    がスタックを割り当てた後、setup_を呼び出すthread_stackはスタック内のレイアウトを決定します.この関数は、親プロセスのthread_をinfo(プロセス記述構造)値はtskのプロセス記述構造にコピーされます.その後、tskのプロセス記述子のtaskドメインをtskに変更します.
    #define task_thread_info(task)	((struct thread_info *)(task)->stack)
    
    static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org)
    {
    	*task_thread_info(p) = *task_thread_info(org);
    	task_thread_info(p)->task = p;
    }
    setup_thread_stackを実行した後、親子プロセスはstackのポインタ以外は全く同じです.
  • 三、検査プロセス数作成制限
    	rt_mutex_init_task(p);//      
    
    #ifdef CONFIG_TRACE_IRQFLAGS
    	DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
    	DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
    #endif
    	retval = -EAGAIN;
    	if (atomic_read(&p->user->processes) >=
    			p->signal->rlim[RLIMIT_NPROC].rlim_cur) {
    		if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
    		    p->user != current->nsproxy->user_ns->root_user)
    			goto bad_fork_free;
    	}
    	atomic_inc(&p->user->__count);
    	atomic_inc(&p->user->processes);
    	get_group_info(p->group_info);        
    

    プロセス構造task_structにはuser_というドメインがあります.struct、このドメインは現在のユーザーの使用リソース数を保存します.
    struct task_struct{
    ......
         struct user_struct *user;
    ......
    };

    user_struct構造にprocessesが含まれていることは、現在のユーザーが最も多くのプロセスを作成できることを示します.制限を超えると、作成プロセスは破棄されます.rootユーザーを除く.制限を超えない場合はuser_struct構造の参照カウントに1を追加し、作成したプロセス数に1を追加します.
    四、スレッドチェック
    スレッドの作成が最大制限を超えているかどうかを確認します.これは簡単です.
    	/*
    	 * 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.
    	 */
    	if (nr_threads >= max_threads)
    		goto bad_fork_cleanup_count;
    五、データを書き込む
    ここで、プロセス記述子task_structはすでに確立されており、親プロセスと同じであり、スタックアドレスが異なる以外は、いくつかの値を変更し始めます.
    	if (!try_module_get(task_thread_info(p)->exec_domain->module))
    		goto bad_fork_cleanup_count;
    
    	if (p->binfmt && !try_module_get(p->binfmt->module))
    		goto bad_fork_cleanup_put_domain;
    
    	p->did_exec = 0;               ,   0   
    	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
    	copy_flags(clone_flags, p);
    	INIT_LIST_HEAD(&p->children);
    	INIT_LIST_HEAD(&p->sibling);
    	p->vfork_done = NULL;
    	spin_lock_init(&p->alloc_lock);
    
    	clear_tsk_thread_flag(p, TIF_SIGPENDING);
    	init_sigpending(&p->pending);
    
    	p->utime = cputime_zero;
    	p->stime = cputime_zero;
    	p->gtime = cputime_zero;
    	p->utimescaled = cputime_zero;
    	p->stimescaled = cputime_zero;
    	p->prev_utime = cputime_zero;
    	p->prev_stime = cputime_zero;
    
    #ifdef CONFIG_TASK_XACCT
    	p->rchar = 0;		/* I/O counter: bytes read */
    	p->wchar = 0;		/* I/O counter: bytes written */
    	p->syscr = 0;		/* I/O counter: read syscalls */
    	p->syscw = 0;		/* I/O counter: write syscalls */
    #endif
    	task_io_accounting_init(p);
    	acct_clear_integrals(p);
    
    	p->it_virt_expires = cputime_zero;
    	p->it_prof_expires = cputime_zero;
    	p->it_sched_expires = 0;
    	INIT_LIST_HEAD(&p->cpu_timers[0]);
    	INIT_LIST_HEAD(&p->cpu_timers[1]);
    	INIT_LIST_HEAD(&p->cpu_timers[2]);
    
    	p->lock_depth = -1;		/* -1 = no lock */
    	do_posix_clock_monotonic_gettime(&p->start_time);
    	p->real_start_time = p->start_time;
    	monotonic_to_bootbased(&p->real_start_time);
    #ifdef CONFIG_SECURITY
    	p->security = NULL;
    #endif
    	p->io_context = NULL;
    	p->audit_context = NULL;
    	cgroup_fork(p);
    #ifdef CONFIG_NUMA
     	p->mempolicy = mpol_copy(p->mempolicy);
     	if (IS_ERR(p->mempolicy)) {
     		retval = PTR_ERR(p->mempolicy);
     		p->mempolicy = NULL;
     		goto bad_fork_cleanup_cgroup;
     	}
    	mpol_fix_fork_child_flag(p);
    #endif
    #ifdef CONFIG_TRACE_IRQFLAGS
    	p->irq_events = 0;
    #ifdef __ARCH_WANT_INTERRUPTS_ON_CTXSW
    	p->hardirqs_enabled = 1;
    #else
    	p->hardirqs_enabled = 0;
    #endif
    	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
    #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
    
    ここでは、いくつかの値を初期化し、スケジューリングなどの様々なポリシーに関連する値が重要です.
    六、スケジューリングに参加する
    	/* Perform scheduler related setup. Assign this task to a CPU. */
    	sched_fork(p, clone_flags);
    これはプロセスがスケジューリングに参加できることを使用することができるが、このときプロセスの状態はTASK_に変更される.RUNNINGは、カーネルの他の部分が実行可能状態に変更されることを防止します.プロセスの設定がまだ完了していないため、スケジューリングプロセスに問題があります.
    七、コピー開始
    	if ((retval = security_task_alloc(p)))
    		goto bad_fork_cleanup_policy;
    	if ((retval = audit_alloc(p)))
    		goto bad_fork_cleanup_security;
    	/* copy all the process information */
    	if ((retval = copy_semundo(clone_flags, p)))System V   
    		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_keys(clone_flags, p)))
    		goto bad_fork_cleanup_mm;
    	if ((retval = copy_namespaces(clone_flags, p)))
    		goto bad_fork_cleanup_keys;
    	retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
    	if (retval)
    		goto bad_fork_cleanup_namespaces;
    ここにはいろいろなCopyがあります.
    八、PID等の操作を割り当てる
    	if (pid != &init_struct_pid) {
    		retval = -ENOMEM;
    		pid = alloc_pid(task_active_pid_ns(p));
    		if (!pid)
    			goto bad_fork_cleanup_namespaces;
    
    		if (clone_flags & CLONE_NEWPID) {
    			retval = pid_ns_prepare_proc(task_active_pid_ns(p));
    			if (retval < 0)
    				goto bad_fork_free_pid;
    		}
    	}
    
    	p->pid = pid_nr(pid);
    	p->tgid = p->pid;
    	if (clone_flags & CLONE_THREAD)
    		p->tgid = current->tgid;
    
    	p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
    	/*
    	 * Clear TID on mm_release()?
    	 */
    	p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;
    #ifdef CONFIG_FUTEX
    	p->robust_list = NULL;
    #ifdef CONFIG_COMPAT
    	p->compat_robust_list = NULL;
    #endif
    	INIT_LIST_HEAD(&p->pi_state_list);
    	p->pi_state_cache = NULL;
    #endif
    	/*
    	 * sigaltstack should be cleared when sharing the same VM
    	 */
    	if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
    		p->sas_ss_sp = p->sas_ss_size = 0;
    
    	/*
    	 * Syscall tracing should be turned off in the child regardless
    	 * of CLONE_PTRACE.
    	 */
    	clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
    #ifdef TIF_SYSCALL_EMU
    	clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
    #endif
    
    	/* Our parent execution domain becomes current domain
    	   These must match for thread signalling to apply */
    	p->parent_exec_id = p->self_exec_id;
    
    	/* ok, now we should be set up.. */
    	p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
    	p->pdeath_signal = 0;
    	p->exit_state = 0;
    
    	/*
    	 * Ok, make it visible to the rest of the system.
    	 * We dont wake it up yet.
    	 */
    	p->group_leader = p;
    	INIT_LIST_HEAD(&p->thread_group);
    	INIT_LIST_HEAD(&p->ptrace_children);
    	INIT_LIST_HEAD(&p->ptrace_list);
    
    	/* Now that the task is set up, run cgroup callbacks if
    	 * necessary. We need to run them before the task is visible
    	 * on the tasklist. */
    	cgroup_fork_callbacks(p);
    	cgroup_callbacks_done = 1;
    
    	/* Need tasklist lock for parent etc handling! */
    	write_lock_irq(&tasklist_lock);
    
    	/* for sys_ioprio_set(IOPRIO_WHO_PGRP) */
    	p->ioprio = current->ioprio;
    
    	/*
    	 * The task hasn't been attached yet, so its cpus_allowed mask will
    	 * not be changed, nor will its assigned CPU.
    	 *
    	 * The cpus_allowed mask of the parent may have changed after it was
    	 * copied first time - so re-copy it here, then check the child's CPU
    	 * to ensure it is on a valid CPU (and if not, just force it back to
    	 * parent's CPU). This avoids alot of nasty races.
    	 */
    	p->cpus_allowed = current->cpus_allowed;
    	if (unlikely(!cpu_isset(task_cpu(p), p->cpus_allowed) ||
    			!cpu_online(task_cpu(p))))
    		set_task_cpu(p, smp_processor_id());
    九、親子関係の初期化
    	/* CLONE_PARENT re-uses the old parent */
    	if (clone_flags & (CLONE_PARENT|CLONE_THREAD))
    		p->real_parent = current->real_parent;
    	else
    		p->real_parent = current;
    	p->parent = p->real_parent;
    
    	spin_lock(&current->sighand->siglock);
    
    	/*
    	 * Process group and session signals need to be delivered to just the
    	 * parent before the fork or both the parent and the child after the
    	 * fork. Restart if a signal comes in before we add the new process to
    	 * it's process group.
    	 * A fatal signal pending means that current will exit, so the new
    	 * thread can't slip out of an OOM kill (or normal SIGKILL).
     	 */
    	recalc_sigpending();
    	if (signal_pending(current)) {
    		spin_unlock(¤t->sighand->siglock);
    		write_unlock_irq(&tasklist_lock);
    		retval = -ERESTARTNOINTR;
    		goto bad_fork_free_pid;
    	}

    スレッドまたはPARENTオプションが作成されている場合、親プロセスはcurrentの親プロセスです.
    十、スレッド作成部分
    今回作成されたスレッドがスレッドである場合、プロセスによって作成されたアクションはいくつかあります.
    	if (clone_flags & CLONE_THREAD) {
    		p->group_leader = current->group_leader;//             
    		list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
    
    		if (!cputime_eq(current->signal->it_virt_expires,
    				cputime_zero) ||
    		    !cputime_eq(current->signal->it_prof_expires,
    				cputime_zero) ||
    		    current->signal->rlim[RLIMIT_CPU].rlim_cur != RLIM_INFINITY ||
    		    !list_empty(&current->signal->cpu_timers[0]) ||
    		    !list_empty(&current->signal->cpu_timers[1]) ||
    		    !list_empty(&current->signal->cpu_timers[2])) {
    			/*
    			 * Have child wake up on its first tick to check
    			 * for process CPU timers.
    			 */
    			p->it_prof_expires = jiffies_to_cputime(1);
    		}
    	}
    

    十一、新しいプロセス挿入プロセスチェーンテーブル
    	if (likely(p->pid)) {
    		add_parent(p);
    		if (unlikely(p->ptrace & PT_PTRACED))
    			__ptrace_link(p, current->parent);
    
    		if (thread_group_leader(p)) {
    			if (clone_flags & CLONE_NEWPID)
    				p->nsproxy->pid_ns->child_reaper = p;          ,       child_reaper         ,                init  。
    
    			p->signal->tty = current->signal->tty;
    			set_task_pgrp(p, task_pgrp_nr(current));
    			set_task_session(p, task_session_nr(current));
    			attach_pid(p, PIDTYPE_PGID, task_pgrp(current));
    			attach_pid(p, PIDTYPE_SID, task_session(current));
    			list_add_tail_rcu(&p->tasks, &init_task.tasks);
    			__get_cpu_var(process_counts)++;
    		}
    		attach_pid(p, PIDTYPE_PID, pid);
    		nr_threads++;
    	}
    add_parentマクロは、プロセスのchildrenチェーンテーブルを親プロセスに接続し、次のように実現します.
    #define add_parent(p)		list_add_tail(&(p)->sibling,&(p)->parent->children)

    十二、最後
    	total_forks++;
    	spin_unlock(&current->sighand->siglock);
    	write_unlock_irq(&tasklist_lock);
    	proc_fork_connector(p);
    	cgroup_post_fork(p);
    	return p;
    
    bad_fork_free_pid:
    	if (pid != &init_struct_pid)
    		free_pid(pid);
    bad_fork_cleanup_namespaces:
    	exit_task_namespaces(p);
    bad_fork_cleanup_keys:
    	exit_keys(p);
    bad_fork_cleanup_mm:
    	if (p->mm)
    		mmput(p->mm);
    bad_fork_cleanup_signal:
    	cleanup_signal(p);
    bad_fork_cleanup_sighand:
    	__cleanup_sighand(p->sighand);
    bad_fork_cleanup_fs:
    	exit_fs(p); /* blocking */
    bad_fork_cleanup_files:
    	exit_files(p); /* blocking */
    bad_fork_cleanup_semundo:
    	exit_sem(p);
    bad_fork_cleanup_audit:
    	audit_free(p);
    bad_fork_cleanup_security:
    	security_task_free(p);
    bad_fork_cleanup_policy:
    #ifdef CONFIG_NUMA
    	mpol_free(p->mempolicy);
    bad_fork_cleanup_cgroup:
    #endif
    	cgroup_exit(p, cgroup_callbacks_done);
    	delayacct_tsk_free(p);
    	if (p->binfmt)
    		module_put(p->binfmt->module);
    bad_fork_cleanup_put_domain:
    	module_put(task_thread_info(p)->exec_domain->module);
    bad_fork_cleanup_count:
    	put_group_info(p->group_info);
    	atomic_dec(&p->user->processes);
    	free_uid(p->user);
    bad_fork_free:
    	free_task(p);
    fork_out:
    	return ERR_PTR(retval);
    }