当前位置: 首页 > article >正文

【梦想终会实现】Linux驱动学习6

1、ioctrl接口的应用层与驱动层实现

// 驱动层部分,对应ioctrl的cmd:PWM_IOCTL_SET_FREQ、PWM_IOCTL_STOP
static int x210_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
	switch (cmd) 
	{
		case PWM_IOCTL_SET_FREQ:
			printk("PWM_IOCTL_SET_FREQ:\r\n");
			if (arg == 0)
				return -EINVAL;
			PWM_Set_Freq(arg);
			break;

		case PWM_IOCTL_STOP:
		default:
			printk("PWM_IOCTL_STOP:\r\n");
			PWM_Stop();
			break;
	}

	return 0;
}

// file_operations 结构体
static struct file_operations dev_fops = {
    .owner   =   THIS_MODULE,
    .open    =   x210_pwm_open,
    .release =   x210_pwm_close, 
    .ioctl   =   x210_pwm_ioctl,
};

// 中间件层实现
fd = open("dev/buzzer", O_RDWR);
if (fd < 0)
{
    ...
}
ioctrl(fd, PWM_IOCTL_SET_FREQ, 1000); // 1000ms响一次
sleep(3);
ioctrl(fd, PWM_IOCTL_SET_FREQ, 300);  // 300ms响一次
sleep(3);

为何ioctrl存在而不是直接使用write?

明显为write仅支持不复杂的数据写入,若针对复杂的数据结构,若还用write接口,无疑会增加难度,故使用ioctrl接口,以应对复杂的控制。类似于文件的读、写、执行属性一样,ioctrl类似于执行这个属性。当然也可以通过给write写0.1.2.3这些魔法数字,驱动层根据不同的case来做特定处理,但这样的魔法数字会导致中间件层无法理解。

2、杂散设备:蜂鸣器、ADC

为什么还要给杂散设备归个类呢?

就是为了使用杂散类的接口,给该设备创建个主次设备号。

杂散设备kernel源码分析:

misc.c

static LIST_HEAD(misc_list);   // 定义双向链表头节点,以实现内核中杂项类设备的添加。

static DEFINE_MUTEX(misc_mtx); // 定义misc_mtx互斥锁

#define MISC_MAJOR		10     // 杂散类设备主设备号均为10,此设备号不同
#define MISC_DYNAMIC_MINOR	255

static int __init misc_init(void)
{
	int err;

#ifdef CONFIG_PROC_FS          // /proc虚拟文件系统,已不再使用
	proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
	misc_class = class_create(THIS_MODULE, "misc");     // 创建misc类
	err = PTR_ERR(misc_class);
	if (IS_ERR(misc_class))
		goto fail_remove;

	err = -EIO;
	if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))  // 注册主设备号
		goto fail_printk;
	misc_class->devnode = misc_devnode;
	return 0;

fail_printk:
	printk("unable to get major %d for misc devices\n", MISC_MAJOR);
	class_destroy(misc_class);
fail_remove:
	remove_proc_entry("misc", NULL);
	return err;
}

int misc_register(struct miscdevice * misc)
{
	struct miscdevice *c;
	dev_t dev;
	int err = 0;

	INIT_LIST_HEAD(&misc->list);                // 杂项类设备使用链表管理,此为初始化链表,使其指向自己。

	mutex_lock(&misc_mtx);
	list_for_each_entry(c, &misc_list, list) {  // 遍历次设备号
		if (c->minor == misc->minor) {
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		}
	}

	if (misc->minor == MISC_DYNAMIC_MINOR) {   // 次设备号创建,若为255则交由内核分配从设备号
		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);  // 找到第一个为0的位
		if (i >= DYNAMIC_MINORS) {
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		}
		misc->minor = DYNAMIC_MINORS - i - 1;
		set_bit(i, misc_minors);
	}

	dev = MKDEV(MISC_MAJOR, misc->minor);   // 组合主次设备号成设备唯一的设备号

	misc->this_device = device_create(misc_class, misc->parent, dev,
					  misc, "%s", misc->name); // 根据主设备号和次设备号创建一个设备节点
	if (IS_ERR(misc->this_device)) {
		int i = DYNAMIC_MINORS - misc->minor - 1;
		if (i < DYNAMIC_MINORS && i >= 0)
			clear_bit(i, misc_minors);
		err = PTR_ERR(misc->this_device);
		goto out;
	}

	/*
	 * Add it to the front, so that later devices can "override"
	 * earlier defaults
	 */
	list_add(&misc->list, &misc_list);
 out:
	mutex_unlock(&misc_mtx);
	return err;
}

