Archive for the ‘Kernel内核编程’ category

为修改的kernel生成patch

September 9th, 2015

最近在为之前修改的内核打patch,平时对于patch只是停留在用用的地步,比如我们这里有一个palloc-3.18.21.patch,打补丁,我们只需要进入到linux-3.18.21根目录下,然后使用命令:

$patch -p1 < palloc-3.18.21.patch

如果我们想撤销,那么我们只需要下面的命令:

$patch -R -p1 < palloc-3.18.21.patch

那么patch -p0 -p1 是什么意思呢?
patch -p0 (p指的是路径,后面的数字表示去掉路径的第几部分。0,表示不去掉,为全路径)
patch -p1 (p后面的数字1,表示去掉前第一个路径)

比如我们修改了内核的某个文件,我们可以使用diff生成单个文件的patch,可以使用下面的命令

$diff -urN arch/x86/include/ ../linux-3.18.21/arch/x86/include/ > patch

比如我们我们除了修改源码,还修改了kernel的头文件,那么如何将多个补丁放在一个patch文件中呢?

方法一:

$echo patch1 >> patch2

方法二:

$diff -uNr srcDir dstDir > all.patch

http://bbs.chinaunix.net/thread-2230170-1-1.html

阻塞,非阻塞访问与异步通知的比较

August 25th, 2015

最近在编写字符设备驱动,在使用场景上面存在不同的实现:阻塞I/O,非阻塞I/O和异步通知三种,之前都是朦朦胧胧知道三者区别,而没有认真的学习三者不同,这这篇文章中我会仔细的比较三者的区别。

设备的阻塞访问

指的是执行设备操作时如果无法回去资源,那么挂起进程,挂起的进程进入休眠状态,kernel将其从rq中移出,直到条件满足,示例代码:

char buf;
fd = open("/dev/ttyS1",O_RDWR);
...
res = read(fd,&buf,1);
if(res == 1)
   printf("%c\n",buf);

20150825112857

阻塞访问的优点就是节省CPU资源,资源没有得到满足,那么挂起即可,进程进入休眠状态,将cpu资源让给其他进程(当然如果进入休眠,那么当资源满足,我们需要一种方式唤醒这个休眠进程,可以使用信号)。阻塞I/O 一般使用等待队列来实现。

设备的非阻塞访问

指的是如果得不到资源,那么立即返回,并不挂起这个进程,我们可以不断的轮训这个设备,直到这个设备满足资源。

char buf;
fd = open("/dev/ttyS1",O_RDWR | O_NONBLOCK);
...
while(read(fd,&buf,1)!= 1)
   printf("%c\n",buf);

20150825112920

非阻塞访问的最大缺点是因为要不停的轮训设备,会浪费大量的cpu时间,但是我们可以借助sigaction通过异步通知的方式访问串口提高cpu利用率,说到非阻塞,通常会用到select() poll() 系统调用,这两个调用最后都会调用到驱动设备中的poll函数。

poll函数原型是unsigned int (* poll)(struct file *filp,struct poll_table *wait),在驱动里面,调用poll_wait() 向poll_table注册等待队列,当字符设备中存在数据时,return POLLIN,POLLRDNORM,POLLOUT。这里我们要注意:设备驱动的poll函数本身并不会阻塞,但是poll和select()系统调用会阻塞等待文件描述符集合中的至少一个可访问或者超时。

异步通知

异步通知的全程是“信号驱动的异步I/O”,也就是说一旦设备准备就绪,主动通知应用程序,这样应用程序根本就不需要查询设备状态。

20150825112911

我们可以使用信号来通知设备处理,其中STDIN_FILENO是int类型,不同于STDIN 的FILE * 类型,使用signal添加信号处理函数,使用fcntl()设置SIGIO信号被STDIN_FILENO接收,之后使用O_ASYNC 使得IO具有异步特性。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#define MAX_LEN 100

void input_handler(int num)
{
        char data[MAX_LEN];
        int len;

        len = read(STDIN_FILENO,&data,MAX_LEN);
        data[len] = 0;
        printf("input:%s\n",data);
}

int main()
{
        int oflags;
        signal(SIGIO,input_handler);
        fcntl(STDIN_FILENO,F_SETOWN,getpid());
        oflags = fcntl(STDIN_FILENO,F_GETFL);
        fcntl(STDIN_FILENO,F_SETFL,oflags | O_ASYNC);

        while(1);
}

 

 

[1] UNIX 高级编程

[2] Linux 设备驱动开发

[3] http://stackoverflow.com/questions/15102992/what-is-the-difference-between-stdin-and-stdin-fileno

[4] http://www.c4learn.com/c-programming/c-reference/fread-function/

Linux 跨模块函数调用

August 20th, 2015

在编写模块的时候,我们经常会同时编写多个模块,模块中的函数,难免会有相互调用的需求,这个时候我们需要修改调用这个函数的Makefile文件,使其可以找到要调用的函数。

