天天看點

linux程序,普通線程,核心線程

在Linux中,程序和線程的差別并不大,程序的建立主要依賴于fork函數(還有vfork函數),普通線程的建立則依賴于clone函數,核心線程比較特殊,後面再講

在Linux 2.6.32版本的核心中,三個函數的實作如下(體系結構為x86):

asmlinkage int sys_fork(struct pt_regs regs)
{
	return do_fork(SIGCHLD, regs.sp, &regs, 0, NULL, NULL);
}
           
asmlinkage int sys_vfork(struct pt_regs regs)
{
	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.sp, &regs, 0, NULL, NULL);
}
           
asmlinkage int sys_clone(struct pt_regs regs)
{
	unsigned long clone_flags;
	unsigned long newsp;
	int __user *parent_tidptr, *child_tidptr;

	clone_flags = regs.bx;
	newsp = regs.cx;
	parent_tidptr = (int __user *)regs.dx;
	child_tidptr = (int __user *)regs.di;
	if (!newsp)
		newsp = regs.sp;
	return do_fork(clone_flags, newsp, &regs, 0, parent_tidptr, child_tidptr);
}
           

可以看到,這三個函數最後都調用了do_fork函數,幾乎唯一的差別就是clone_flags不同,是以在Linux中是否共享位址空間幾乎是程序和普通線程間本質上的唯一差別,當CLONE_VM被設定後,核心就不再需要調用allocate_mm()函數,而僅僅需要在調用copy_mm函數時将mm域指向其父程序的位址空間就可以了。

if (clone_flags & CLONE_VM) {
	atomic_inc(&oldmm->mm_users);
	mm = oldmm;
	goto good_mm;
}
           

對于核心線程來說,它沒有程序位址空間(mm=NULL),是以也不存在使用者上下文(程序上下文的一部分),因為核心線程不需要通路任何使用者空間的記憶體,但是核心線程還是需要知道一些資料的,比如頁表,為了解決這個問題,Linux采取了一個比較省時省力的方法——直接使用前一個程序的位址空間。因為核心線程不通路使用者空間的記憶體,是以它們僅僅使用位址空間中和核心記憶體相關的資訊,這些資訊的含義和普通程序完全相同。

現在我們來看看核心線程的實作

struct task_struct *kthread_create(int (*threadfn)(void *data),
				   void *data,
				   const char namefmt[],
				   ...)
{
	struct kthread_create_info create;

	create.threadfn = threadfn;
	create.data = data;
	//初始化完成量
	init_completion(&create.started);
	init_completion(&create.done);

	spin_lock(&kthread_create_lock);
	//将核心線程加入kthread_create_list連結清單(需要建立的核心線程資訊連結清單,此時并沒有真的建立線程)中
	list_add_tail(&create.list, &kthread_create_list);
	spin_unlock(&kthread_create_lock);
        //喚醒kthreadd線程
	wake_up_process(kthreadd_task);
	//等待建立完成,為了防止kthreadd線程發生核心搶占,目前程序重新被排程這種情況的發生
	wait_for_completion(&create.done);

	if (!IS_ERR(create.result)) {
		//下面的代碼就是設定線程的名字還有路徑啥的
		va_list args;
		va_start(args, namefmt);
		vsnprintf(create.result->comm, sizeof(create.result->comm),
			  namefmt, args);
		va_end(args);
	}
	return create.result;
}
           

上面的代碼最重要的一段就是wake_up_process(kthreadd_task);這段代碼喚醒了kthreadd核心線程,它的作用就是管理排程其它核心線程,然後kthreadd會調用kthreadd函數

int kthreadd(void *unused)
{
	struct task_struct *tsk = current;

	/* Setup a clean context for our children to inherit. */
	//設定程序名字
	set_task_comm(tsk, "kthreadd");
	//清空信号
	ignore_signals(tsk);
	//設定優先級,為高優先級-5,保證能夠第一時間排程
	set_user_nice(tsk, KTHREAD_NICE_LEVEL);
	set_cpus_allowed(tsk, CPU_MASK_ALL);

	current->flags |= PF_NOFREEZE;

	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE);
		/* 如果沒有核心線程需要建立,即不是通過kthread_create
		 *入口進來的,目前程序就應該排程
		*/
		if (list_empty(&kthread_create_list))
			schedule();
		__set_current_state(TASK_RUNNING);

		spin_lock(&kthread_create_lock);
		//周遊全局連結清單kthread_create_list,建立核心線程
		while (!list_empty(&kthread_create_list)) {
			struct kthread_create_info *create;

			create = list_entry(kthread_create_list.next,
					    struct kthread_create_info, list);
			//将核心線程移出kthread_create_list
			list_del_init(&create->list);
			spin_unlock(&kthread_create_lock);
			//此時才是真正建立線程
			create_kthread(create);

			spin_lock(&kthread_create_lock);
		}
		spin_unlock(&kthread_create_lock);
	}

	return 0;
}
           

然後再來看create_thread函數

static void create_kthread(struct kthread_create_info *create)
{
	int pid;

	/* We want our own signal handler (we take no signals by default). */
	//調用do_fork建立線程,所有線程具有統一的處理函數kthread
	pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
	if (pid < 0) {
		create->result = ERR_PTR(pid);
	} else {
		struct sched_param param = { .sched_priority = 0 };
		//等待剛建立的核心線程把自己設定為不可運作狀态
		wait_for_completion(&create->started);
		read_lock(&tasklist_lock);
		create->result = find_task_by_pid_ns(pid, &init_pid_ns);
		read_unlock(&tasklist_lock);
		/*
		 * root may have changed our (kthreadd's) priority or CPU mask.
		 * The kernel thread should not inherit these properties.
		 */
		 //設定核心線程的排程政策為CFS
		sched_setscheduler(create->result, SCHED_NORMAL, &param);
		set_user_nice(create->result, KTHREAD_NICE_LEVEL);
		set_cpus_allowed(create->result, CPU_MASK_ALL);
	}
	//喚醒調用kthread_create函數的程序
	complete(&create->done);
}
           

kthread函數如下

static int kthread(void *_create)
{
	struct kthread_create_info *create = _create;
	int (*threadfn)(void *data);
	void *data;
	int ret = -EINTR;

	/* Copy data: it's on kthread's stack */
	threadfn = create->threadfn;
	data = create->data;

	/* OK, tell user we're spawned, wait for stop or wakeup */
	__set_current_state(TASK_UNINTERRUPTIBLE);
	complete(&create->started);
	schedule();

	if (!kthread_should_stop())
		ret = threadfn(data);

	/* It might have exited on its own, w/o kthread_stop.  Check. */
	if (kthread_should_stop()) {
		kthread_stop_info.err = ret;
		complete(&kthread_stop_info.done);
	}
	return 0;
}
           

到這就說完了,其實在linux中do_fork是建立程序和線程的唯一路口

繼續閱讀