3、 设备号温故:

设备号创建温故:

register_chrdev() 分配主设备号,第一个入参0,表示交由内核分配主设备号,同时注册了所有的次设备号。可以直接绑定到内核中。

register_chrdev_region()  静态分配从设备号

* register_chrdev_region() 函数 - 注册一组设备编号
* @from: 指定设备编号范围内的第一个编号;必须包含主设备号。若为0,表示自由分配一个主设备号。
* @count: 需要注册的连续设备编号的数量
* @name: 设备或驱动程序的名称。*

alloc_chrdev_region()      动态分配从设备号

* `alloc_chrdev_region()` 函数 - 注册一组字符设备编号
* @dev:输出参数,用于存储分配的第一个编号
* @baseminor:所请求的编号范围中的最小编号
* @count:所需的编号数量
* @name:与该设备或驱动程序相关联的名称*
* 分配一系列的字符设备编号。 主设备编号将动态选取,并与第一个从设备编号一同返回至 @dev 中。 返回零或一个负的错误代码。

// 经典源码分析
dev_t devt = MKDEV(arg->dev_major, arg->dev_minor);
if (!MAJOR(devt))  // 若不存在主设备号,则动态分配。
    rc = alloc_chrdev_region(&devt, MINOR(devt), 1, devinfo.name);
else               // 若存在主设备号,则使用。
    rc = register_chrdev_region(devt, 1, devinfo.name);

4、创建类和创建设备不冲突。

类创建用class_create(),设备创建用device_create(),设备创建时需要主次设备号,用***_chrdev_***相关函数创建即可,随后使用cdev绑定到内核。device_create()关联应用层的udev处理,即上抛相关信息,上层接收到后创建/删除对应的设备文件。同时可以使用device_create_file的形式创建一些属性,以实现用户可以在/sys目录下操作内核。

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/sysfs.h>
#include <linux/uaccess.h>

static struct cdev my_cdev;
static dev_t dev_number;
static struct class *cls;
static int my_value = 0; // 示例属性值

// 定义属性结构体
static struct device_attribute my_attr = {
    .attr = {
        .name = "my_value",
        .mode = S_IRUGO | S_IWUSR,
    },
    .show = my_value_show,
    .store = my_value_store,
};

// 实现 show 函数
static ssize_t my_value_show(struct device *dev, struct device_attribute *attr, char *buf) {
    return sprintf(buf, "%d\n", my_value);
}

// 实现 store 函数
static ssize_t my_value_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
    int ret = kstrtol(buf, 10, &my_value);
    if (ret < 0)
        return ret;
    return count;
}

static int __init my_init_module(void) {
    // 分配设备号
    alloc_chrdev_region(&dev_number, 0, 1, "my_device");

    // 初始化 cdev 结构体
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;

    // 添加 cdev 到内核
    cdev_add(&my_cdev, dev_number, 1);

    // 创建设备类
    cls = class_create(THIS_MODULE, "my_class");

    // 创建设备节点
    struct device *dev = device_create(cls, NULL, dev_number, NULL, "my_device");

    // 创建 sysfs 属性文件
    if (IS_ERR(device_create_file(dev, &my_attr))) {
        pr_err("Failed to create sysfs attribute\n");
        return PTR_ERR(device_create_file(dev, &my_attr));
    }

    return 0;
}

