编写自定义Linux内核模块

来源:互联网 发布:轻云vpn mac 编辑:程序博客网 时间:2024/05/22 08:11

1.开始一个简单的内核模块

 

让我们也从HelloWorld开始吧。从这里我们了解一个内核模块的基本框架,还有如何生成,如何加载。
废话少说,Coding吧:

  1. //////////hello.c 
  2. #include <linux/config.h> 
  3. #include <linux/module.h> 
  4.  
  5. #include <linux/kernel.h> /* printk()在这个文件里 */ 
  6.  
  7. static int init_module() 
  8.     printk("Hello,World!/n"); 
  9.     return 0; /* 如果初始工作失败,就返回非0 */ 
  10.  
  11. static void cleanup_module() 
  12.     printk("Bye!/n"); 

编译吧,当然是 gcc了:
gcc -c -o hello.o -I/usr/src/linux/include -D__KERNEL__ -DMODULE hello.c
说明: -I指定了内核模块工作环境的内核的头文件路径,如不指定,通常会遇到与kernel版本不符的警告,并且会经常出现问题。
-D定义宏,内核模块要使用两个宏Macro:__KERNEL__ 和 MODULE

现在我们有了模块文件,加载吧:
使用命令: insmod hello.o
如果没有问题,现在就加载成功了。检查一下,看看内核模块是否进入内核中了,使用命令: lsmod ,看看输出列表中有没有hello模块。
好吧,现在我们有了一个模块,它会在加载时输出"Hello,World!",退出时输出”Bye!"。没有看到,使用命令:dmesg,看输出消息中会有"Hello,World!"。
现在退出吧,把这个模块从内核中去掉,使用命令: rmmod hello 现在模块hello就从内核中走了,再用命令 dmesg,可以看到 ”Bye!"。

好了,这也是一个内核模块。但是要多说一点:
static int init_module()是模块加载时,系统要进行调用的函数,Oh,man,看样子是构造函数。如果成功,其需要返回 0,否则就是错误号码。

static void cleanup_module() 是模块卸载时,要调用的函数,和析构函数想像。

内核编程时不能使用标准库,在内核模块中只能使用内核函数,也就是可以在/proc/ksyms 中找到的函数。如printf-->printk

ok,我们还是来个Makefile吧,免得每次都常常的命令:
######## Makefile
CC      = gcc

KERNELSRC = /usr/src/linux-2.4 ////if different, change it.

CFLAGS  = -O2 -Wall -I$(KERNELSRC)/include -D__KERNEL__ -DMODULE

object    = test.o
source    = test.c

$(object):
 $(CC) $(CFLAGS) -c $(source)

.PHONY: clean
clean:
 rm -f *.o


2.复杂一点的吧
"Hello World"太简单,我们从这里知道了如何生成模块文件,如何加载,如何卸载。也知道了模块加载和卸载时的函数。
但我们还想知道更多。来点复杂的吧。

