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

声明

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

本节目标

  1. 注册杂项设备
  2. 生成设备节点

2018-12-07
  在本节之前要啰嗦一下,刚开始学的时候,越往后学会越迷糊。所以一定要清楚概念,记不清哪个函数都行,找一找就有了,但一定要理清整个思路,以及意义。比如我现在其实就还比较模糊。

  我们之前提到过,主、次设备号这个概念。主设备号有256个,编号是0-255,但是一些常用设备由linux集成进去占用了部分编号,且0和255保留不使用,其他的主设备号,可以由我们去使用。

  我们可以用命令cat /proc/devices查看已有的字符设备的主设备号。

  对于本节而言,讲的就是主设备号为10的设备 — 杂项设备

正文

  对于一个普通的字符设备来说,我们要注册设备,注册驱动,生成设备节点。

  什么是设备节点?上层应用想要操作一个设备,需要打开设备节点来操作,而非直接操作硬件设备(毕竟是系统而非裸机)。这就是设备节点的作用。(大致有这么个概念即可,后面会讲设备节点)

  如果每一个设备都按普通的字符设备来注册,有个重要的问题是需要分配主设备号,如果每个设备都分配主设备号无疑是一种资源浪费,而且一系列注册过程中需要调用各种各样的API。

  因此,杂项设备出现了,他是一个普通的字符设备的一种封装。

  第一,不需要给他分配主设备号,因为杂项设备的主设备号固定是10。

  第二,简化步骤,匹配了设备和驱动之后,可以直接生成设备节点。

  所以一个杂项设备,我们的步骤应该是,注册platform设备,注册platform驱动,在probe()函数里选择把设备注册成杂项设备(分配次设备号,编写操作函数以供上层调用)。

注册驱动部分的模板

注册设备和注册驱动之前都做过,所以我们直接在此基础上做模板

#include <linux/init.h>
#include <linux/module.h>
// platform相关头文件(设备、驱动的结构体和函数)
#include <linux/platform_device.h>
// 驱动名(与设备名一致)
#define DRIVER_NAME "mryang_ctl"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");

/* 加载驱动,设备、驱动匹配成功,则调用probe()函数 */
int mryang_probe(struct platform_device *pdv)
{
	printk(KERN_EMERG "probe!\n");
	return 0;
}

/* 卸载驱动调用remove()函数 */
int mryang_remove(struct platform_device *pdv)
{
	printk(KERN_EMERG "remove!\n");
	return 0;
}

/* 驱动结构体 */
struct platform_driver mryang_driver = {
	.probe = mryang_probe,
	.remove = mryang_remove,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	}
};

/* 加载模块 */
static int mryang_init(void)
{
	printk(KERN_EMERG "HELLO MrYang\n");
	platform_driver_register(&mryang_driver);
	return 0;
}

/* 卸载模块 */
static void mryang_exit(void)
{
	printk(KERN_EMERG "Bye MrYang\n");
	platform_driver_unregister(&mryang_driver);
}

module_init(mryang_init);
module_exit(mryang_exit);

  我们需要关注的 就是probe()函数,设备和驱动匹配了,我们下一步就是把设备注册为杂项设备,下面慢慢讲解。

  无论注册设备还是驱动,我们都要用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);

不知道有没有注意到,以往我们代码只看了结构体和注册卸载函数,为什么会把宏定义也放上来呢?因为杂项设备需要分配次设备号,我们可以指定,也可以由linux自动分配,当次设备号被赋值为MISC_DYNAMIC_MINOR时,则表示自动分配次设备号。

首先我们要包含头文件

#include <linux/miscdevice.h>			// 注册杂项设备的头文件

我们先定义结构体,然后在probe函数里,把结构体传给注册函数进行注册(卸载同理)

static  struct miscdevice mryang_dev= {
	// 次设备号,可以指定次设备号是多少,若是想让linux自己去分配,则定义为宏定义
	.minor = MISC_DYNAMIC_MINOR,
	// 次设备的名字
	.name = "mryang_misc_ctl",
	.fops = &mryang_ops,			// 这个待会儿说
};

我们定义了这个结构体之后,在probe()函数里面注册即可,同理,卸载就在remove()里注册

驱动设备匹配成功,则加载probe(),卸载驱动,则会调用remove()进行移除

那么fops是什么呢?打开结构体看是这么定义的

const struct file_operations *fops;

  他是一个文件操作的结构体,对于linux来说,一切皆文件,所以如果我们的应用要操控LED,自然是打开LED的设备节点,写入1开,写入0关(这只是随便举的例子!)。

  自然先要包含fs.h这个头文件,编写file_operations类型的结构体。

  结构体很长,就不放上来了,有兴趣最好自己打开看一下struct file_operations这个结构体。打开之后会发现有很多的函数指针,看名字可以看出来是用于上层应用调用的,比如open(),release()等等,当然这个也有owner,我们赋值为THIS_MODULE即可(以前有讲过),对于这些函数变量,我们也要编写相应的函数,因为下一节会讲编写一个简单的应用去操作设备,现在就是为了给上层调用而写代码。