我们可以举例Module A 与 Module B,Module A中含有Module B要调用的函数,模块A中使用EXPORT_SYMBOL或EXPORT_SYMBOL_GPL将要提供给B模块的函数导出,具体Module A的代码形式如下:

void A_function(void)
{
        printk("A function");
        return;
}
EXPORT_SYMBOL(A_function);

这个时候我们通过make 可以生成一系列的中间文件,这里面包括Module.symvers,如果我们打开看这个符号表,可以发现这个文件包含函数虚拟地址,函数名,模块路径,导出形式EXPORT_SYMBOL。

然后我们在Module B中使用这个A_function,需要首先声明extern void A_function(void);然后才可以使用。

extern void A_function(void);

static int __init B_init(void)
{
        printk("B_func module init!\n");
        A_function();
        return 0;
}

我们已经把函数主体编写完毕,但是当我们使用insmod插入这个模块时,我们会发现系统提示Unknwon symbol,所以这个时候我们要向Module A的Makefile中加入Module B的Module.symvers,这样kernel在插入Module A时才知道Module B的位置。

obj-m:= module-B.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/lib/modules/$(LINUX_KERNEL)/build

KBUILD_EXTRA_SYMBOLS +=/home/dslab/kmod/huawei_hook/Module.symvers
export KBUILD_EXTRA_SYMBOLS

all:
 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
 make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

还有一种方式,就是在kernel源码 编译的时候加入到源码树中的Module.symers,然后编译内核,不过显然这种方式过于笨重,推荐使用修改Module A的Module.symvers这种方式。

如何杀死一个内核线程

June 23rd, 2015

首先明确杀死一个进程与杀死一个kthread是不同的,杀死进程的时机是进程从内核态返回到用户态检查_TIF_SIGPENDING标志位,进一步进入到处理信号的函数进行处理杀死这个进程。

内核线程运行在整个内核之上,,如果不返回,则不可能检查信号,所以内核的线程实质上的停止与启动必须由线程本身状态决定,不允许随意杀死。如果这个线程正在持有某个全局锁时,强制杀死kthread会造成整个内核的死锁。所以目前kernel对于内核线程的停止主要依赖于线程内部的停止。

一种方式

发送信号,对于内核线程默认是对于信号是忽略的,所以我们要想停止一个线程必须在线程内部使用allow_signal(SIGKILL)方式,然后在内核线程代码的某个部位处理这个信号。所以发送信号的时机非常重要,如果当前kthread正在进行某些业务逻辑,那么发送SIGKILL无效。

另外一种方式

使用目前kernel提供工具函数int kthread_stop(struct task_struct *k) 用来对某个kthread进行停止。这个函数仅仅限于kthread_create()创建的内核线程,通过这个函数创建的内核线程都会被挂在kthreadd 内核线程树上。这种方式也可以被看作是一种发送信号的方式,但是这些函数已经被提供出来供编写者用来停止内核线程。线程内部必须显式的检查THREAD_SHOULD_STOP信号,从而使得线程return或者使用do_exit()退出线程[1]。否则无法停止内核线程。

当kthread_create()创建的内核线程时:

kthread_create
  -> kthread_create_on_node                              // in kthead.c
      -> adds your thread request to kthread_create_list
          -> wakes up the kthreadd_task

当唤醒kthreadd_task时,这个函数会运行kthreadd()。

pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
...
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

kthreadd()这个函数会调用kthread()函数。kthread()函数 调用用户定义的内核线程函数。

kthreadd                                                 // all in kthread.c
  -> create_kthread
      -> kernel_thread(kthread, your_kthread_create_info, ...)

kthread()函数会调用我们自己创建的内核线程函数,当需要停止的时候,检查KTHREAD_SHOULD_STOP位,当返回后会将ret值传递到do_exit(ret),这个也就是我们不用显示调用do_exit()的原因。

kthread
  -> initialization stuff
    -> schedule() // allows you to cancel the thread before it's actually started
      -> if (!should_stop)
          -> ret = your_thread_function()
            -> do_exit(ret)

注意:内核线程return时,默认调用do_exit(ret),如果直接使用do_exit()退出线程,那么必须保证task_struct不被释放否则当继续执行kthread_stop()会释放一个无效的task_struct,导致发生Oops。[4]

当需要停止目标内核线程,kernel会获取当前描述目标内核线程状态的结构体kthread,设置KTHREAD_SHOULD_STOP标示位,然后唤醒这个目标线程,当前进程调用wake_for_completion(&kthread->exited)睡眠,被唤醒的条件其实就是这个目标内核线程的task_struct 上的vfork_done完成,这个标志位在do_exit()中被设置。当前进程/内核线程等待目标内核线程结束的过程时不可中断的,直到目标内核线程退出,最后释放task_struct结构体,这样就可以安全的停止当前线程。

