Linux模块(2) - 创建设备节点
今天计划将之前写的模块Linux模块(1) - 加载与卸载填充一下,达到设备节点创建的功能。
1. 全局数据
模块中有许多数据结构是要动态申请和释放的,就少不了一些全局数据来标记这些信息,这里通过宏MODULE_MISC_DEVICE
来区分下杂项设备
和普通设备的注册流程。
#include <linux/misdevice.h>
#include <linux/cdev.h>
struct driver_info { // 自定义结构体类型
#ifdef MODULE_MISC_DEVICE // 将模块作为杂项设备自动注册struct miscdevice miscdev; // 杂项设备结构体,会自动创建设备节点
#else // 将模块作为普通设备自动注册struct cdev cdev; // 设备结构体,可以使用动态内存申请,也可以静态定义struct class *class; // 用于自动创建设备节点
#endif
}
atomic_t mac_instance_num = ATOMIC_INIT(0); // 设备实例化的个数,用于创建设备文件时的数字后缀,避免多核驱动加载时产生竞争,采用原子类型
1.1 设备节点
linux设备文件都有一个主设备号
和次设备号
,主设备号
相同的设备使用相同的驱动,次设备号
决定了设备实例化的个数,部分主设备号
保留给特定的设备,其他值可以由用户自由使用。设备号可以在编写模块时指定,指定的设备号不能与系统中现有的所有设备冲突,也可以动态申请。
// include/linux/kdev_t.h --- 定义了`dev_t`的结构是一个`uint32_t`的数
#define MINORBITS 20 // 次设备号使用低20位,主设备号使用高12位
#define MINORMASK ((1U<<MINORIBITS)-1)
#define MAJOR(dev) ((unsigned int)((dev))>>MINORBITS) // 提取主设备号
#define MINOR(dev) ((unsigned int)((dev)&MINORMASK)) // 提取次设备号
#define MKDEV(ma, mi) (((ma)<<MINORBITS)|(mi)) // 合成设备号
1.2 misdevice
杂项设备
是指主设备号
固定为10
的设备,内核为杂项设备提供了一套统一的驱动,在驱动内部再根据次设备号
调用模块中用户定义的接口。
// drivers/char/misc.c
// 向内核注册设备信息,同时创建设备节点,设备节点的名称即misc->name
int misc_register(struct misdevice *misc)
{...bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR); // 杂项设备的次设备号支持指定和动态申请...
}
// 注销设备,删除设备节点
void misc_deregister(struct miscdevice *misc)
{...
}
1.3 cdev
字符设备
是Linux常用设备中的一种,内核对此抽象了一个结构体struct cdev
。
// fs/char_dev.c
// 动态申请一个结构体
struct cdev *cdev_alloc(void)
{struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);if (p) {INIT_LIST_HEAD(&p->list);// ktype_cdev_dynamic中定义了一个release()函数,该函数会用于内存的释放kobject_init(&p->kobj, &ktype_cdev_dynamic);}return p;
}
// 静态定义的初始化
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{memset(cdev, 0, sizeof(*cdev));INIT_LIST_HEAD(&cdev->list);// ktype_cdev_default中定义了一个release()函数,该函数仅用于从内核中移除相关信息kobject_init(&cdev->kobj, &ktype_cdev_default);cdev->ops = fops; // 比cdev_alloc()多了一步
}
// 填充设备信息,并添加到内核中
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{...p->dev = dev; // 赋值设备号p->count = count; // 赋值设备编号,即第几个设备...
}
// 移除设备信息,无论动态申请的还是静态初始化的都使用该函数,动态的内存会自动释放
void cdev_del(struct cdev *p)
{...
}
1.4 class
这一部分暂未做详细了解,大致意思是通过struct class
在文件系统中创建/sys/class/mac
文件夹,里面记录了设备节点的信息,再由udev
来创建对应的设备文件。
// include/linux/device.h
// 动态申请一个class数据结构并注册,class_create本身是一个宏定义
struct class *class_create(struct module *ower, const char *name)
{...
}
// drivers/base/class.c
// 注销并释放
void class_destroy(struct class *class)
{...
}
创建了class
之后还需要根据class
创建一个设备并注册到文件系统中。
// drivers/base/core.c
// 创建一个设备,`parent`和`drvdata`可以为`NULL`,`fmt`表示设备文件的名称
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
{...// 返回值可能是负值,也可能是正常的指针,应当使用`IS_ERR()`进行判断,使用`PTR_ERR()`转换结果为错误值
}
// 注销设备文件
void device_destroy(struct class *class, dev_t devt)
{...
}
// include/linux/err.h
static inline bool IS_ERR(void *ptr)
{return unlikely((unsigned long)(void*)ptr >= (unsigned long)-4095);
}
static inline long PTR_ERR(void *ptr)
{return (long)ptr;
}
1.5 设备号
// fs/char_dev.c
// 动态申请一个主设备号,和一段连续的次设备号,并完成注册
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, uinsigned count, const char *name)
{...__register_chrdev_region(0, baseminor, count, name); // 0表示申请新的主设备号,非0表示沿用主设备号{find_dynamic_major(); // 查找空闲主设备号{// fs/char_dev.c#define CHRDEV_MAJOR_HASH_SIZE 255 // 静态分配// include/linux/fs.h#define CHRDEV_MAJOR_MAX 512 // 设定最大主设备号为512#define CHRDEV_MAJOR_DYN_END 234 // 第一区域,明显第一区域被静态分配了无法申请#define CHRDEV_MAJOR_DYN_EXT_START 511#define CHRDEV_MAJOR_DYN_EXT_END 384 // 第二区域倒着分配}...*dev = MKDEV(cd->major, cd->baseminor); 返回申请到的起始设备号return 0; // 0成功
}
// 静态注册一段连续的设备号
int register_chrdev_region(dev_t form, unsigned count, const char *name)
{...__register_chrdev_region(MAJOR(n), MINOR(n), next-n, name); // 按主设备号进行注册...
}
// 取消注册,对于动态申请的会给予释放
void unregister_chrdev_region(dev_t form, unsigned count)
{...
}
1.6 设备计数
一份驱动可能会适配到多个硬件设备,则设备名需要递增,就需要一个静态数据进行累加,该数据需要原子属性,该属性通常由汇编实现,与CPU架构有关。
#include <linux/atomic.h>
typedef struct {int counter; // 就是个普通的整形,只是在执行读写操作时要采用专用函数,由函数确保原子操作
} atomic_t;// arch/csky/include/asm/atomic.h
int atomic_add_return(int i, atomic_t *v); // 加i并返回最新值
int atomic_sub_return(int i, atomic_t *v); // 减i并返回最新值
...
2. probe
模块加载后,若驱动和设备的名称匹配,则会调用struct platform_driver->probe()
函数,则我们可以再probe()
中完成设备的初始化和注册动作。
int mac_probe(struct platform_device *pdev)
{struct driver_info *mac;int ret, instance_num;struct device *device;// 使用devm_前缀的函数,能够自动进行内存的释放,当然也可以手动释放// 申请一个结构体用于标记我们申请的各种内存,以便最后释放mac = mdev_kzalloc(&pdev->dev, sizeof(struct driver_info), GFP_KERNEL); // 错误返回NULLif (unlikely(mac == NULL)) { // 预期不会出现de_err(&pdev->dev, "alloc mac failed\n"); // 内核日志函数,具备打印等级控制的功能ret = -ENOMEM; // 内存不足goto go_ret; // 驱动加载过程较多故需要采用goto语句,逐层返回}instance_num = atomic_add_return(1, &mac_instance_num) - 1; // 获取当前已注册设备的数量,从0开始,后续若注册失败也不再回减,以免发生冲突#ifdef MODULE_MISC_DEVICE // 杂项设备注册流程mac->miscdev.minor = MISC_DYNAMIC_MINOR; // 设置次设备号进行动态分配,当然也可以手动指定mac->miscdev.fops = &mac_file_operations; // 设备文件操作接口,我们稍后定义mac->miscdev.parent = &pdev_dev; // 暂不清楚作用// C库中也有一个asprintf()函数作用类似mac->miscdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%d", "mac", instance_num); // 申请一块内存,用于存放格式化的字符串,该字符串用于设备文件名,错误返回NULLif (unlikely(mac->miscdev.name == NULL)) {de_err(&pdev->dev, "alloc miscdev name failed\n");ret = -ENOMEM; // 内存不足goto go_kfree_mac;}ret = misc_register(&mac->miscdev); // 杂项设备注册,返回值使用IS_ERR()检查if (IS_ERR(ret)) {de_err(&pdev->dev, "misc register[%s] failed\n", mac->miscdev.name);goto go_kfree_name;}
#else // 以下是普通设备的注册流程cdev_init(&mac->cdev, &mac_file_operations); // 静态初始化cdev,设备文件操作接口我们稍后定义mac->cdev.owner = THIS_MODULE; // 指定当前模块为cdev拥有者,THIS_MODULE,是一个结构体指针ret = alloc_chrdev_region(&mac->cdev.dev, instance_num, 1, "mac"); // 申请设备号,当然也可以不用该函数,手动指定if (IS_ERR(ret)) {de_err(&pdev->dev, "alloc chrdev failed\n");goto go_kfree_mac;}ret = cdev_add(&mac->cdev, mac->cdev.dev, 1); // 注册cdevif (IS_ERR(ret)) {de_err(&pdev->dev, "cdev add failed\n");goto go_unregister_chrdev;}mac->class = class_create(THIS_MODULE, "mac"); // 创建class,用于设备文件的创建if (IS_ERR(mac->class)) {de_err(&pdev->dev, "class create failed\n");goto go_cdev_del;}// 创建设备文件,文件名为mac%u格式化后的字符串device = device_create(mac->class, NULL, mac->cdev.dev, NULL, "mac%u", instance_num);if (IS_ERR(device)) {de_err(&pdev->dev, "device create failed\n");ret = PTR_ERR(device);goto go_class_destroy;}
#endifplatform_set_drvdata(pdev, mac); // 等同于pdev->dev.driver_data = mac;dev_info(&pdev->dev, "mac");return 0;#ifdef MODULE_MISC_DEVICEgoto go_misc_deregister; // 不会运行到这
go_misc_deregister:misc_deregister(&mac->miscdev);
go_kfree_name:devm_kfree(&pdev->dev, (void *)mac->miscdev.name);
#elsegoto go_device_destory; // 不会运行到这
go_device_destory:device_destory(mac->class, mac->cdev.dev); // mac->cdev.dev是设备号
go_class_destroy:class_destroy(mac->class);
go_cdev_del:cdev_del(&mac->cdev);
go_unregister_chrdev:unregister_chrdev_region(mac->cdev.dev, 1); // mac->cdev.dev是设备号
#endif
go_kfree_mac:devm_kfree(&pdev->dev, mac);
go_ret:return ret;
}
3. remove
有加载就有卸载,我们将probe()
函数goto
的内容拷贝过来即可。
int mac_remove(struct platform_device *pdev)
{struct driver_info *mac = platform_get_drvdata(pdev); // 等同于pdev->dev.driver_data#ifdef MODULE_MISC_DEVICEmisc_deregister(&mac->miscdev);devm_kfree(&pdev->dev, (void *)mac->miscdev.name);
#elsedevice_destory(mac->class, mac->cdev.dev); // mac->cdev.dev是设备号class_destroy(mac->class);cdev_del(&mac->cdev);unregister_chrdev_region(mac->cdev.dev, 1); // mac->cdev.dev是设备号
#endifdevm_kfree(&pdev->dev, mac);
}
4. 文件操作接口
Linux下一切皆文件,内核对文件的操作也做了抽象。
// include/linux/fs.h
struct file_operations {struct module *owner;ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);int (*open)(struct inode *, struct file *);int (*release)(struct *inode *, struct file *); // 实现close的动作...
}
暂时先实现几个空函数用于测试。
static int mac_open(struct inode *inode, struct file *file)
{printk("%s\n", __func__);return 0;
}
static int mac_close(struct *inode *, struct file *)
{printk("%s\n", __func__);return 0;
}
static ssize_t mac_read(struct file *file, char __user dst*, size_t len, loff_t *off)
{printk("%s\n", __func__);return 0;
}
static ssize_t mac_write(struct file *file, const char __user *src, size_t len, loff_t *off)
{printk("%s\n", __func__);return 0;
}
static struct file_operations mac_file_operations = {.owner = THIS_MODULE,.open = mac_open;.release = mac_close,.read = mac_read,.write = mac_write,
};
5. 用户程序
目前模块已经有设备文件了,就可以通过用户程序打开或关闭设备文件了。
main.c
#include <stdio.h> #include <fcntl.h> #include <unistd.h>int main(void) {int fd;fd = open("/dev/mac0", O_RDWR);if (fd < 0) {perror("/dev/mac0");}close(fd);return 0; }
编译、下载(略)、运行
# 主机上交叉编译 $ csky-abiv2-linux-gcc main.c -mcpu=ck860 -O2 -W -Wall -o test # 板卡运行 $ insmod mac.ko [77401.056269] mac mac.0: mac # dev_info(&pdev->dev, "mac");打印的 $ ./test # 执行用户程序 [77405.099229] mac_open # mac_open()函数正常调用了 [77405.101611] mac_close $ ls /dev/mac0 -l crw-rw---- 1 root root 250, 0 Jan 1 21:29 /dev/mac0 # 字符设备文件,设备号250,0 $ cat /proc/devices # 这里记录了系统中所有的设备号信息 Character devices: ... 250 mac ... $ cat /sys/class/mac/mac0/dev # 查看/sys/class中的文件 250:0 $ rmmod [77912.745661] mac_release
下一章Linux模块(3) - 资源映射。
Linux模块(2) - 创建设备节点相关推荐
- linux使用DEVICE_ATTR创建设备节点(常用)
DEVICE_ATTR是一个宏,其定义在include/linux/device.h文件: #define DEVICE_ATTR(_name, _mode, _show, _store) \ str ...
- linux创建设备 mknod,linux用mknod创建设备(节点)
mknod命令用于创建一个设备文件,即特殊文件 首先要明白什么是设备文件,简单的我们说 操作系统与外部设备(入磁盘驱动器,打印机,modern,终端 等等)都是通过设备文件来进行通信的,在Unix/L ...
- linux内核创建节点,Linux内核驱动自动创建设备节点文件
Linux下生成驱动设备节点文件的方法有3个:1.手动mknod:2.利用devfs:3.利用udev 在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上L ...
- linux 设备节点 驱动,【Linux驱动】自动创建设备节点
开始学习驱动的时候,是将驱动程序编译成模块然后用mknod命令手动建立设备节点以提供给应用程序调用.这对于刚开始调试驱动程序的时候常用的一种方法.但是,当有种需要必须在系统启动的时候就将驱动程序就绪, ...
- linux驱动:自动创建设备节点
在加载驱动模块后,就要自己使用mknod创建设备节点,这样虽然是可行的,但是比较麻烦.我们可以在__init()函数里面添加一些函数,自动创建设备节点.创建设备节点使用了两个函数 class_crea ...
- Linux 字符设备驱动结构(二)—— 自动创建设备节点
上一篇我们介绍到创建设备文件的方法,利用cat /proc/devices查看申请到的设备名,设备号. 第一种是使用mknod手工创建:mknod filename type major minor ...
- linux字符驱动之自动创建设备节点
上一节中,我们是手工创建设备节点,大家肯定也会觉得这样做太麻烦了. 上一节文章链接:https://blog.csdn.net/qq_37659294/article/details/10430270 ...
- linux自动创建设备节点
在有2.6系列版本中支持udev管理设备文件可以方便的创建设备节点,不必使用mknod来创建 //主要用到的四个方法在linux/device.h定义: //创建类和释放类的函数 创建成后将创建/s ...
- Linux驱动(11)--生成设备节点
生成设备节点 1. 生成设备节点 1.1 杂项设备 1.2 注册文件 1.3 生成设备节点源代码 1.4 生成设备节点步骤 1.5 需要注意的问题 2. 调用设备节点 1. 生成设备节点 1.1 杂项 ...
最新文章
- 电路非门_【连载】电路和维修基础之门电路、转换器
- python3 shell 正则表达式 攫取复杂字符串特定子串
- ArcGis 10+Oracle发布WFS-T服务,无法更新Feature的解决方法
- Apache Flink 零基础入门(十六)Flink DataStream transformation
- 牛客练习赛74 D CCA的图
- Java中的低GC:使用原语而不是包装器
- noip退役之路--祝福
- vscode添加源文件_VSCode自制的IDE编译多个源文件
- 利用Python实现定时发送邮件,实现一款营销工具
- 五月数据库技术通讯丨Oracle 12c因新特性引发异常Library Cache Lock等待
- Eclipse正式代替Oracle接管Java EE
- 洛谷——P1162 填涂颜色【bfs】
- 联想怎么启用计算机的无线功能,IdeaCentre B3系列电脑无线连接中心的使用方法...
- 红外测距模块工作原理_红外测温仪方案工作原理
- Apache与Tomcat有什么关系和区别
- python实现指纹识别毕业论文_指纹识别技术毕业论文-指纹识别密码锁毕业论文...
- fastlane 问题记录
- FHIR标准和国际基于FHIR的互联互通实践(7):国际互联互通实践
- 移动用户免费领取15G流量(秒到)
- 尝试关闭阿里云ESC的阿里云盾相关服务