【迅为iTop4412学习笔记】8.注册杂项设备,以及生成设备节点

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39057987/article/details/84502555

声明

以下都是我刚开始看驱动视频的个人强行解读,如果有误请指出,共同进步。

本节目标

注册杂项设备,并生成设备节点


首先讲一下之前

我们说过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);

加载和卸载只会输出模块信息,那么我们怎么判断是否成功注册了杂项设备呢?

  1. misc_register()和之前的注册的函数都有返回值,我们可以在程序里打印他们的返回值来查看
  2. 使用命令查看

此命令查看设备节点

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()传入命令和参数。

对于本节我们虽然写了 但是没有用上,而后面,我们就会来使用这些。

猜你喜欢

转载自blog.csdn.net/qq_39057987/article/details/84502555
今日推荐