在Linux中,进程和线程的区别并不大,进程的创建主要依赖于fork函数(还有vfork函数),普通线程的创建则依赖于clone函数,内核线程比较特殊,后面再讲
在Linux 2.6.32版本的内核中,三个函数的实现如下(体系结构为x86):
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.sp, ®s, 0, NULL, NULL);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.sp, ®s, 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, ®s, 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, ¶m);
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是创建进程和线程的唯一路口