Linux kernel 分析之十:内核线程

       众所周知,内核中创建一个内核线程是通过kernel_thread实现的。声明如下:

        int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags);

      我们知道,用户态创建线程调用clone(),如果要在内核态创建线程,首先想到的是在内核态调用clone()。这是可以的。比如在init内核线程中就直接在内核态调用execve,参数为/sbin/init等等。但是还是要小心翼翼。因为系统调用里会有很多参数要求是用户态的(一般在声明前有__user ),在调用一些内核函数时也会检查参数的界限,严格要求参数在用户态。一旦发现参数是在内核态,就立即返回出错。
所以kernel_thread采用了另外一种办法。
       由于不是从用户态进入内核的,它需要制造一种现场,好像它是通过clone系统调用进入内核一样。方法是手动生成并设置一个struct pt_regs,然后调用do_fork()。但是怎样把线程的函数指针fn,参数arg传进去呢?和flags不同,flags可以作为do_fork()的参数。但是fn正常情况下应该是在clone()结束后才执行的。此外,线程总不能长生不老吧,所以执行完fn()还要执行exit()。
        所以,我们希望内核线程在创建后,回到内核态(普通情况下是用户态)后,去调用fn(arg),最后调用exit()。而要去“遥控”内核线程在创建以后的事,只能通过设置pt_regs来实现了。
看kernel_thread的实现:

355         regs.ebx = (unsigned long) fn;

356         regs.edx = (unsigned long) arg;

这里设置了参数fn,arg,当内核线程在创建以后,ebx中放的是fn,edx中放的是arg

358         regs.xds = __USER_DS;

359         regs.xes = __USER_DS;

360         regs.orig_eax = -1;

361         regs.eip = (unsigned long) kernel_thread_helper;

当内核线程在创建以后,执行的是 kernel_thread_helper函数

362         regs.xcs = __KERNEL_CS;

当内核线程在创建以后,cs寄存器的值表明当前仍然处于内核态。
363         regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2;364 
365         /* Ok, create the new process.. */
366         return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);

看来kernel_thread_helper就是我们想要的东西了。

336 __asm__(".section .text\n"

337         ".align 4\n"

338         "kernel_thread_helper:\n\t"

339         "movl %edx,%eax\n\t"

340         "pushl %edx\n\t"

341         "call *%ebx\n\t"342         "pushl %eax\n\t"343         "call do_exit\n"
344         ".previous");
首先把edx保存到eax(不明白为什么这么做,因为调用fn后返回值就把eax覆盖掉了)把edx(其实就是参数arg)压入堆栈,然后调用ebx(也就是fn)。最后调用do_exit。kernel_thread_helper是不返回的。
这里,内核通过巧妙设置pt_regs,在没有用户进程的情况下,在内核态创建了线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值