深入浅出Linux设备驱动编程之内核模块

深入浅出Linux设备驱动编程之内核模块

刺猬@http://blog.csdn.net/littlehedgehog

 





注: 该系列文章转载自arm+linux chinaunix博客圈圈主之博客——http://blog.chinaunix.net/u/22630/article_54997.html


Linux设备驱动属于内核的一部分,Linux内核的一个模块可以以两种方式被编译和加载:

(1)直接编译进Linux内核,随同Linux启动时加载;

(2)编译成一个可加载和删除的模块,使用insmod加载(modprobe和insmod命令类似,但依赖于相关的配置文件),rmmod删除。这种方式控制了内核的大小,而模块一旦被插入内核,它就和内核其他部分一样。

下面我们给出一个内核模块的例子:

  1. #include <linux/module.h> //所有模块都需要的头文件
  2. #include <linux/init.h> // init&exit相关宏
  3. MODULE_LICENSE("GPL");
  4. static int __init hello_init (void)
  5. {
  6.  printk("Hello module init/n");
  7.  return 0;
  8. }
  9. static void __exit hello_exit (void)
  10. {
  11.  printk("Hello module exit/n");
  12. }
  13. module_init(hello_init);
  14. module_exit(hello_exit);

分析上述程序,发现一个 Linux内核模块需包含模块初始化和模块卸载函数,前者在 insmod的时候运行,后者在rmmod的时候运行。初始化与卸载函数必须在宏module_init和module_exit使用前定义,否则会出现编译错误。程序中的MODULE_LICENSE("GPL")用于声明模块的许可证。

下面是makefile:
[由于原作者是做得嵌入式linux设备驱动,原Makefile是为交叉编译编写的,这里我把Makefile改为我自己x86格式 ubuntu 8.04+gcc 4.23  需要注意的是源文件要保存为test.c,另外听说Makefile第一个字母要大写(连这个都要规定 -_-)  刺猬注]

  1. obj-m :test.o
  2. KERNELBUILD :=/lib/modules/`uname -r`/build
  3. default:
  4.     make -C $(KERNELBUILD) M=$(shell pwd) modules
  5. clean:
  6.     rm -rf *.o *.ko *.mod.c .*.cmd .tmp_versions

下列命令将可加载hello模块:

insmod ./hello.o

下列命令完成相反过程:

rmmod hello

如果要将其直接编译入Linux内核,则需要将源代码文件拷贝入Linux内核源代码的相应路径里,并修改Makefile。

 

我们有必要补充一下Linux内核编程的一些基本知识:

内存

在Linux内核模式下,我们不能使用用户态的malloc()和free()函数申请和释放内存。进行内核编程时,最常用的内存申请和释放函数为在include/linux/kernel.h文件中声明的kmalloc()和kfree(),其原型为:
  1. void *kmalloc(unsigned int len, int priority);
  2. void kfree(void *__ptr);


kmalloc的priority参数通常设置为GFP_KERNEL,如果在中断服务程序里申请内存则要用GFP_ATOMIC参数,因为使用GFP_KERNEL参数可能会引起睡眠,不能用于非进程上下文中(在中断中是不允许睡眠的)。

由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。而应该使用Linux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):

  1. unsigned long copy_from_user(void *to,const void *from, unsigned long n);
  2. unsigned long copy_to_user (void * to, void * from, unsigned long len);

copy_from_user、copy_to_user函数返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。

include/asm/uaccess.h中定义的put_user和get_user用于内核空间和用户空间的单值交互(如char、int、long)。

这里给出的仅仅是关于内核中内存管理的皮毛,关于Linux内存管理的更多细节知识,《内存与I/O操作》进行更加深入地介绍。

输出

在内核编程中,我们不能使用用户态C库函数中的printf()函数输出信息,而只能使用 printk()。但是,内核中printk()函数的设计目的并不是为了和用户交流,它实际上是内核的一种日志机制,用来记录下日志信息或者给出警告提示。每个printk都会有个优先级,内核一共有8个优先级,它们都有对应的宏定义。如果未指定优先级,内核会选择默认的优先级 DEFAULT_MESSAGE_LOGLEVEL。如果优先级数字比int console_loglevel变量小的话,消息就会打印到控制台上。如果syslogd和klogd守护进程在运行的话,则不管是否向控制台输出,消息都会被追加进/var/log/messages文件。klogd 只处理内核消息,syslogd 处理其他系统消息,比如应用程序。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值