linux 编写驱动程序,[操作系统]Linux下编写驱动程序(VFS)

[操作系统]Linux下编写驱动程序(VFS)

0 2014-06-16 18:00:18

转:http://hi.baidu.com/firstm25/item/8fe022155e1fa78988a9568f

摘要:设备驱动程序是操作系统内核与机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节。那么驱动程序如何书写实现这一接口功能是本文讨论的重点,并以一简单的驱动程序介绍书写细节。

在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。(应用程序一般是在用户态下进行)也就是说系统必须在驱动程序的子函数返回后才能进行其它的工作,即驱动程序不能进入死循环。

字符型设备驱动程序的编写包含一下信息:

#define _NO_VERSION_

#include

#include

char kernel_version[]=UTS_RELEASE

这段定义了一些版本信息,虽然用处不大,但也必不可少。最好要包含。由于用户进程是通过设备文件同硬件打交道,对设备文件的操作不外乎就是一些系统调用,如open,read,write,close……,(注意,不是fopen,fread,)但是如何把系统调用和驱动程序联系起来呢?这需要了解一个非常关键的数据结构:

struct file_opertions{

int(*seek)(struct inode*, struct file*, off_t, int);/*文件定位*/

int(*read)(struct inode*, struct file*, char, int);/*读取数据*/

int(*write)(struct inode*, struct file*, off_t, int);/*写数据*/

int(*readdir)(struct inode*, struct file*, struct dirent*, int);/*读取相关目录*/

int(*select)(struct inlde*, struct file*, int, select_table*);/*非阻塞设备访问*/

int(*ioctl)(struct inlde*, struct file*, unsigned int, unsigned long);

int(*mmap)(struct inlde*, struct file*, struct vm_area_struct*);

int(*open)(struct inlde*, struct file*);

int(*release)(struct inlde*, struct file*);

int(*fsync)(struct inlde*, struct file*);/*强制同步*/

int(*fasync)(struct inlde*, struct file*);

int(*check_media_change)(struct inlde*, struct file*);

int(*revalidata)(dev_t dev);/*使设备重新有效*/

}

其中read,write,open,close(release),ioctl是最核心的,必须实现的。

这个结构体的每个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备注册程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operatons的各个域。

以下是简单的字符型设备的驱动程序编写方式,例子程序并不牵扯到具体设备,只是个编写框架。

#include //Linux基本类型定义

#include //文件系统相关头文件

#include //memmory management内存管理

#include //错误代码

#include //汇编文件

unsigned int test_major = 0; /*定义一个主设备号(主设备号、从设备号在Linux设备管理中有相关介绍)*/

static int read_test(struct inode *inode, struct file *file, char *buf, int count)

{

/*本函数对应于file_opertions中read的实现,函数名自己定义。inode为设备节点,file为设备文件描述符(open()打开后自动或得),buf为数据缓冲区,count为数据传送个数。“static ”这里修饰函数名表示函数只在本文件中有效。这里函数只实现简单数据拷贝功能。*/

int left;

if(verify_area(VERIFY_WRITE, buf, count) == -EFAULT) //验证缓存中的数据是否有效

return -EFAULT; //错误码,在 包含

for(left=count; left>0; left--)

{

__put_user(1, buf, 1);

/* “ __”表示内核调用函数,此函数表示把数据从内核空间放到用户空间,参数依次表示:填充数、用户空间、数据量。*/

buf++;

}

return count;

}

这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态(内核空间),必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数,参考内核调用接口函数。在向用户空间拷贝数据之前,必须验证buf空间是否可用。这就用到verify_area()。

static int write_test(struct inode *inode *inode, struct file *file, const char *buf, int count)

{

return count;

}

写数据函数,具体没有实现,直接返回计数值。

static int open_test(struct inode *inode, struct file *file)

{

MOD_INC_USE_COUNT; //宏:注册模块数加1

return 0; //返回0表示成功,根据函数自己定义。

}

这个函数比较简单,它不牵扯到设备文件,仅将模块数加1。

static void release_test(struct inode *inode, struct file *file)

{

MOD_DEC_USE_COUNT; //模块数减1

}

以上实现四个函数,后三个函数都是空操作,实际调用发生时什么也不做,它们仅仅为file_operations结构体提供函数指针。

