Chinaunix首页 | 论坛 | 博客
  • 博客访问: 373108
  • 博文数量: 73
  • 博客积分: 3574
  • 博客等级: 中校
  • 技术积分: 1503
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-26 11:17
文章分类

全部博文(73)

文章存档

2012年(14)

2011年(15)

2010年(44)

分类: LINUX

2010-04-14 21:09:56

   内核经常需要在后台执行一些操作,这种任务就可以通过内核线程(kernle thread)完成--独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,mm指针被设置为NULL;它只在内核空间运行,从来不切换到用户空间去;并且和普通进程一样,可以被调度,也可以被抢占。
   实际上,内核线程只能由其他内核线程创建,在现有的内核线程中创建一个新的内核线程的方法:
   int kernel_thread(int(*fn)(void *),void *arg,unsigned long flags)
   fn为你想要运行的函数,arg为函数的参数,flags为创建进程时的标志位,因为新的任务也是通过clone()系统调用而创建的,flags即为传递给clone函数的标志参数。
   #include 
#include 
#include 
#include 

MODULE_AUTHOR("T-bagwell_CU");
MODULE_LICENSE("GPL");

static DECLARE_WAIT_QUEUE_HEAD(myevent_waitqueue);
extern unsigned int myevent_id;

static int example_kernel_thread(void *unused)
{
        DECLARE_WAITQUEUE(wait, current);

        daemonize("create_by_T-bag");
        allow_signal(SIGKILL);
        add_wait_queue(&myevent_waitqueue, &wait);

        while(1){
                set_current_state(TASK_INTERRUPTIBLE);
                schedule();

                if(signal_pending(current)){
                        break;
                }
        }

        set_current_state(TASK_RUNNING);
        remove_wait_queue(&myevent_waitqueue, &wait);
        printk(KERN_WARNING "This is in example_kernel_thread\n");

        return 0;
}

static __init int init_hello_kernel_thread(void)
{
        int ret;

        ret=kernel_thread(example_kernel_thread, NULL,
                                  CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD );

        if(unlikely(ret<0)){
                printk(KERN_WARNING "kernel_thread create failed \n");
        }
        else{
                printk(KERN_WARNING "kernel_thread create success \n");
        }

        return 0;
}

static __exit void cleanup_hello_kernel_thread(void)
{
        printk(KERN_WARNING "kernel_thread exit \n");
        return ;
}

module_init(init_hello_kernel_thread);
module_exit(cleanup_hello_kernel_thread);
 
  以上代码就是创建一个内核线程的例子,example_kernel_thread即为创建的内核线程。一般新的内核线程在创建以后要通过daemonize释放父进程的资源,完成信号量,睡眠的相关工作(暂时不提)。
  用户态创建线程调用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的函数源码如下:
kernel_thread原形在/arch/kernel/process.c中
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;
memset(®s, 0, sizeof(regs));
regs.ARM_r1 = (unsigned long)arg;
regs.ARM_r2 = (unsigned long)fn;
regs.ARM_r3 = (unsigned long)do_exit;
regs.ARM_pc = (unsigned long)kernel_thread_helper;
regs.ARM_cpsr = SVC_MODE;
/* 利用do_fork来产生一个新的线程,共享父进程地址空间,并且不允许调试子进程 */
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
}
 
[/arch/i386/kernel/process.c/kernel_thread_helper]
extern void kernel_thread_helper(void); /* 定义成全局变量 */
__asm__(".section .text\n"
   ".align 4\n"
   "kernel_thread_helper:\n\t"
   "movl %edx,%eax\n\t"
   "pushl %edx\n\t"   /* edx指向参数,压入堆栈 */
   "call *%ebx\n\t"   /* ebx指向函数地址,执行函数 */
   "pushl %eax\n\t"
   "call do_exit\n"   /* 结束线程 */
   ".previous");
在kernel_thread中调用了do_fork,那么do_fork是怎样转入kernel_thread_helper去执行的呢,继续跟踪下do_fork函数.
[kernel/fork.c/do_fork()]
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
....
....
   p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);
....
....
}

它调用copy_process函数来向子进程拷贝父进程的进程环境和全部寄存器副本.
[kernel/fork.c/do_fork()->copy_process]
static task_t *copy_process(unsigned long clone_flags,
               unsigned long stack_start,
               struct pt_regs *regs,
               unsigned long stack_size,
               int __user *parent_tidptr,
               int __user *child_tidptr,
               int pid)
{
...
...
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
...
...
}
它又调用copy_thread来拷贝父进程的系统堆栈并做相应的调整.
[/arch/i386/kernel/process.c/copy_thread]:
int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,
   unsigned long unused,
   struct task_struct * p, struct pt_regs * regs)
{
...
...
   p->thread.eip = (unsigned long) ret_from_fork;
}
在这里把ret_from_fork的地址赋值给p->thread.eip,p->thread.eip表示当进程下一次调度时的指令开始地址,
所以当线程创建后被调度时,是从ret_from_fork地址处开始的.
[/arch/i386/kernel/entry.s]
到这里说明,新的线程已经产生了.
ENTRY(ret_from_fork)
   pushl %eax
   call schedule_tail
   GET_THREAD_INFO(%ebp)
   popl %eax
   jmp syscall_exit
syscall_exit:
...
work_resched:
   call schedule
...
当它从ret_from_fork退出时,会从堆栈中弹出原来保存的ip,而ip指向kernel_thread_helper,
至此kernel_thread_helper被调用,它就可以运行我们的指定的函数了.
 
阅读(1276) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~