文章来源: http://blog.csdn.net/firststp/archive/2005/06/15/395009.aspx

1.开始一个简单的内核模块
让我们也从HelloWorld开始吧。从这里我们了解一个内核模块的基本框架,还有如何生成,如何加载。
废话少说,Coding吧:
//hello.c
#include <linux/config.h>
#include <linux/module.h>

#include <linux/kernel.h> /* printk()在这个文件里 */

static int init_module()
{
    printk("Hello,World!/n");
    return 0; /* 如果初始工作失败,就返回非0 */
}

static void cleanup_module()
{
    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"太简单,我们从这里知道了如何生成模块文件,如何加载,如何卸载。也知道了模块加载和卸载时的函数。
但我们还想知道更多。来点复杂的吧。

好吧,来个稍微复杂的。
/// TestModule.c
#define _NO__VERSION__

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <asm/segment.h>
#include <linux/sched.h>
#include <asm/uaccess.h>

#define DATA_LENGTH 10
/* Module parameters */

MODULE_AUTHOR("sss <stp111@sina.com>");
MODULE_DESCRIPTION("test driver");
MODULE_LICENSE("GPL");


char kernel_version[]=UTS_RELEASE;

unsigned int test_major=0;  用来保存申请到的主设备号

ssize_t read_test(struct file *file, char *buf, size_t count, loff_t *f_ops)
{
 int left,i,*p;
 int data[DATA_LENGTH];
 p=data;
 for(i=0;i<10;i++)
 data[i]=61; // ”= “符号。

 for(left=count;left>0;left--)
 {
  放入用户空间
  __copy_to_user(buf,p,1);
  buf++;
  p++;
 }
 return 0; must reture 0;
}

ssize_t write_test(struct file *file, const char *buf, size_t count, loff_t *f_ops)
{
 return 0; must reture 0;
}

static int open_test(struct inode *inode, struct file *file)
{
 MOD_INC_USE_COUNT;
 return 0; must reture 0;
}

static int release_test(struct inode *inode, struct file *file)
{
 MOD_DEC_USE_COUNT;
 return 0; must reture 0;
}


// 申请主设备号时用的结构, 在linux/fs.h里定义
struct file_operations test_fops ={
 read: read_test,
 write: write_test,
 open: open_test,
 release:release_test
};


/* insmod, call it */
int init_module(void)
{
 int result;
 result=register_chrdev(0,"test",&test_fops);
 if(result<0)
 {
  /*printf("can't get major number");*/
  printk(KERN_INFO "test:Can't get major number/n");
  return result;
 }
 if(test_major==0)
  test_major=result;
 printk("test major:%d/r/n",result);
 return 0;模块正常初始化
}

/* rmmod, call it */
void cleanup_module(void)
{
 unregister_chrdev(test_major,"test");注销以后,设备就不存在了
}

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

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

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

// TestApp.c
#include "stdio.h"
#include "sys/stat.h"
#include "fcntl.h"

int main()
{
 char buf[20]={0,};
 int i=0;
 int p = open("/dev/test",O_RDWR);
 if (p == -1)
 {
  printf("Can't open /dec/test /r/n");
  exit(0);
 }
 
 printf("buf addr:%ui/r/n",buf);
 //
 read(p,buf,10);
 for (i=0;i<10;i++)
 {
  printf("%s/r/n",buf+i);
 }
 close(p);
 
 return 1;
}

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

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

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

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

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

 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/firststp/archive/2005/06/15/395009.aspx

Logo

更多推荐