Linux内核线程

参考:https://blog.csdn.net/qq_15437667/article/details/69490325

https://blog.csdn.net/chen_1020/article/details/45789193

https://blog.csdn.net/gatieme/article/details/51566690

makefile

默认的情况下,make会在工作目录(执行make的目录)下按照文件名顺序寻找makefile文件读取并执行,查找的文件名顺序为:“GNUmakefile”、“makefile”、“Makefile”。
常应该使用“makefile”或者“Makefile”作为一个makefile的文件名(我们推荐使用“Makefile”,首字母大写而比较显著, 一般在一个目录中和当前目录的一些重要文件(README,Chagelist等)靠近,在寻找时会比较容易的发现它)。而“GNUmakefile”是 我们不推荐使用的文件名,因为以此命名的文件只有“GNU make”才可以识别,而其他版本的make程序只会在工作目录下“makefile”“Makefile”这两个文件。
lsmod

1列:表示模块的名称。

2列:表示模块的大小。字节为单位。

3列:表示依赖模块的个数。几个其他模块依赖此模块。

4列:表示依赖模块的内容。

linux内核编程(hello world

Linux可加载内核模块是 Linux 内核的最重要创新之一。它们提供了可伸缩的、动态的内核。其它开发者可以不用重新编译整个内核便可以开发内核层的程序,极大方便了驱动程序等的开发速度。

本系列文章主要是记录个人从hello world开始,通过学习linux内核基本编程操作,再对ipvs负载均衡器源码进行初步学习分析;最后,基于netfilter机制(其实是iptablesxtables-plugin)完成一个报文转发工具,xt_GTPU工具。

基础环境:腾讯云虚拟机(最便宜的那种),ubuntu 14.04

hello world!

内核编程,当然最开始也是要从经典的hello world开始。

目录结构如下:

feiqianyousadeMacBook-Pro:helloworld yousa$ tree

.

├── Makefile

└── helloworld.c

0 directories, 2 files

示例代码(命名此文件为helloworld.c

#include <linux/init.h>

#include <linux/module.h>

MODULE_LICENSE("GPL");

MODULE_AUTHOR("lyq");

MODULE_DESCRIPTION("hello world test kernel");

static int helloworld_init(void) {

    printk(KERN_INFO "hello world!\n");

    return 0;

}

static int helloworld_exit(void) {

    printk(KERN_INFO "see you.\n");

    return 0;

}

module_init(helloworld_init);

module_exit(helloworld_exit);

makefile文件(命名此文件为Makefile

KERNEL_VER = $(shell uname -r)

# the file to compile

obj-m += helloworld.o

# specify flags for the module compilation

EXTRA_CFLAGS = -g -O0

build: kernel_modules

kernel_modules:

    #make -C /lib/modules/`uname -r`/build M=$(PWD) modules

    make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) modules

clean:

    make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) clean

编译好之后执行make即可;编译成功之后,会有一个helloworld.ko文件,使用insmod命令加载模块

sudo insmod helloworld.ko

使用lsmod | grep hello可以看到已经加载了该内核模块

ubuntu@VM-7-212-ubuntu:~/kernel-code/kernel-netfilter-sample-code/helloworld$ lsmod | grep hello

helloworld             16384  0

使用dmesg | tail -n 20可以查看hello world相应的打印(也可以 tail /var/log/messages查看)

内核模块

内核模块(linux kernel module–LKM)与直接编译到内核或典型程序的元素有根本区别。典型的程序有一个 main 函数,而内核模块 包含 entry exit 函数。当向内核插入模块时,调用 entry 函数,从内核删除模块时则调用 exit 函数。因为 entry exit 函数是用户定义的,所以存在 module_init module_exit 宏,用于定义这些函数属于哪种函数和告知内核在加载/卸载对应模块时需要执行哪个函数。内核模块还包含一组必要的宏和一组可选的宏,用于定义模块的许可证、模块的作者、模块的描述等等。

(代码中的entry函数就是static int helloworld_init(void)exit函数是static int helloworld_exit(void),它们最后使用module_init module_exit 宏告知内核加载时调用helloworld_init函数和退出时调用helloworld_exit函数)

2.6版本之后(当前基本都是linux内核2.6以后的了吧……),可以通过insmod/rmmod等命令操作内核,其主要命令如下:

insmod: 将模块插入内核中,使用方法:#insmod XXX.ko

rmmod: 将模块从内核中删除,使用方法:#rmmod XXX.ko

lsmod: 列表显示所有的内核模块,可以和grep指令结合使用。使用方法:#lsmod | grep XXX

modprobe: modprobe可载入指定的个别模块,或是载入一组相依赖的模块。modprobe会根据depmod所产生的依赖关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。依赖关系是通过读取 /lib/modules/2.6.xx/modules.dep得到的。而该文件是通过depmod 所建立。

modinfo: 查看模块信息。使用方法:#modinfo XXX.ko

源码信息

代码github

https://github.com/Miss-you/kernel-netfilter-sample-code

其中helloworld是在helloworld目录下

遇到的问题

make: * /lib/modules/4.4.0-53-generic/build: 没有那个文件或目录。 停止。

参照链接

http://blog.csdn.net/qq_15437667/article/details/69831509

---------------------------------------------内核多线程--------------------------------------------

前几天看了看C语言多线程,今天就想看看linux内核多线程是怎么一回事。经过多方资料查询,写了一个小程序和大家分享下。

在这里先介绍程序中用到的几个方法、结构。

1.task_struct      //用户定义j进程描述符,linux中把并不对进程和线程做强制区分

2.kthread_run()  //用户创建一个线程并运行函数原型如下kthread_run(threadfn, data, namefmt, ...)threadfn是线程被唤醒后执行的方法,

3.kthread_stop()  //用于结束一个线程的运行,需要注意的是调用此方法时,该线程必须不能已经结束,否则后果严重

4.kthread_should_stop()  //用户返回结束标志

5.wait_event_interruptible_on_timeout()  //中断一个线程,知道满足条件或者超时为止

下面贴出代码:

#include<linux/init.h>

#include<linux/module.h>

#include<linux/kernel.h>

#include<linux/delay.h>

#include<linux/wait.h>

#include<linux/kthread.h>

#include<linux/spinlock.h>

MODULE_LICENSE("GPL");

static struct task_struct *task1;

static struct task_struct *task2;

static int number=0;

int i,j;

int num=0;

DEFINE_SPINLOCK(mylock);

static wait_queue_head_t wait_queue;

static int thread_fun1(void *data){

    init_waitqueue_head(&wait_queue);

    printk(KERN_INFO"thread1: number = %d\n",number);

    for(i=0;i<10;i++){

        spin_lock(&mylock);

        number++;

        spin_unlock(&mylock);

        printk("thread1: number = %d\n",number);

        msleep(1000);

    }

    j=1;

    while(!kthread_should_stop()){

        wait_event_interruptible_timeout(wait_queue,false,HZ);

        printk("thread 1 sleeping..%d/n", j++);

    }

    return 0;

}

static int thread_fun2(void *data){

    //printk(KERN_INFO"thread2: number = %d\n",number);

     init_waitqueue_head(&wait_queue);

    for(i=0;i<10;i++){

        spin_lock(&mylock);  //加锁以保证同步

        number++;

        spin_unlock(&mylock);

        printk("thread2: number = %d\n",number);

        msleep(1000);

        }

    j=1;

    while(!kthread_should_stop()){

        wait_event_interruptible_timeout(wait_queue,false,HZ);

        printk("thread 2 sleeping..%d\n", j++);

    }

    return 0;

}

static int __init hello_init(void){

    task1 = kthread_run(thread_fun1,NULL,"mythread1");

    if(IS_ERR(task1)){

        printk("thread1 create failed!\n");

    }else{

        printk("thread1 create success!\n");

    }

    task2 = kthread_run(thread_fun2,NULL,"mythread2");

    if(IS_ERR(task2)){

        printk("thread2 create failed!\n");

    }else{

        printk("thread2 create success\n");

    }

    return 0;

}

static void __exit hello_exit(void){

    if(!IS_ERR(task1)){     //这里判断指针是否正常

        kthread_stop(task1);

        printk("thread1 finished!\n");

    }

    if(!IS_ERR(task2)){

        kthread_stop(task2);

        printk("thread2 finished!\n");

    }

}

module_init(hello_init);

module_exit(hello_exit);

Makefile

KERNEL_VER = $(shell uname -r)

# the file to compile

obj-m += kthread_test.o

# specify flags for the module compilation

EXTRA_CFLAGS = -g -O0

build: kernel_modules

kernel_modules:

        #make -C /lib/modules/`uname -r`/build M=$(PWD) modules

        make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) modules

