设为首页 收藏本站
查看: 346|回复: 0

[经验分享] Linux下编写驱动程序(VFS)

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-6-17 10:10:20 | 显示全部楼层 |阅读模式
摘要:设备驱动程序是操作系统内核与机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节。那么驱动程序如何书写实现这一接口功能是本文讨论的重点,并以一简单的驱动程序介绍书写细节。        在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。(应用程序一般是在用户态下进行)也就是说系统必须在驱动程序的子函数返回后才能进行其它的工作,即驱动程序不能进入死循环。
字符型设备驱动程序的编写包含一下信息:
#define _NO_VERSION_
#include <linux/modules.h>
#include <linux/version.h>
char kernel_version[]=UTS_RELEASE
        这段定义了一些版本信息,虽然用处不大,但也必不可少。<linux/config.h>最好要包含。由于用户进程是通过设备文件同硬件打交道,对设备文件的操作不外乎就是一些系统调用,如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/types.h> //Linux基本类型定义
#include <linux/fs.h> //文件系统相关头文件
#include <linux/mm.h> //memmory management内存管理
#include <linux/errno.h> //错误代码
#include <asm/segment.h> //汇编文件
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; //错误码,在<linux/errno.h> 包含
    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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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);
    close(testdev);
}
        编译运行,打印结果应该输出全1
        以上只是一个简单的演示。真正使用的驱动程序要复杂的多,要处理中断,DMA,I/Oport等问题。这才是真正的难点。


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-20714-1-1.html 上篇帖子: linux tar.gz zip 解压缩 压缩命令 下篇帖子: CentOS系统配置记录 Linux 程序
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表