初学字符设备驱动

来源:互联网 发布:大学生java就业培训 编辑:程序博客网 时间:2024/05/18 05:20
经过上次学习Linux的helloworld驱动,现在对驱动程序有了一点概念了。今天,接着往下学习Linux驱动咯!
Linux驱动学习任务:
(1)网络接口驱动:重点;(2)块设备驱动;(3)字符设备驱动:重点
Linux用户如何使用上面这些驱动程序的?
对应序号分别是:
(1)用户-->套接字-->协议栈-->网络设备驱动-->网络接口设备
(2)用户-->文件系统-->块设备文件-->块设备驱动-->块设备
(3)用户-->字符设备文件-->字符设备驱动-->字符设备
今天主题:简单的字符设备驱动程序设计框架
相关理论:
1.字符设备:是一种按字节来访问的设备
2.字符驱动:负责驱动字符设备,通常实现open、close、read、write的系统调用
3.主设备号:反映设备类型
4.次设备号:区分同类型的设备,被驱动程序用来辨别操作的是哪一个设备,比如操作多个串口时,次设备就可以知道操作哪一个
5.内核中设备号的描述:
dev_t类型:unsigned int 32位的数,高12位主设备号,底20位次设备号
分离主次设备号:使用宏
MAJOR(dev_t drv)
MINOR(dev_t drv)
6.内核如何分配设备号
①静态申请:
首先程序员根据Documentation/devices.txt查看确定没有使用的主设备号,然后使用register_chrdev_region函数注册设备号
缺点:容易设备号冲突
②动态分配
交给内核自己去安排使用哪一个设备号,因为它最清楚哪一个编号没有被使用
程序中使用alloc_chrdev_region函数注册,使用unregister_chrdev_region函数注销
insmod安装完驱动之后cat proc/drivers查询设备号
③还有一个古老的函数可以实现设备号分配
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops),当major为0时为动态分配,不为0时major数值就是指定分配的设备号
3个参数意义:
major:主设备号
name:设备名字

fops:指向驱动操作函数结构体

成功注册:返回值是主设备号

注:在2.6内核里边register_chrdev_region()和unregister_chrdev_region()函数分别是register_chrdev()和unregister_chrdev()的升级版。

这两个版本的函数有什么区别?

①老版本:看源码可以发现:把添加字符设备的工作囊括了进来

register_chrdev()

cdev = cdev_alloc();//申请字符设备

cdev_add(cdev,MKDER(cd->major,0),256); //添加字符设备

②新版本:将老版本的函数拆开,添加字符设备另外操作

register_chrdev_region();

cdev_add();

实验操作:

一、新建一个目录cdrv,在其中新建char_drv.c文件,文件内容如下:

#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>int major;static int char_drv_open(struct inode *inode, struct file *file){printk("char_drv_open\n");return 0;}static int char_drv_write(struct file * file, const char __user * buffer, size_t count, loff_t * ppos){printk("char_drv_write\n");return 0;}static struct file_operations char_drv_fops = {    .owner  =   THIS_MODULE,    .open   =   char_drv_open,     .write=char_drv_write,   };static int char_drv_init(void){printk("char_drv_init\n");major = register_chrdev(252, "char_drv", &char_drv_fops);return 0;}static void char_drv_exit(void){printk("char_drv_exit\n");unregister_chrdev(major, "char_drv"); // 卸载}module_init(char_drv_init);module_exit(char_drv_exit);MODULE_AUTHOR("CLBIAO");MODULE_DESCRIPTION("Just for Demon");MODULE_LICENSE("GPL");

代码分析:
(1)首先编写驱动操作函数char_dev_open()和char_dev_write()
(2)上面这些操作函数怎么告诉内核?通过char_drv_init函数初始化
   首先,定义file_operation型结构体char_drv_fops,填充他,就会有那么一个对应的函数接口。填充内容:具体的驱动函数的指针
   然后,通过register_chrdev(设备号,设备名,操作函数结构体)函数把这个结构体注册到内核数组chardev[]里面,用户调用该设备时是根据设备号为索引在内核数组里面进行匹配的
(3)卸载驱动程序的时候就是把char_drv_fops从chardev[ ]拖出来
(4)修饰char_drv_init、char_drv_exit
   宏module_init和module_exit就是给这两个函数添加属性:
   module_init:加载到内核映像的initcall区段,初始化加载,后期内存释放
   module_exit:将退出函数添加到指定的区段,调用时直接在该区段搜索。