int kthread_stop(struct task_struct *k)
{
        struct kthread *kthread;
        int ret;

        trace_sched_kthread_stop(k);

        get_task_struct(k);
        kthread = to_live_kthread(k);
        if (kthread) {
            set_bit(KTHREAD_SHOULD_STOP, &kthread->flags);
            __kthread_unpark(k, kthread);
            wake_up_process(k);
            wait_for_completion(&kthread->exited);
        }
        ret = k->exit_code;
        put_task_struct(k);

        trace_sched_kthread_stop_ret(ret);
        return ret;
}

上面的代码必须确保task_struct有效,如果无效,调用这个函数会发生Oops。

在内核线程中的业务处理逻辑外使用kthread_should_stop()检查当前线程的KTHREAD_SHOULD_STOP标志位,如果被设置,退出循环,就要执行线程的退出操作。

do {
        //do business
} while(!kthread_should_stop());

[1] http://v4l.videotechnology.com/dwg/kernelthreads/kernelthreads.html
[2] http://lwn.net/Articles/65178/
[3] http://blog.csdn.net/chinayangbo2011/article/details/8923731

[4] http://stackoverflow.com/questions/10177641/proper-way-of-handling-threads-in-kernel

内核线程与用户进程在信号处理上的区别

June 8th, 2015

Update 2015-6-11

上一篇博客里面,我分析了信号在内核中处理的时机,发现对于内核线程没有类似于用户态程序信号处理的机制。后来我发邮件问了kthread的维护者Tetsuo Handa,他明确的给出了我内核线程没有类似于用户进程发送SIGSTOP将进程停止的机制。这个也就意味着我们要想让内核线程接收信号,并进行处理,必须在创建kernel thread代码中显式的允许某个信号。

进程对信号的响应

  1. 忽略信号:大部分信号可被忽略,除SIGSTOP和SIGKILL信号外(这是超级用户杀掉或停掉任意进程的手段)。
  2. 捕获信号:注册信号处理函数,它对产生的特定信号做处理。
  3. 让信号默认动作起作用:unix内核定义的默认动作,有5种情况:
    • a) 流产abort:终止进程并产生core文件。
    • b) 终止stop:终止进程但不生成core文件。
    • c) 忽略:忽略信号。
    • d) 挂起suspend:挂起进程。
    • e) 继续continue:若进程是挂起的,则resume进程,否则忽略此信号。

通常意义上来说内核线程对于信号是不处理的,如果想显式的让kernel thread支持信号,必须在内核线程中开启signal。编程框架类似于

static int thread_process(void *arg)
{
....
    allow_signal(SIGURG);
    allow_signal(SIGTERM);
    allow_signal(SIGKILL);
    allow_signal(SIGSTOP);
    allow_signal(SIGCONT);  
...
    for ( ; !remove_mod; ) {
        /* Avoid infinite loop */
        msleep(1000);
        if (signal_pending(current)) {
                siginfo_t info;
                unsigned long signr;
                signr = dequeue_signal_lock(current, &current->blocked, &info);
                switch(signr) {
                        case SIGSTOP:
                                printk(KERN_DEBUG "thread_process(): SIGSTOP received.\n");
                                set_current_state(TASK_STOPPED);
                                schedule();
                                break;
                        case SIGCONT:
                                printk(KERN_DEBUG "thread_process(): SIGCONT received.\n");
                                set_current_state (TASK_INTERRUPTIBLE);
                                schedule();
                                break;

                        case SIGKILL:
                                printk(KERN_DEBUG "thread_process(): SIGKILL received.\n");
                                break;
                        //      goto die;

                        case SIGHUP:
                                printk(KERN_DEBUG "thread_process(): SIGHUP received.\n");
                                break;
                        default:
                                printk(KERN_DEBUG "thread_process(): signal %ld received\n", signr);
                        }
        }
        schedule_timeout_interruptible(msecs_to_jiffies(1));
    }
    return 0;
}

在用户态下,我们只需要编写信号处理函数,然后使用signal(sig,handler)方式将信号处理函数与特定信号连接。向内核线程发信号与用户态进程发送信号都是发送某个特定特定pid号,比如19号信号是SIGSTOP,那么我们使用kill -19 pid即可。具体pid解释

创建内核线程,拥有两种方式1) kthread_create() 2) kernel_thread() 函数,虽然都是创建内核线程,但是二者在原理上不同。kthread_create() 创建的线程是挂在kthreadd()上面,kthread_create创建的内核线程有干净的上那上下文环境,适合于驱动模块或用户空间的程序创建内核线程使用,不会把某些内核信息暴露给用户程序。而kernel_thread()创建的线程来自于init进程。

所以我们推荐使用kthread_create()这种感觉方式创建内核线程,这种方式有利于模块的加载与卸载,有的时候kernel_thread创建的线程不容易卸载,只能通过reboot处理这种问题。

另外我们要非常注意内核线程的可重入性,在线程中使用函数必须保证函数是线程安全的,有些函数并不保证线程安全,如果我们在一个模块中修改全局变量,很有可能导致数据的不一致性,这里有必要要加锁。

 

参考:

http://www.spongeliu.com/165.html

http://blog.csdn.net/maimang1001/article/details/16906451