clean:

        make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) clean

使用dmesg | tail -n 20可以查看最后20条日志,但不能动态查看,需要不停执行,不宜实际使用。

建议使用:tail -f 10 /var/log//messages,动态查看内核日志

ps -ef 可以查看到两个内核线程:

[root@localhost build]# ps -ef|grep thread

root          2      0  0 May16 ?        00:00:00 [kthreadd]

root     139250      2  0 19:49 ?        00:00:00 [mythread1]

root     139251      2  0 19:49 ?        00:00:00 [mythread2]

rmmod 卸载模块,两个线程将消失。

------------------------------------------------------二号进程---------------------------------------------------------------

过程中发现了特殊的2号进程,顺带说明一下,主要是原理性东东。

前言

Linux下有3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)kthreadd(PID = 2)


* idle进程由系统自动创建, 运行在内核态 

idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换


* init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终用户空间 

0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程 
Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。


* kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理 

它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 

我们下面就详解分析2号进程kthreadd

2号进程

内核初始化rest_init函数中,由进程 0 (swapper 进程)创建了两个process

init 进程 (pid = 1, ppid = 0)

kthreadd (pid = 2, ppid = 0)

所有其它的内核线程的ppid 都是 2,也就是说它们都是由kthreadd thread创建的

所有的内核线程在大部分时间里都处于阻塞状态(TASK_INTERRUPTIBLE)只有在系统满足进程需要的某种资源的情况下才会运行