好吧,来个稍微复杂的。

  1. // TestModule.c 
  2. #define _NO__VERSION__ 
  3.  
  4. #include <linux/module.h> 
  5. #include <linux/version.h> 
  6. #include <linux/kernel.h> 
  7. #include <linux/types.h> 
  8. #include <linux/fs.h> 
  9. #include <linux/mm.h> 
  10. #include <linux/errno.h> 
  11. #include <asm/segment.h> 
  12. #include <linux/sched.h> 
  13. #include <asm/uaccess.h> 
  14.  
  15. #define DATA_LENGTH 10 
  16. /* Module parameters */ 
  17.  
  18. MODULE_AUTHOR("sss <stp111@sina.com>"); 
  19. MODULE_DESCRIPTION("test driver"); 
  20. MODULE_LICENSE("GPL"); 
  21.  
  22.  
  23. char kernel_version[]=UTS_RELEASE; 
  24.  
  25. unsigned int test_major=0;  //用来保存申请到的主设备号 
  26.  
  27. ssize_t read_test(struct file *file, char *buf, size_t count, loff_t *f_ops) 
  28.  int left,i,*p; 
  29.  int data[DATA_LENGTH]; 
  30.  p=data; 
  31.  for(i=0;i<10;i++) 
  32.  data[i]=61; // ”= “符号。 
  33.  
  34.  for(left=count;left>0;left--) 
  35.  { 
  36.   // 放入用户空间 
  37.   __copy_to_user(buf,p,1); 
  38.   buf++; 
  39.   p++; 
  40.  } 
  41.  return 0; // must reture 0; 
  42.  
  43. ssize_t write_test(struct file *file, const char *buf, size_t count, loff_t *f_ops) 
  44.  return 0; // must reture 0; 
  45.  
  46. static int open_test(struct inode *inode, struct file *file) 
  47.  MOD_INC_USE_COUNT; 
  48.  return 0; // must reture 0; 
  49.  
  50. static int release_test(struct inode *inode, struct file *file) 
  51.  MOD_DEC_USE_COUNT; 
  52.  return 0; // must reture 0; 
  53.  
  54.  
  55. // 申请主设备号时用的结构, 在linux/fs.h里定义 
  56. struct file_operations test_fops ={ 
  57.  read: read_test, 
  58.  write: write_test, 
  59.  open: open_test, 
  60.  release:release_test 
  61. }; 
  62.  
  63.  
  64. /* insmod, call it */ 
  65. int init_module(void
  66.  int result; 
  67.  result=register_chrdev(0,"test",&test_fops); 
  68.  if(result<0) 
  69.  { 
  70.   /*printf("can't get major number");*/ 
  71.   printk(KERN_INFO "test:Can't get major number/n"); 
  72.   return result; 
  73.  } 
  74.  if(test_major==0) 
  75.   test_major=result; 
  76.  printk("test major:%d/r/n",result); 
  77.  return 0;//模块正常初始化 
  78.  
  79. /* rmmod, call it */ 
  80. void cleanup_module(void
  81.  unregister_chrdev(test_major,"test");// 注销以后,设备就不存在了 

这个内核注册了一个字符设备,并通过一个file_operations结构,指定了其各种操作。

好了,make吧,编译生成内核文件。再加载吧。
用命令: dmesg,看看得到的主设备号。(我的系统上是268)
再用命令:lsmod检查一下内核是否加载了我们的模块。

3.测试一下
好了,让我们的测试一下吧。
测试前,先看看是否已经建立了设备文件。 检查 /dev下是否有test设备文件,如果没有,那我们就手工建立一个。用前面看到的主设备号,使用命令: “mknod test c 268 0”
建立的设备文件,那就开始我的测试程序吧。

  1. // TestApp.c 
  2. #include "stdio.h" 
  3. #include "sys/stat.h" 
  4. #include "fcntl.h" 
  5.  
  6. int main() 
  7.  char buf[20]={0,}; 
  8.  int i=0; 
  9.  int p = open("/dev/test",O_RDWR); 
  10.  if (p == -1) 
  11.  { 
  12.   printf("Can't open /dec/test /r/n"); 
  13.   exit(0); 
  14.  } 
  15.  // 
  16.  printf("buf addr:%ui/r/n",buf); 
  17.  // 
  18.  read(p,buf,10); 
  19.  for (i=0;i<10;i++) 
  20.  { 
  21.   printf("%s/r/n",buf+i); 
  22.  } 
  23.  close(p); 
  24.  // 
  25.  return 1; 

再写个Makefile,然后make得到一个执行文件,开始我们的测试。看到了吗,从内核得到了正确的结果了。

4.一点小总结
这里展示了一个简单的字符驱动程序,并使用了一个程序进行了测试。看到了,我们主要的工作就是实现open,read,write,ioctl...系统调用的处理。这些函数指针被赋值进了fileoperations结构。当然,这些函数是有固定的形式的,你要遵循它们。

还有一点的就是,内核2.4和 2.6对内核的处理有了很大的变化。这里所说的都是2.4版。

还有一点就是版本问题,《Linux Device Driver》中有介绍。这本书也许每个要写内核的人都要学习。

5.驱动有什么不同呢
嗯,这个不好说。驱动可以是内核模块,但内核模块不是驱动。所以驱动是内核模块的子集。
这要看内核模块的作用。内核模块主要是为方便地加入内核,并在内核空间运行。
而驱动呢,驱动是内核模块,有内核模块的属性,但其最主要的目的是“使某个硬件响应一个定义良好的的内部编程接口,同时要完全隐藏设备的工作细节”。主要是解决硬件的可用性问题。一般而言,它要和硬件发生直接的关系。


来自:http://blog.csdn.net/firststp/archive/2005/06/15/395009.aspx

 

原创粉丝点击