二、驱动程序万能的Makefile编写:

ifneq ($(KERNELRELEASE),)obj-m+= char_drv.oelseKDIR = /opt/EmbedSky/linux-2.6.30.4  all:make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linuxclean:rm -f *.ko *.o *.mod.o *.mod.c *.symversendif

这个Makefile文件执行过程:第一次进入Makefile,KERNELRELEASE变量为空,条件不成立,执行else分支,make后用“-C”选项指定进入“KDIR”目录读取里边的Makefile文件,从中读取信息,即KERNELRELEASE在这里边有被赋值,接着根据“M=$(PWD)”进入到当前的目录,执行当前目录下面的Makefile,这一次KERNELRELEASE已经有数值了,执行ifneq分支,注意目标文件的名字部分要和其依赖文件的一样,不然编译将出错,这里的编译方式是“obj-m”编译成模块(“obj-y”则是编译进内核)

三、编译make,将生成的char_drv.ko拷贝到nfs文件系统里面,启动开发板内核并挂载nfs文件系统
四、编写测试应用程序:

cdev_test.c代码:

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(void){int fd;int val = 1;fd = open("/dev/xxx",O_RDWR);//打开设备xxx,xxx设备需要使用mknod手动创建设备节点if(fd < 0){printf("open error\n");}write(fd,&val,4); //写xxx设备return 0;}

ubuntu下编译生成可执行文件:
arm-linux-gcc -static -o char_dev char_dev.c
由于我们制作的是静态链接的根文件系统,库文件不共享,所以这里要添加-static编译选项
将编译好的可执行文件复制到nfs文件系统中。

五、在开发板控制台终端输入:
insmod char_drv.ko
cat proc/devices          查看驱动的设备号,当然如果是手动分配设备号程序员自己心中有数了
mknod /dev/xxx c 252 0    手动创建设备文件,这里的“xxx”为设备文件(节点)名字,名字不重要,重要的是这个名字对应的设备号,这个名字要和app打开的设备名称一致
ls -l /dev/xxx
./cdev_test

运行结果就是:

char_dev_open

char_dev_write

运行的整个流程就是:

首先,顶层APP的open()函数根据设备节点文件/dev/xxx里面的信息(包括:设备类型、主设备号等),找到内核里边字符设备数组chrdev[ ]匹配的一项的file_operation结构体,然后调用结构体里边“.open”指向的函数char_dev_open( )。write()函数根据刚才open打开的那个设备文件的描述符找到设备文件xxx,再根据他找到对应file_operation结构体。write()传入4字节,即val指针指向的内容(int型数值“1”),这个数值最终是被传给file_operation结构体里边“.write”所指向的函数的第二个参数,最终有没有被使用就决定于这个函数了。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 矿泉水瓶盖拧不开了怎么办 弩弦用手拉不上怎么办 茅台酒瓶口漏酒怎么办 化妆品盖子丢了怎么办 化妆品盖子碎了怎么办 自制水泵压力小怎么办 大学数学不会做怎么办 下雪了怎么办教案幼儿园小班 下水道被混凝土堵塞怎么办 日本足贴丢了胶布怎么办 牙齿被可乐腐蚀怎么办 三十岁满嘴无牙怎么办 水乳盖子打不开怎么办 蜂蜜罐子打不开了怎么办 蜂蜜盖子第二次拧不开怎么办 玻璃杯子拧不开盖子怎么办 玻璃杯水杯盖子拧不开怎么办 鞋子蝴蝶结掉了怎么办 蝴蝶翅膀受伤了怎么办 手被割了个口子怎么办 致炫方向盘重怎么办 黑檀7打不透怎么办 乒乓球底板太轻怎么办 狙击精英4卡怎么办 鼠标点一下变两下怎么办 工程干完不给钱怎么办 屋里有大蛾子怎么办 房间很多小飞虫怎么办 雷蛇键盘失灵怎么办 xp驱动 不支持win10怎么办 阿提拉全面战争统治度太低怎么办 微信号变成wxid怎么办 ipv4 ipv6未连接怎么办 土豆丝粘锅怎么办还面 土豆丝容易碎怎么办 胡萝卜的菱形块怎么办 茄子多了吃不完怎么办 炒木耳会爆怎么办 土豆丸子太粘怎么办 兔子吃多了怎么办 兔子把多肉吃了怎么办