目录
三种流行的字符设备编程模型:
杂项设备驱动模型,早期经典标准字符设备驱动模型, Linux 2.6 标准字符设备驱动模型。
1.杂项设备的核心数据结构
1. 结构体:
头文件路径:
include\Linux\ Miscdevice.h
上面结构中核心部分有三个成员: minor, name, fops 。这三个成员必须实现。
fops 成员对应的结构定义: struct file_operations。
它定义了一系列的操作设备的函数指针 ,用户根据自己需要实现所需要功能的指针成员。
include\Linux\Fs.h
用户程序中有什么接口,这个 file_operations 结构中有相应功能的成员。
struct file_operations firsr_drv_fops;
常用成员说明:
llseek: 移动文件指针,对应 Linux 系统应用编程接口的 lseek 函数。
read: 从设备文件中读取数据,对应 Linux 系统应用编程接口的 read 函数。
write: 向设备文件写入数据,对应 Linux 系统应用编程接口的 write 函数。
poll: 轮询函数,对应 Linux 系统应用编程接口的 poll 函数或 select 函数
unlocked_ioctl: i/o 控制,对应 Linux 系统应用编程接口的 ioctl 函数
mmap:内存映射, 对应 Linux 系统应用编程接口的 mmap 函数
open:打开设备,操作设备的第一步,对应系统应用编程接口的 open 函数
release:关闭设备,操作设备的最后一步,对应系统应用编程接口的 close 函数
实际应用中只会实现一部分成员,不可能全部实现,也没有必要实现全部成员
2.杂项设备的设备号
主设备号: 固定是 10
次设备号: 0~255. (写 255 时候内核会动态分配一个能用的次设备号)
每个杂项设备驱动次设备不能相同。 当你不能确定哪一个次设备号是可用的情况下需要使用 255,这样内核可以帮你找一个可以使用的号。
开发板/dev 目录下的杂项设备列表:
[root@ChenZhiFa ]# ls /dev/ -l | grep 10
crw-rw---- 1 root root 10, 57 Sep 2 2016 adc
crw-rw---- 1 root root 10, 55 Sep 2 2016 android_adb
crw-rw---- 1 root root 10, 58 Sep 2 2016 backlight
crw-rw---- 1 root root 10, 52 Sep 2 2016 backlight-1wire
crw-rw---- 1 root root 10, 60 Sep 2 2016 buttons
crw-rw---- 1 root root 10, 50 Sep 2 2016 cpu_dma_latency
crw-rw---- 1 root root 10, 240 Sep 2 2016 fimg2d
crw-rw---- 1 root root 10, 229 Sep 2 2016 fuse
crw-rw---- 1 root root 10, 63 Sep 2 2016 ion
crw-rw---- 1 root root 10, 61 Sep 2 2016 leds
crw-rw---- 1 root root 10, 237 Sep 2 2016 loop-control
crw-rw---- 1 root root 10, 56 Sep 2 2016 mali
……
crw-rw---- 1 root root 10, 130 Sep 2 2016 watchdog
[root@ChenZhiFa ]#
3. 杂项设备特征
安装杂项设备会自动在/dev 目录生成设备文件。
早期经典标准字符设备驱动模型和 Linux2.6 标准字符设备驱动不会自动设备文件。
调用一次 misc_register 注册函数,只会占用一个次设备号。
[root@ChenZhiFa ]# cd home/
[root@ChenZhiFa home]# ls
app misc_device_module.ko
app2 misc_device_module2.ko
[root@ChenZhiFa home]# rm /dev/my*
[root@ChenZhiFa home]#
[root@ChenZhiFa home]#
[root@ChenZhiFa home]# insmod misc_device_module.ko
[ 51.715000] misc_register ok
[root@ChenZhiFa home]# insmod misc_device_module2.ko
[ 60.070000] misc_register ok
[root@ChenZhiFa home]# ls /dev/my* -l
crw-rw---- 1 root root 10, 47 Sep 12 2016 /dev/mymisc
crw-rw---- 1 root root 10, 46 Sep 12 2016 /dev/mymisc2
[root@ChenZhiFa home]# ./app
[ 130.250000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device/misc_device_module.c
[ 130.250000] line:13, xxx_open is call
[ 130.255000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device/misc_device_module.c
[ 130.255000] line:21, xxx_read is call
[ 130.255000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device/misc_device_module.c
[ 130.255000] line:28, xxx_write is call
[ 130.265000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device/misc_device_module.c
[ 130.265000] line:35, xxx_llseek is call
[ 130.280000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device/misc_device_module.c
[ 130.280000] line:52, xxx_unlocked_ioctl is call
[ 130.290000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device/misc_device_module.c
[ 130.290000] line:43, xxx_release is call
fd=3
/dev/mymisc open success
[root@ChenZhiFa home]# ./app2
[ 145.275000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device_back/misc_device_module2.c
[ 145.275000] line:13, xxx_open is call
[ 145.275000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device_back/misc_device_module2.c
[ 145.275000] line:21, xxx_read is call
[ 145.275000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device_back/misc_device_module2.c
[ 145.275000] line:28, xxx_write is call
[ 145.290000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device_back/misc_device_module2.c
[ 145.290000] line:35, xxx_llseek is call
[ 145.300000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device_back/misc_device_module2.c
[ 145.300000] line:52, xxx_unlocked_ioctl is call
[ 145.315000] file:/mnt/hgfs/share/20160909device_drvier/02_misc_device_back/misc_device_module2.c
[ 145.315000] line:43, xxx_release is call
/dev/mymisc2 open success
fd=3
[root@ChenZhiFa home]
4. 杂项设备注册/注销函数
实现了 struct miscdevice 结构必须的成员后,还需要使用内核专用 函数向系统注册,这样才可以完成一个驱动 的编程。
1. 注册函数
int misc_register(struct miscdevice * misc);
功能:向内核注册一个杂项字符设备
参数: misc 已经实现好 min,name, fops 三个成员的 struct miscdevice 结构变量的地址
返回: 0:注册成功;
<0 :失败,返回失败错误码、
2. 注销函数
这个函数功能的 misc_register 函数相反。
int misc_deregister(struct miscdevice *misc);
功能:注销一个已经注册到内核中杂项字符设备
参数: misc 已经注册的 struct miscdevice 结构变量的地址
返回: 0:注册成功;
<0 :失败,返回失败错误码
5. 杂项设备驱动代码模板
驱动代码模板:
#include <linux/module.h>
#include <linux/init.h>
//包含必须的头文件
#include <linux/fs.h>
#include <linux/miscdevice.h>
//以下是文件操作方法的具体实现代码
static int xxx_open(struct inode *pinode, struct file *pfile )
{
printk(KERN_EMERG"line:%d, %s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static ssize_t xxx_read(struct file *pfile,char __user *buf, size_t count, loff_t *poff)
{
printk(KERN_EMERG"line:%d, %s is call\n", __LINE__, __FUNCTION__);
return count;
}
static ssize_t xxx_write(struct file *pfile,const char __user *buf, size_t count, loff_t *poff)
{
printk(KERN_EMERG"line:%d, %s is call\n", __LINE__, __FUNCTION__);
return count;
}
static loff_t xxx_llseek(struct file *pfile, loff_t off, int whence)
{
printk(KERN_EMERG"line:%d, %s is call\n", __LINE__, __FUNCTION__);
return off;
}
static int xxx_release (struct inode *pinode, struct file *pfile)
{
printk(KERN_EMERG"line:%d, %s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static long xxx_unlocked_ioctl (struct file *pfile,unsigned int cmd, unsigned long args)
{
printk(KERN_EMERG"line:%d, %s is call\n", __LINE__, __FUNCTION__);
return 0;
}
//文件操作方法集合指针
static const struct file_operations mymisc_fops = {
.open = xxx_open,
.read = xxx_read,
.write = xxx_write,
.llseek = xxx_llseek,
.release = xxx_release,
.unlocked_ioctl = xxx_unlocked_ioctl,
};
//定义核心结构
static struct miscdevice misc = {
.minor = 255,
.name = "mymisc", ///dev 目录下的设备名
.fops = &mymisc_fops,
};
static int __init mymisdevice_init(void)
{
int ret;
//注册核心结构
ret = misc_register(&misc);
if(ret < 0) {
printk(KERN_EMERG"misc_register error\n");
return ret;
}
printk(KERN_EMERG"misc_register ok\n");
return 0;
}
static void __exit mymisdevice_exit(void)
{
int ret;
//注销核心结构
ret = misc_deregister(&misc);
if(ret < 0) {
printk(KERN_EMERG"misc_deregister error\n");
return ;
}
printk(KERN_EMERG"misc_deregister ok\n");
}
module_init(mymisdevice_init);
module_exit(mymisdevice_exit);
MODULE_LICENSE("GPL");
应用程序测试驱动:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> //lseek
#include <sys/ioctl.h> //ioctl
int fd; //存放文件描述符号
char save_buf[1024]={0}; //存放数据使用
int main(void)
{
int ret;
fd = open("/dev/mymisc",O_RDWR); //以读写方式进行打开
if(fd < 0){
printf("open error\r\n");
return -1;
}
printf("fd=%d\r\n",fd); //成功时候输出文件描述符
//读操作
ret = read(fd, save_buf,1024);
if(ret < 0){
printf("open error\r\n");
return -1;
}
//写操作
write(fd, "console out test\r\n", sizeof("console out test\r\n"));
//移动文件指针操作
lseek(fd,0,SEEK_SET);
//i/o 控制操作
ioctl(fd,0,0);
//关闭文件
close(fd);
return 0;
}
Makefile
# Makefile 2.6
#hello 是模块名,也是对应的 c 文件名。
obj-m += misc_device_module.o
# KDIR 内核源码路径,根据自己需要设置
# X86 源码路径统一是/lib/modules/$(shell uname -r)/build
#如果要编译 ARM 的模块,则修改成 ARM 的内核源码路径
#KDIR :=/lib/modules/$(shell uname -r)/build
KDIR := /root/work/linux-3.5_xyd_modversion/
all:
@make -C $(KDIR) M=$(PWD) modules
@rm -f *.o *.mod.o *.mod.c *.symvers *.markers *.unsigned *.order *~ *.bak
arm-linux-gcc app.c -o app
cp app *.ko /root/work/rootfs/home
clean:
make -C $(KDIR) M=$(PWD) modules clean
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.unsigned *.order *~ *.bak
开发板测试结果:
[root@ChenZhiFa home]# ls /dev/mymisc
ls: /dev/mymisc: No such file or directory
安装后生成了设备文件,通过这个文件可以找到这份驱动程序的文件操作方法
[root@ChenZhiFa home]# insmod misc_device_module.ko
[ 65.010000] misc_register ok
[root@ChenZhiFa home]# ls /dev/mymisc
/dev/mymisc
卸载后,设备文件自动删除:
[root@ChenZhiFa home]# rmmod misc_device_module.ko
[ 91.145000] misc_deregister ok
[root@ChenZhiFa home]# ls /dev/mymisc
ls: /dev/mymisc: No such file or directory
查看杂项设备的主次设备号: 主是 10,次 47---内核自动分配的(因为代码写了 255)
[root@ChenZhiFa home]# insmod misc_device_module.ko
[ 98.970000] misc_register ok
[root@ChenZhiFa home]# ls /dev/mymisc -l
crw-rw---- 1 root root 10, 47 Sep 9 2016 /dev/mymisc
编写用户程序,通过用户编程 API 接口调用驱动程序的文件操作方法:
[root@ChenZhiFa home]# lsmod
Tainted: G
misc_device_module 2079 0 - Live 0xbf004000 (O)
[root@ChenZhiFa home]# ls
app misc_device_module.ko
[root@ChenZhiFa home]# ./app
[ 732.400000] line:13, xxx_open is call
[ 732.400000] line:21, xxx_read is call
[ 732.400000] line:28, xxx_write is call
[ 732.400000] line:35, xxx_llseek is call
[ 732.400000] line:52, xxx_unlocked_ioctl is call
[ 732.400000] line:43, xxx_release is call
fd=3
[root@ChenZhiFa home]#
6.分析
insmod---->/pro/devices/10 这里有主设备号为10 的杂项设备
---->/sys/class/misc/fist_misc(63次设备号)
---->/dev/fist_misc
rmmod---->/pro/devices/10
---->/sys/class/misc/fist_misc
---->/dev/fist_misc
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
/proc/misc 其他的主要设备(设备号为10)上注册的驱动,其实就是主设备号10上注册次设备号
关于标题内容的几点解释:
1、proc目录是一个虚拟文件系统,可以为linux用户空间和内核空间提供交互
它只存在于内存中,而不占实际的flash或硬盘空间
/proc/devices/里的设备是加载驱动程序时生成的,系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名;
/dev/下的设备是通过创建设备节点生成的,用户通过此设备节点来访问内核里的驱动
/proc/cpuinfo
处理器的相关信息的文件;
/proc/fb
帧缓冲设备列表文件,包含帧缓冲设备的设备号和相关驱动信息;
/proc/filesystems
当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型;
/proc/meminfo
系统中关于当前内存的利用状况等的信息,常由free命令使用;可以使用文件查看命令直接读取此文件,其内容显示为两列,前者为统计属性,后者为对应的值;
/proc/version
当前系统运行的内核版本号,在作者的RHEL5.3上还会显示系统安装的gcc版本,如下所示;
7.misc_register
/**
* misc_register - register a miscellaneous device
* @misc: device structure
*
* Register a miscellaneous device with the kernel. If the minor
* number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
* and placed in the minor field of the structure. For other cases
* the minor number requested is used.
*
* The structure passed is linked into the kernel and may not be
* destroyed until it has been unregistered.
*
* A zero is returned on success and a negative errno code for
* failure.
*/
/*
主要的功能有:给设备分配次设备号;根据设备号在/dev目录下新建设备节点;
将杂项设备加入misc_list链表
*/
int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;// 定义一个 miscdevice 结构体指针
dev_t dev;// 设备号
int err = 0;
INIT_LIST_HEAD(&misc->list);/*初始化misc_list链表*/
mutex_lock(&misc_mtx);// 上锁
/*
遍历misc_list链表,看这个次设备号以前有没有被用过,
如果次设备号已被占有则退出
*/
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
mutex_unlock(&misc_mtx);
return -EBUSY;// 如果存在直接退出
}
}
/*
*#define DYNAMIC_MINORS 64
*static unsigned char misc_minors[DYNAMIC_MINORS / 8];
*这里存在一个次设备号的位图,一共64位,下边是遍历每一位;
*如果这位为0,表示没有被占有,可以使用,为1表示被占用。
*/
// misc->minor == 255 表示 自动分配次设备号
if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = DYNAMIC_MINORS;
while (--i >= 0)
if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
break;
if (i<0) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
//得到可用的次设备号
misc->minor = i;
}
if (misc->minor < DYNAMIC_MINORS)
//设置位图中相应为1
misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
//计算出设备号
dev = MKDEV(MISC_MAJOR, misc->minor);
/*
在/dev下创建设备节点,这就是有些驱动程序没有显式调用device_create,
却出现了设备节点的原因
*/
misc->this_device = device_create(misc_class, misc->parent, dev,
"%s", misc->name);
if (IS_ERR(misc->this_device)) {
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
/*将这个miscdevice添加到misc_list链表中*/
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}