它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

2号进程的创建

rest_init函数中创建2号进程的代码如下

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

rcu_read_lock();

kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

rcu_read_unlock();

complete(&kthreadd_done);

2号进程的事件循环

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);

    set_cpus_allowed_ptr(tsk, cpu_all_mask);            //  允许kthreadd在任意CPU上运行

    set_mems_allowed(node_states[N_MEMORY]);

    current->flags |= PF_NOFREEZE;

    for (;;) {

            /*  首先将线程状态设置为 TASK_INTERRUPTIBLE, 如果当前

            没有要创建的线程则主动放弃 CPU 完成调度.此进程变为阻塞态*/

            set_current_state(TASK_INTERRUPTIBLE);

            if (list_empty(&kthread_create_list))  //  没有需要创建的内核线程

                    schedule();                                 //   什么也不做, 执行一次调度, 让出CPU

             /*  运行到此表示 kthreadd 线程被唤醒(就是我们当前)

            设置进程运行状态为 TASK_RUNNING */

            __set_current_state(TASK_RUNNING);

            spin_lock(&kthread_create_lock);                    //  加锁,

            while (!list_empty(&kthread_create_list)) {

                    struct kthread_create_info *create;

                    /*  从链表中取得 kthread_create_info 结构的地址,在上文中已经完成插入操作(

                    kthread_create_info 结构中的 list 成员加到链表中,此时根据成员 list 的偏移

                    获得 create)  */

                    create = list_entry(kthread_create_list.next,

                                        struct kthread_create_info, 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;

}

kthreadd的核心是一forwhile循环体。

for循环中,如果发现kthread_create_list是一空链表,则调用schedule调度函数,因为此前已经将该进程的状态设置为TASK_INTERRUPTIBLE,所以schedule的调用将会使当前进程进入睡眠。

如果kthread_create_list不为空,则进入while循环,在该循环体中会遍历该kthread_create_list列表,对于该列表上的每一个entry,都会得到对应的类型为struct kthread_create_info的节点的指针create.

然后函数在kthread_create_list中删除create对应的列表entry,接下来以create指针为参数调用create_kthread(create).

create_kthread的过程如下

create_kthread完成内核线程创建

static void create_kthread(struct kthread_create_info *create)

{

    int pid;

#ifdef CONFIG_NUMA

    current->pref_node_fork = create->node;#endif

    /* We want our own signal handler (we take no signals by default).

    其实就是调用首先构造一个假的上下文执行环境,最后调用 do_fork()

    返回进程 id, 创建后的线程执行 kthread 函数

    */

    pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);

    if (pid < 0) {

            /* If user was SIGKILLed, I release the structure. */

            struct completion *done = xchg(&create->done, NULL);

            if (!done) {

                    kfree(create);

                    return;

            }

            create->result = ERR_PTR(pid);

            complete(done);

    }

}

create_kthread()函数中,会调用kernel_thread来生成一个新的进程,该进程的内核函数为kthread,调用参数为

pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);

