今天计划将之前写的模块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) - 创建设备节点相关推荐

  1. linux使用DEVICE_ATTR创建设备节点(常用)

    DEVICE_ATTR是一个宏,其定义在include/linux/device.h文件: #define DEVICE_ATTR(_name, _mode, _show, _store) \ str ...

  2. linux创建设备 mknod,linux用mknod创建设备(节点)

    mknod命令用于创建一个设备文件,即特殊文件 首先要明白什么是设备文件,简单的我们说 操作系统与外部设备(入磁盘驱动器,打印机,modern,终端 等等)都是通过设备文件来进行通信的,在Unix/L ...

  3. linux内核创建节点,Linux内核驱动自动创建设备节点文件

    Linux下生成驱动设备节点文件的方法有3个:1.手动mknod:2.利用devfs:3.利用udev 在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上L ...

  4. linux 设备节点 驱动,【Linux驱动】自动创建设备节点

    开始学习驱动的时候,是将驱动程序编译成模块然后用mknod命令手动建立设备节点以提供给应用程序调用.这对于刚开始调试驱动程序的时候常用的一种方法.但是,当有种需要必须在系统启动的时候就将驱动程序就绪, ...

  5. linux驱动:自动创建设备节点

    在加载驱动模块后,就要自己使用mknod创建设备节点,这样虽然是可行的,但是比较麻烦.我们可以在__init()函数里面添加一些函数,自动创建设备节点.创建设备节点使用了两个函数 class_crea ...

  6. Linux 字符设备驱动结构(二)—— 自动创建设备节点

    上一篇我们介绍到创建设备文件的方法,利用cat /proc/devices查看申请到的设备名,设备号. 第一种是使用mknod手工创建:mknod filename type major minor ...

  7. linux字符驱动之自动创建设备节点

    上一节中,我们是手工创建设备节点,大家肯定也会觉得这样做太麻烦了. 上一节文章链接:https://blog.csdn.net/qq_37659294/article/details/10430270 ...

  8. linux自动创建设备节点

    在有2.6系列版本中支持udev管理设备文件可以方便的创建设备节点,不必使用mknod来创建 //主要用到的四个方法在linux/device.h定义: //创建类和释放类的函数  创建成后将创建/s ...

  9. Linux驱动(11)--生成设备节点

    生成设备节点 1. 生成设备节点 1.1 杂项设备 1.2 注册文件 1.3 生成设备节点源代码 1.4 生成设备节点步骤 1.5 需要注意的问题 2. 调用设备节点 1. 生成设备节点 1.1 杂项 ...

最新文章

  1. 电路非门_【连载】电路和维修基础之门电路、转换器
  2. python3 shell 正则表达式 攫取复杂字符串特定子串
  3. ArcGis 10+Oracle发布WFS-T服务,无法更新Feature的解决方法
  4. Apache Flink 零基础入门(十六)Flink DataStream transformation
  5. 牛客练习赛74 D CCA的图
  6. Java中的低GC:使用原语而不是包装器
  7. noip退役之路--祝福
  8. vscode添加源文件_VSCode自制的IDE编译多个源文件
  9. 利用Python实现定时发送邮件,实现一款营销工具
  10. 五月数据库技术通讯丨Oracle 12c因新特性引发异常Library Cache Lock等待
  11. Eclipse正式代替Oracle接管Java EE
  12. 洛谷——P1162 填涂颜色【bfs】
  13. 联想怎么启用计算机的无线功能,IdeaCentre B3系列电脑无线连接中心的使用方法...
  14. 红外测距模块工作原理_红外测温仪方案工作原理
  15. Apache与Tomcat有什么关系和区别
  16. python实现指纹识别毕业论文_指纹识别技术毕业论文-指纹识别密码锁毕业论文...
  17. fastlane 问题记录
  18. FHIR标准和国际基于FHIR的互联互通实践(7):国际互联互通实践
  19. 移动用户免费领取15G流量(秒到)
  20. 尝试关闭阿里云ESC的阿里云盾相关服务

热门文章

  1. 游戏服务器开发环境搭建
  2. wegame 取消直播提醒
  3. 8.12 Web前端-小米商城项目实战
  4. “回复TD即可退订” 是真的吗?
  5. 【Java】绘图入门和机制,绘图方法演示(绘制坦克)
  6. 2011年很美的118句话
  7. node抓取58同城信息_如何使用标准库和Node.js轻松抓取网站以获取信息
  8. STM32的RS485通信
  9. 有限差分——图像求偏导
  10. proftpd java_基于 proftpd 配置加密 FTP