下面开始注册刚刚写好的函数

struct file_operations test_fops =

/*file_operations结构体名,test_fops结构体对象*/

{

NILL,     /*seek*/

read_test,

write_test,

NULL,    /*test_readdir*/

NULL,    /*test_mmap*/

open_test,

release_test,

NULL,    /*test_fsvnc*/

NULL,    /*test_fasync*/

/* 其它位置均填为空NILL*/

};

设备驱动程序的主体可以说是写好了,现在把驱动程序嵌入内核。驱动程序可用按照两种方式编译。一种是编译进内核(kernel),另一种是编译成模块(modules)如*.o文件,如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。

1、登记注册设备:

方式一:编译进内核,利用函数init_module()

int init_module(void)

{

int result;

result = register_chrdev(0, "test", &test_fops);

/*内核函数:注册一个字符型设备到内核中去。参数:指定设备号(0:表示内核根据主设备号获得并返回给result)、设备名、设备结构体名*/

if(result < 0)

{

printk(KERN_INFO"test:: can't get major number\n");

/*在内核空间驱动程序打印数据,参数:打印优先级、打印信息*/

return result;

}

if(test_major == 0) test_major = result;

return 0;

}

方式二:编译加载模块方式,利用insmod命令,在用insmod命令将编译好的模块调用内存时,init_module()函数被调用。在这里,init_module()函数只做了一件事,就是向系统的字符设备表登记了一个字符设备。

register_chrdev()需要三个参数,参数一是希望获得的设备号,如果是0,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数指针。如果登记成功,返回设备的主设备号,不成功,返回一个负数。

2、卸载设备:

void cleanup_module(void)

{

unregister_chrdev(test_major, "test");

}

在用rmmod卸载模块时,cleanup_module()函数被调用,它释放字符设备test在系统字符设备表中占有的表项。

至此,一个及其简单的字符设备可以说写好了,为以下叙述方便,命名文件为test.c。

上文讲到驱动程序已经基本写好,并命名为test.c文件,下面进行编译:

$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c

注释:-O2表示优化等级,-DMODULE表示编译成模块,-D__KERNEL__表示加载到内核的某个模块, -c表示编译生成test.o文件(2.4版本)

得到的文件test.o就是一个设备驱动程序。如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后进行链接

$ ld -r file1.o file2.o -o

驱动程序已经编译好了,现在把它安装到系统中去。

$ insmod -f test.o

注释:-f表示强制加载,test.o为模块名

如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的设备号。要卸载的话,运行

$ rmmod test

下一步要创建设备节点

$ mknod /dev/test c 主设备号 从设备号

注释:c表示字符型设备,主设备号就是在/proc/devices里看到的。用shell命令打印全部设备,就可以获得主设备号。

$ cat /proc/device

我们现在可以通过设备文件来访问我们的驱动程序。写一个测试程序。

#include

#include

#include

#include

main()

{

int testdev;

int i;

char buf[10];

testdev = open("/dev/test", O_RDWR);

/*open()函数首先为打开文件分配文件句柄fd,然后再为打开的文件在文件表中分配一个空闲文件结构项,然后让刚分配的文件句柄fd的文件结构指针指向搜索到的文件结构,然后调用namei()取得对应文件i节点,然后让文件结构与这个i节点结构相关联。i节点中包含有该文件所代表的主设备号和子设备号,还有它属于什么类型文件(如普通文件、目录文件、字符设备文件、块设备文件、管道文件等)。*/

if(testdev == -1)

{

printf("Cann't open file \n"); //用户空间使用printf(),内核空间使用printk()

exit(0);//退出系统

}

read(testdev, buf, 10);

/*read()/write()根据open()返回的文件句柄fd,取得该文件的i节点。根据该i节点的属性字段(i_pipe和i_mode)来决定调用相应的读写操作函数。*/

for(i=0; i<10; i++)

printf("%d\n", buf[i]);

close(testdev);

}

编译运行,打印结果应该输出全1

以上只是一个简单的演示。真正使用的驱动程序要复杂的多,要处理中断,DMA,I/Oport等问题。这才是真正的难点。

转载请保留本文网址:http://www.shaoqun.com/a/94168.html

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们:admin@shaoqun.com。

linux

0

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值