我们可以看到,创建的内核线程执行的事件kthread

此时回到 kthreadd thread,它在完成了进程的创建后继续循环,检查 kthread_create_list 链表,如果为空,则 kthreadd 内核线程昏睡过去

那么我们现在回想我们的操作 
我们在内核中通过kernel_create或者其他方式创建一个内核线程, 然后kthreadd内核线程被唤醒, 来执行内核线程创建的真正工作,于是这里有三个线程

kthreadd已经光荣完成使命(接手执行真正的创建工作),睡眠

唤醒kthreadd的线程由于新创建的线程还没有创建完毕而继续睡眠 (kthread_create函数中)

新创建的线程已经正在运行kthread,但是由于还有其它工作没有做所以还没有最终创建完成.

新创建的内核线程kthread函数

static int kthread(void *_create)

{

    /* Copy data: it's on kthread's stack

     create 指向 kthread_create_info 中的 kthread_create_info */

    struct kthread_create_info *create = _create;

     /*  新的线程创建完毕后执行的函数 */

    int (*threadfn)(void *data) = create->threadfn;

    /*  新的线程执行的参数  */

    void *data = create->data;

    struct completion *done;

    struct kthread self;

    int ret;

    self.flags = 0;

    self.data = data;

    init_completion(&self.exited);

    init_completion(&self.parked);

    current->vfork_done = &self.exited;

    /* If user was SIGKILLed, I release the structure. */

    done = xchg(&create->done, NULL);

    if (!done) {

            kfree(create);

            do_exit(-EINTR);

    }

    /* OK, tell user we're spawned, wait for stop or wakeup

     设置运行状态为 TASK_UNINTERRUPTIBLE  */

    __set_current_state(TASK_UNINTERRUPTIBLE);

     /*  current 表示当前新创建的 thread task_struct 结构  */

    create->result = current;

    complete(done);

    /*  至此线程创建完毕 ,  执行任务切换,让出 CPU  */

    schedule();

    ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {

            __kthread_parkme(&self);

            ret = threadfn(data);

    }

    /* we can't just return, we must preserve "self" on stack */

    do_exit(ret);

}

线程创建完毕:

创建新 thread 的进程恢复运行 kthread_create() 并且返回新创建线程的任务描述符 
新创建的线程由于执行了 schedule() 调度,此时并没有执行.

直到我们使用wake_up_process(p);唤醒新创建的线程

线程被唤醒后, 会接着执行threadfn(data)

    ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {

            __kthread_parkme(&self);

            ret = threadfn(data);

    }

    /* we can't just return, we must preserve "self" on stack */

    do_exit(ret);

总结

kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理,它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

我们在内核中通过kernel_create或者其他方式创建一个内核线程, 然后kthreadd内核线程被唤醒, 来执行内核线程创建的真正工作,新的线程将执行kthread函数, 完成创建工作,创建完毕后让出CPU,因此新的内核线程不会立刻运行.需要手工 wake up, 被唤醒后将执行自己的真正工作函数

任何一个内核线程入口都是 kthread()

通过 kthread_create() 创建的内核线程不会立刻运行.需要手工 wake up.

通过 kthread_create() 创建的内核线程有可能不会执行相应线程函数threadfn而直接退出

猜你喜欢

转载自blog.csdn.net/lyq_csdn/article/details/80976576