// 生成设备节点的结构体的头文件
#include <linux/fs.h>

// file_operations 结构体 mryang_ops
static struct file_operations mryang_ops = {
	.owner = THIS_MODULE,
	// 以下都是函数指针,一会儿还要编写调用函数
	// 打开设备节点时调用此函数
	.open = mryang_open,
	// 关闭设备节点时调用此函数
	.release = mryang_release,
	// ioctl操作时,调用此函数
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

  对于这个结构体,我们就简单的写了三个成员函数,打开、关闭、操作。所以接下来我们还要编写这三个函数,当上层应用打开、关闭、操作设备节点时会分别调用这几个函数。

// 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)
{
	// 打印由上层应用调用ioctl时,参数里cmd的值
	printk("cmd is %u\n",cmd);
	// 打印由上层应用调用ioctl时,参数里arg的值
	printk("arg is %lu\n",arg);
	return 0;
}

  这样,我们的程序就完成了。

整个程序

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include <linux/miscdevice.h>			// 注册杂项设备的头文件
#include <linux/fs.h>					// 注册设备节点的结构体的头文件

#define DRIVER_NAME "mryang_ctl"

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");

// 打开设备节点时调用此函数
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;
}
// ioctl操作时,调用此函数
static long mryang_unlocked_ioctl( struct file *files, unsigned int cmd, unsigned long arg)
{
	// 打印由上层应用调用ioctl时,参数里cmd的值
	printk("cmd is %u\n",cmd);
	// 打印由上层应用调用ioctl时,参数里arg的值
	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,
	// ioctl操作时,调用此函数
	.unlocked_ioctl = mryang_unlocked_ioctl,
};

static  struct miscdevice mryang_dev= {
	// 次设备号,可以指定次设备号是多少,若是想让linux自己去分配,则定义为宏定义
	.minor = MISC_DYNAMIC_MINOR,
	// 次设备的名字
	.name = "mryang_misc_ctl",
	
	.fops = &mryang_ops,
};

/* 加载驱动,设备、驱动匹配成功,则调用probe()函数 */
int mryang_probe(struct platform_device *pdv)
{
	printk(KERN_EMERG "probe!\n");
	// 注册杂项设备
	misc_register(&mryang_dev);
	return 0;
}

/* 卸载驱动调用remove()函数 */
int mryang_remove(struct platform_device *pdv)
{
	printk(KERN_EMERG "remove!\n");
	// 卸载杂项设备
	misc_deregister(&mryang_dev);
	return 0;
}

/* 驱动结构体 */
struct platform_driver mryang_driver = {
	.probe = mryang_probe,
	.remove = mryang_remove,
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	}
};

/* 加载模块 */
static int mryang_init(void)
{
	printk(KERN_EMERG "HELLO MrYang\n");
	platform_driver_register(&mryang_driver);
	return 0;
}

/* 卸载模块 */
static void mryang_exit(void)
{
	printk(KERN_EMERG "Bye MrYang\n");
	platform_driver_unregister(&mryang_driver);
}

module_init(mryang_init);
module_exit(mryang_exit);

代码看起来很长,其实分解开来,是很简单的。

  1. 起始都是module_init,加载模块就调用他的参数,我们就去看mryang_init()
  2. 在mryang_init()里我们注册了驱动,驱动注册之后通过名字成功匹配设备则调用probe()函数(驱动的结构体内定义的)
    3.我们再probe()里把设备注册为了杂项设备,自动生成了设备节点(杂项设备的结构体定义的)
  3. 若上层应用操作这个设备节点,则会调用文件操作结构体。根据操作不同,调用不同的函数。

这就是整个流程。一步一步来,很清晰。

最后

我们怎么判断是否成功注册了杂项设备呢?

  1. 对于注册类函数都有返回值,我们可以声明一个变量,来根据返回值来判断是否注册成功(我的代码懒得写了…比如int ret; ret = misc_deregister(…),若ret为负数则注册失败,写个if判断一下就行了。)

  2. 使用命令 ls /dev查看所有的设备节点

mryang_misc_ctl 确实出现在了返回的列表里

  1. 使用命令shellcat /proc/misc再查看一下杂项设备里有没有我们注册的杂项设备的次设备号。

返回 46 mryang_misc_ctl,说明杂项设备的次设备有mryang_misc_ctl,其次设备号为46

结束

对于学习linux,没必要专门去记那些API函数,主要学习的是这些思想,API函数都是可以查到的,记这些干啥,重点关注的是这些思想,这些概念。比如杂项设备和普通字符设备的区别等等…

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值