解析Linux内核的基本的模块管理与时间管理操作
内核模块管理 Linux内核的整体结构非常庞大,其包含的组件非常多。我们把需要的功能都编译到linux内核,以模块方式扩展内核功能。 先来看下最简单的内核模块 #include <linux/init.h> #include <linux/module.h> static int __init hello_init(void) { printk(KERN_ALERT "Hello world! %s,%dn",__FILE__,__LINE__); return 0; } static void __exit hello_exit(void) { printk(KERN_ALERT "Hello world! %s,__LINE__); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Mikcy Liu"); MODULE_DESCRIPTION("A simple Module"); MODULE_ALIAS("a simple module"); 头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。 内核模块中用于输出的函数式内核空间的printk()而非用户空间的printf(),printk()的用法和printf()相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试手段 前者可以定义输出级别,在 <内核目录>/include/linux/kernel.h中 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant condition */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ 未设定级别的,在<内核目录>/kernel/printk.c中定义 /* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */ 只有当printk打印信息时的loglevel小于DEFAULT_CONSOLE_LOGLEVEL的值(优先级高于console loglevel),这些信息才会被打印到console上。 模块声明与描述
模块编译 obj-m := hello.o KERNEL_BUILD := /lib/modules/$(shell uname -r)/build all: make -C $(KERNEL_BUILD) M=$(shell pwd) modules clean: -rm -rf *.o *.ko *.mod.c .*.cmd *.order *.symvers .tmpversions KERNELBUILD :=/lib/modules/$(shell uname -r)/ build是编译内核模块需要的Makefile的路径,Ubuntu下是/lib/modules/2.6.31-14-generic/build 如果是Arm平台的开发板,则-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层Makefile文件. make -C $(KERNEL_BUILD) M=$(shell pwd) modules 编译内核模块。-C 将工作目录转到KERNEL_BUILD,调用该目录下的Makefile,并向这个Makefile传递参数M的值是$(shell pwd) modules。 M=选项让该makefile在构造modules目标之前返回到模块源代码目录。然后modules目标指向obj-m变量中设定的模块 执行make命令开始编译模块,生成hello.ko,执行make clean可清除编译产生的文件。 1、添加模块 insmod hello.ko 2、查看模块 lsmod | grep hello lsmod命令实际上读取并分析/proc/modules文件,也可以cat /proc/modules文件 在模块所在目录下执行 modinfo hello.ko可以查看模块信息,如下所示 filename: hello.ko alias: a simple module description: A simple Module author: Mikcy Liu license: GPL srcversion: 875C95631F4F336BBD4216C depends: vermagic: 3.5.0-17-generic SMP mod_unload modversions 686 3、删除模块 rmmod hello
Linux内核模块加载函数一般以__init标识声明,典型的模块加载函数的形式如下: static int __init initialization_function(void) { //初始化代码 } module_init(initialization_function); 模块加载函数必须以“module_init(函数名)”的形式指定。它返回整形值,若初始化成功,应返回0。而在初始化失败时。应该返回错误编码。 在linux内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类的符号值。返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用perror等方法把它们转换成有意义的错误信息字符串。 在linux2.6内核中,所有标识为__init的函数在连接的时候都会放在.init.text(这是module_init宏在目标代码中增加的一个特殊区段,用于说明内核初始化函数的所在位置)这个区段中,此外,所有的__init函数在区段.initcall.init中还保存着一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数,并在初始化完成后释放init区段(包括.init.text和.initcall.init等)。所以大家应注意不要在结束初始化后仍要使用的函数上使用这个标记。 模块卸载函数 Linux内核卸载模块函数一般以__exit标识声明,典型的模块卸载函数的形式如下: static void __exit cleanup_function(void) { //释放代码 } module_exit(cleanup_function); 模块卸载函数在模块卸载时被调用,不返回任何值,必须以”module_exit(函数名)”的形式来指定 一般来说,模块卸载函数完成与模块加载函数相反的功能:
#include <linux/jiffies.h> #define time_after(unknown,known) // unknow > known #define time_before(unknown,known) // unknow < known #define time_after_eq(unknown,known) // unknow >= known #define time_before_eq(unknown,known) // unknow <= known unknown通常是指jiffies,known是需要对比的值(常常是一个jiffies加减后计算出的相对值) unsigned long timeout = jiffies + HZ/2; /* 0.5秒后超时 */ ... if(time_before(jiffies,timeout)){ /* 没有超时,很好 */ }else{ /* 超时了,发生错误 */ u64 j2; j2 = get_jiffies_64(); a. struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; 较老,但很流行。采用秒和毫秒值,保存了1970年1月1日0点以来的秒数 b. struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; 较新,采用秒和纳秒值保存时间。 #include <linux/time.h> void do_gettimeofday(struct timeval *tv); d.current_kernel_time() #include <linux/time.h> struct timespec current_kernel_time(void);
#include <linux/delay.h> /* 实际在<asm/delay.h> */ void ndelay(unsigned long nsecs); /*延迟纳秒 */ void udelay(unsigned long usecs); /*延迟微秒 */ void mdelay(unsigned long msecs); /*延迟毫秒 */ while(time_before(jiffies,j1)) schedule(); 在等待期间可以让出处理器,但系统无法进入空闲模式(因为这个进程始终在进行调度),不利于省电。 #include <linux/sched.h> signed long schedule_timeout(signed long timeout); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(2*HZ); /* 睡2秒 */ 进程经过2秒后会被唤醒。如果不希望被用户空间打断,可以将进程状态设置为TASK_UNINTERRUPTIBLE。 #include <linux/init.h> #include <linux/module.h> #include <linux/time.h> #include <linux/sched.h> #include <linux/delay.h> static int __init test_init(void) { set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(5 * HZ); printk(KERN_INFO "Hello Mickyn"); return 0; } static void __exit test_exit(void) { } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Micky Liu"); MODULE_DESCRIPTION("Test for delay");
#include <linux/wait.h> struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t; DECLARE_WAIT_QUEUE_HEAD(name); wait_queue_head_t my_queue; init_waitqueue_head(&my_queue); #include <linux/wait.h> long wait_event_timeout(wait_queue_head_t q,condition,long timeout); long wait_event_interruptible_timeout(wait_queue_head_t q,long timeout);
wait_queue_head_t wait; init_waitqueue_head(&wait); wait_event_interruptible_timeout(wait,2*HZ); /*当前进程在等待队列wait中睡2秒 */ struct timer_list{ struct list_head entry; /* 定时器链表 */ unsigned long expires; /* 以jiffies为单位的定时值 */ spinlock_t lock; void(*function)(unsigned long); /* 定时器处理函数 */ unsigned long data; /* 传给定时器处理函数的参数 */ } struct timer_list my_timer; init_timer(&my_timer); /* 填充数据结构 */ my_timer.expires = jiffies + delay; my_timer.data = 0; my_timer.function = my_function; /*定时器到期时调用的函数*/ void my_timer_function(unsigned long data); 可以利用data参数用一个处理函数处理多个定时器。可以将data设为0 add_timer(&my_timer); 定时器一旦激活就开始运行。 mod_timer(&my_timer,jiffies+ney_delay); 可以用于那些已经初始化但还没激活的定时器, del_timer(&my_timer); 被激活或未被激活的定时器都可以使用,如果调用时定时器未被激活则返回0,否则返回1。 del_time_sync(&my_timer); 在smp系统中,确保返回时,所有的定时器处理函数都退出。不能在中断上下文使用。 #include <linux/init.h> #include <linux/module.h> #include <linux/time.h> #include <linux/sched.h> #include <linux/delay.h> #include <linux/timer.h> struct timer_list my_timer; static void timer_handler(unsigned long arg) { printk(KERN_INFO "%s %d Hello Micky! arg=%lun",__func__,__LINE__,arg ); } static int __init test_init(void) { init_timer(&my_timer); my_timer.expires = jiffies + 5 * HZ; my_timer.function = timer_handler; my_timer.data = 10; add_timer(&my_timer); return 0; } static void __exit test_exit(void) { del_timer(&my_timer); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Micky Liu"); MODULE_DESCRIPTION("Test for timer"); #include <linux/init.h> #include <linux/module.h> #include <linux/time.h> #include <linux/sched.h> #include <linux/delay.h> #include <linux/timer.h> struct timer_list my_timer; static void timer_handler(unsigned long arg) { printk(KERN_INFO "%s %d Hello Micky! arg=%lun",arg ); } static int __init test_init(void) { init_timer(&my_timer); //my_timer.expires = jiffies + 5 * HZ; my_timer.function = timer_handler; my_timer.data = 10; //add_timer(&my_timer); mod_timer(&my_timer,jiffies + 5 * HZ); return 0; } static void __exit test_exit(void) { del_timer(&my_timer); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Micky Liu"); MODULE_DESCRIPTION("Test for timer"); void wait_event( wait_queue_head_t q,int condition); B. int wait_event_interruptible(wait_queue_head_t q,int condition); q: 是等待队列头,注意是采用值传递。 void wake_up( wait_queue_head_t *queue); void wake_up_interruptible( wait_queue_head_t *queue); (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |