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是创建进程和线程的唯一路口








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值