声明
以下都是我刚开始看驱动视频的个人强行解读,如果有误请指出,共同进步。
本节目标
注册杂项设备,并生成设备节点
首先讲一下之前
我们说过linux的关键是:驱动和设备挂载在总线上
比如单片机EEPROM是IIC协议的,那么设备EEPROM挂载在IIC总线上,编写驱动即可。
对于总线
那么对于LED这种没协议的咋整?他就是个GPIO,所以Linux弄了一个platform虚拟总线,让这种没有总线的都挂载在这个虚拟总线上,好了 总线的事情完成了。
对于设备
比如我们有1个LED,就注册一个LED的设备,LINUX为他分配一个设备号,那么假如我有1000个不同的设备,是不是可以注册1000个设备呢?这里面就有学问了。
Linux为设备分配设备号,而每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备(0-255一共256个),而次设备号用来区分同一类型的多个设备。而对于常用设备,Linux定死了他的设备号。我们用命令查看
cat /proc/devices
返回内容(只截取了字符设备部分)
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
216 rfcomm
226 drm
251 hidraw
252 bsg
253 watchdog
254 rtc
这些主设备号其实并不是完全任由我们设置,linux会把常用的设备固定设备号,比如misc杂项设备的主设备号就是10,然后给归类misc的设备分配次设备号来区分。
正文
对于迅为的教程,是本身内核已经有一个设备的情况下 加载模块 -> 加载编译进内核的设备的驱动-> 调用probe函数。
这是一个标准的注册设备,注册驱动,调用probe()的过程。
然后他在probe()里又注册了一个杂项设备,这样代码很冗长,所以我在此做了一些修改,直接像注册普通设备一样注册杂项设备。(代码相比,区别就是函数不一样)。
以模块module来当模板
#include <linux/init.h>
#include <linux/module.h>
//#include <linux/platform_device.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO MrYang\n");
// platform_device_register(&mryang_device);
return 0;
}
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang\n");
// platform_device_unregister(&mryang_device);
}
module_init(mryang_init);
module_exit(mryang_exit);
无论注册设备还是驱动,我们都要用API函数以及定义结构体,这次也不例外,我们首先打开杂项设备的头文件看看
vim include/linux/miscdevice.h
搜索结构体miscdevice,以下就是我们要用到的部分
#define MISC_DYNAMIC_MINOR 255
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
extern int misc_register(struct miscdevice * misc);
extern int misc_deregister(struct miscdevice *misc);
不知道有没有注意到,以往我们代码只看了结构体和注册卸载函数,为什么会把宏定义也放上来呢?因为MISC_DYNAMIC_MINOR是动态次设备号,结构体内的minor需要我们指定次设备号是多少。
首先我们要包含头文件(注册设备节点的头文件后面再说)
#include <linux/miscdevice.h> // 注册杂项设备的头文件
#include <linux/fs.h> // 注册设备节点的结构体的头文件
我们要定义结构体,然后在probe函数里,把结构体传给注册函数进行注册(卸载同理)
static struct miscdevice mryang_dev= {
// 次设备号,可以指定次设备号是多少,若是想让linux自己去分配,则定义为宏定义
.minor = MISC_DYNAMIC_MINOR,
// 次设备的名字
.name = "mryang_misc_ctl",
.fops = &mryang_ops,
};
结构体为什么这么定义,我们举个例子
我们之前注册设备,有name叫mryang_ctl,其id为-1。怎么杂项设备就没有了呢?其实不然,此杂项设备非彼杂项设备,杂项设备misc是一个统称,misc才是一个有name有id的设备。而我们现在说的杂项设备,misc旗下的乱七八糟的设备,是这种杂项设备!
假如说misc有五个儿子,我们喊:misc的儿子出来一下,结果五个孩子根本不知道叫的谁。所以我们要为五个孩子分配一下次设备号minor,以及次设备的名字name,这下就对了。喊misc的1号儿子xxx出来,那么叫谁清清楚楚。
那么fops是什么呢?结构体内是这么定义的
const struct file_operations *fops;
他是一个文件操作的结构体,对于linux来说,一切皆文件,所以如果我们的应用要操控LED,自然是打开LED的文件描述符,写入1开,写入0关(这只是随便举的例子!)。这也是为什么定义头文件fs.h的原因。
没错我们又要定义一个结构体,叫mryang_ops,里面存放这操控这个杂项设备的各种文件操作函数,当然,这些操作函数我们也要写。。。函数该怎么定义需要去查看头文件看函数定义是如何定义的。
// 3个文件操作函数
static int mryang_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_open!\n");
return 0;
}
static int mryang_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_release\n");
return 0;
}
static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{
printk("cmd is %u\n",cmd);
printk("arg is %lu\n",arg);
return 0;
}
// file_operations 结构体 mryang_ops
static struct file_operations mryang_ops = {
.owner = THIS_MODULE,
.open = mryang_open,
.release = mryang_release,
.unlocked_ioctl = mryang_unlocked_ioctl,
};
然后我们把这个结构体mryang_ops传给我们注册杂项设备的结构体mryang_dev的成员fops。
这个流程感觉像一个套娃,你打开一个套娃,发现还要打开一个,你再打开,发现还要再打开三个…不过好在全部编写完成了。
此处挖坑…
结构体定义好了,我们在加载模块的时候注册,卸载模块的时候卸载就行,分别写入注册、卸载杂项设备的函数
// 写入加载模块的函数内
misc_register(&mryang_dev);
// 写入卸载模块的函数内
misc_deregister(&mryang_dev);
整个程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h> // 注册杂项设备的头文件
#include <linux/fs.h> // 注册设备节点的结构体的头文件
#define DEVICE_NAME "mryang_misc_ctl"
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
/*
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
*/
static int mryang_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_open!\n");
return 0;
}
static int mryang_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "mryang_release\n");
return 0;
}
static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{
printk("cmd is %u\n",cmd);
printk("arg is %lu\n",arg);
return 0;
}
static struct file_operations mryang_ops = {
.owner = THIS_MODULE,
.open = mryang_open,
.release = mryang_release,
.unlocked_ioctl = mryang_unlocked_ioctl,
};
static struct miscdevice mryang_dev= {
// 次设备号,可以指定次设备号是多少,若是想让linux自己去分配,则定义为宏定义
.minor = MISC_DYNAMIC_MINOR,
// 次设备的名字
.name = "mryang_misc_ctl",
.fops = &mryang_ops,
};
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO MrYang\n");
printk(KERN_EMERG "return %d\n", misc_register(&mryang_dev) );
return 0;
}
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang\n");
misc_deregister(&mryang_dev);
}
module_init(mryang_init);
module_exit(mryang_exit);
加载和卸载只会输出模块信息,那么我们怎么判断是否成功注册了杂项设备呢?
- misc_register()和之前的注册的函数都有返回值,我们可以在程序里打印他们的返回值来查看
- 使用命令查看
此命令查看设备节点
ls /dev
mryang_misc_ctl 确实出现在了返回的列表里
查看杂项设备号
cat /proc/misc
返回 46 mryang_misc_ctl,说明杂项设备的次设备有mryang_misc_ctl,其次设备号为46
结束
注册杂项设备虽然步骤跟注册设备类似,但是我们要注意到,我们现在说的注册杂项设备,其实只是为了在杂项设备misc下注册的,目的是为了给他分配一个次设备号(儿子就是儿子,地位和爸爸和叔叔不是同一辈啊…)
除了注册了杂项设备,我们还为设备生成了设备节点,以便上层调用。比如上层发出命令LED0开,LED1关。设备节点里的文件操作函数就会发挥作用。比如打开就调用mryang_open,关闭调用mryang_release,操作设备就调用mryang_unlocked_ioctl()传入命令和参数。
对于本节我们虽然写了 但是没有用上,而后面,我们就会来使用这些。