static void __exit my_exit_module(void) {
    // 销毁 sysfs 属性文件
    device_remove_file(cls->dev, &my_attr);

    // 销毁设备节点
    device_destroy(cls, dev_number);

    // 销毁设备类
    class_destroy(cls);

    // 删除 cdev
    cdev_del(&my_cdev);

    // 释放设备号
    unregister_chrdev_region(dev_number, 1);
}

module_init(my_init_module);
module_exit(my_exit_module);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device with sysfs attributes");

5、为什么把/sys目录叫sysfs?

因为sysfs挂载于/sys目录下。sysfs是Linux的虚文件系统本身并不存在。

root@ubuntu:/sys/class/misc$ mount  | grep sysfs
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)

为什么需要mount?Linux中一切皆文件,允许将文件系统与自身的目录树挂接起来。通过挂载,将不同文件系统隔离开来,各文件系统均有读、写、执行权限。支持通过mount挂载USB(U盘)、网络设备(NFS、MSB)、虚拟文件设备(/sys、/proc)。

mount [-t fstype] [-o options] device dir

  • -t fstype:指定文件系统类型,如 ext4ntfsnfs 等。如果不指定,mount 会尝试自动检测文件系统类型。
  • -o options:指定挂载选项,多个选项之间用逗号分隔。常见的选项包括:
    • ro:以只读模式挂载。
    • rw:以读写模式挂载(默认)。
    • sync:同步模式,所有写操作都会立即写入磁盘。
    • async:异步模式,写操作可能会延迟写入磁盘(默认)。
    • noexec:禁止在文件系统上执行程序。
    • exec:允许在文件系统上执行程序(默认)。
    • nosuid:忽略文件系统上的 SUID 和 SGID 位。
    • suid:允许文件系统上的 SUID 和 SGID 位(默认)。
    • nodev:不允许文件系统上包含设备文件。
    • dev:允许文件系统上包含设备文件(默认)。
    • remount:重新挂载已经挂载的文件系统,可以修改挂载选项。

示例:mount /dev/sdb1 /mnt/data

6、

字符设备使用数组,杂散类设备使用链表。

位图:使用位图来表示设备的设备号或状态标志,相对于数组,可以占用更少的内存和按位访问有更快的执行效率。

静态位图的声明:

static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);

声明后后续可以访问位图:

        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); // 查找位图中第一个不为0的位。

7、


http://www.kler.cn/a/537580.html

相关文章:

  • css:怎么设置图片不变形
  • 什么是Spring
  • c++ 浮点数比较判断
  • Linux TCP 编程详解与实例
  • 基于keepalived+GTID半同步主从复制的高可用MySQL集群
  • 掌握API和控制点(从Java到JNI接口)_37 JNI开发与NDK 05
  • 部署open webui 调用ollama启动的deepseek
  • android设置添加设备QR码信息
  • 【Prometheus】如何通过prometheus监控springboot程序运行状态,并实时告警通知
  • Git仓库托管基本使用03——远程仓库
  • 使用Vue开发可复用的Web Components:跨框架组件封装指南
  • 【学术投稿-第五届消费电子与计算机工程国际学术会议】HTML核心元素详解:超链接、列表、表格与实用技巧
  • 【10.7】队列-解预算内的最多机器人数目
  • 一键操作,完美解决办公问题!
  • layui组件库的年份选择器怎么设置区间超过区间不可点击
  • 基于Docker搭建ES集群,并设置冷热数据节点
  • 【Flink实战】Flink -C实现类路径配置与实现UDF Jar
  • linux上scp能不能取代rsync
  • 学习笔记十九:K8S生成pod过程
  • 国自然面上项目|多模态MRI影像组学模型对乳腺癌新辅助治疗疗效的早期预判研究|基金申请·25-02-08
  • Windows逆向工程入门之汇编开发框架解析
  • Axios 拦截器实现的原理
  • C++ 23 的栈踪迹库(stacktrace)
  • 深度洞察与精确匹配:基于HAI部署DeepSeekR1的公考岗位推荐与智能分析
  • XY2-100的Verilog实现
  • 阿里云宝塔在线安装步骤