转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/45054183
linux驱动分为字符设备、块设备驱动、网络驱动三种,其中以字符驱动最为简单。说起要写驱动自然想到从字符设备驱动写起。看了开发板官方的驱动代码,对新手来说简直是噩梦。新手来说要看懂,实在不容易。其中包含了很多知识和设计思想。所以我想还是尽可能从易到难来写这个系列,相信我,我会努力把我知道的都给大家讲清楚。
一、驱动代码
/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/moduleparam.h> #include <linux/slab.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/delay.h> #include <linux/device.h> #define HELLO_CNT 1 //所请求连续设备编号个数 static int hello_major = 0; //默认内核自动分配 static int hello_minor = 0; //默认此设备号从0开始 static struct cdev hello_cdev; static struct class *hello_class; static int hello_open(struct inode *inode, struct file *file) { printk("hello open\n"); return 0; } static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, }; static int hello_char_init(void) { dev_t devid; int err,result; /*1.分配主设备号*/ if(hello_major) { devid = MKDEV(hello_major, hello_minor); result = register_chrdev_region(devid,HELLO_CNT,"hello"); } else { result = alloc_chrdev_region(&devid,hello_minor,HELLO_CNT,"hello"); hello_major = MAJOR(devid); } if(result < 0) { printk(KERN_WARNING"hello:can't get major %d\n", hello_major); return result; } /*2.注册字符设备驱动*/ cdev_init(&hello_cdev, &hello_fops); err = cdev_add(&hello_cdev, devid, HELLO_CNT); if(err) printk(KERN_WARNING"Error %d adding hello",err); /*3.创建设备*/ hello_class = class_create(THIS_MODULE, "hello"); if (IS_ERR(hello_class)) { printk(KERN_WARNING "class_create() failed for hello_class\n"); } device_create(hello_class, NULL, MKDEV(hello_major, 0), NULL, "hello0"); /* /dev/hello0 */ return 0; } static void hello_char_exit(void) { device_destroy(hello_class, MKDEV(hello_major, 0)); class_destroy(hello_class); cdev_del(&hello_cdev); unregister_chrdev_region(MKDEV(hello_major, 0), HELLO_CNT); } module_init(hello_char_init); module_exit(hello_char_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ruoyunliufeng");
二、测试程序
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /* * hello_test /dev/hello0 */ void print_usage(char *file) { printf("%s <dev>\n", file); } int main(int argc, char **argv) { int fd; if (argc != 2) { print_usage(argv[0]); return 0; } fd = open(argv[1], O_RDWR); if (fd < 0) printf("can't open %s\n", argv[1]); else printf("can open %s\n", argv[1]); return 0; }
三、驱动讲解
1.驱动的入口与出口
在用C写的应用程序中,我们程序的入口是main()函数。驱动程序也有入口,那就是module_init();括号里面的函数就是入口函数,有了入口自然就有出口module_exit();每当insmod XXX.ko的时候驱动就会进入入口函数。入口函数主要做一些初始化的工作。rmmod XXX.ko的时候,驱动就会调用出口函数,出口函数组要做一些注销和清理工作。
2.字符驱动初始化
字符设备初始化的框架大体就是这样,可以直接当做模板来用,分三步:
1.分配诸设备号
主设备号可以自己定义,也可以交给内核帮你分配,但一般都推荐内核分配,所以这里用了一个if语句来分别处理这两种情况。
2.注册字符驱动
你的驱动要让内核知道就必须注册呀。调用cdev_init();cdev_add()两个函数
3.创建设备结点
这里选择自动创建的方式,你也完全可以在驱动中不写,然后自己去手动创建。首先class_create()然后device_create()。这样在/dev/下就能出现你的设备了。这样就可以对你的设备进行一系列的读写等操作了。
3.如何调用驱动
应用程序调用open函数,通过你在驱动中写的file_operations hello_fops去调用其中的hello_open。这样应用和驱动就建立了联系,其他函数也是一样。其余的实现也是一样,比如需要写入数据,就在hello_fops中加入写函数。驱动提供的是机制,不提供策略。策略是应用程序该干的事。“需要提供什么功能”即机制,“如何使用这些功能”即策略。每个函数都有它的功能,这里要注意不要乱写。否则你的驱动程序看上去就像一坨翔。
注:这里我并没有详细的去分析每个函数的参数,意义。我觉得这些东西完全可以自己去内核中看,我不想把我的文章变成翻译内核注释,文档。我希望看我的文章你能领会大概的框架,具体细节自己去看内核吧。源码之前,了无秘密。
参考:ldd3