linux-字符设备总结


前言

@和原子哥一起学习Linux

开发环境:I.MX6Ull开发板

参考书籍:
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.6.pdf》
《linux设备驱动开发详解-基于最新的linux4.0内核》

个人学习笔记,欢迎讨论

一、字符设备的位置

驱动编译完成后会形成模块,加载模块会形成驱动文件,本质上就是对文件的操作。

1、使用命令“cat /proc/devices”可以看到你注册设备的设备号
2、字符设备就是“dev/xxx”,xxx是注册的字符设备名称

在这里插入图片描述

二、驱动的分离

简单来说,就是大家想复用写好的东西

1.一个设备驱动想对应多个不同平台

每个平台对设备的控制器是不一样的,比如I2C控制器,SPI控制器,这些根据不同厂家设计不同而不同,厂家实现了主机端控制器的驱动,也就是可以产生各种波形,那么设备驱动写入设备寄存器的接口难道要按照厂家变化?
此处需要引入中间层,实现对外统一接口api,这样不同的主机就有了相同的神经。厂家可以根据自己的不同方案,去实现自己的控制器驱动,统一注册到api接口,设备驱动只需要调用通用接口,这样就完成了一个设备驱动可以在多个平台上运行的构想。在这里插入图片描述

2.一个设备驱动想对应多个同类设备

有了一个设备驱动后,当然想的是无论多少这样的设备都可以使用这个设备驱动了,但是不同设备设备信息不同,比如地址之类的,此时必须要把设备信息从设备驱动中剥离出去,设备驱动使用标准的方法获取设备信息即可。
此处引入总线,当我们向系统注册一个驱动的时候,总线就会在设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在驱动中查找看有没有与之匹配的设备,有的话也联系起来。 这样就实现了一个驱动多个设备都可以用的设想。

在这里插入图片描述

3.设备驱动完成了他的终极进化

Linux 的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离,总线负责连接设备和驱动
在这里插入图片描述

二、驱动的分层

简单来说,就是大家想只做自己的活,不一定是分几层,就是为了在不同的层处理不同的内容。

>最主要的核心层有三个功能
1、对上提供接口,类似2.1中的对外统一接口API
2、中间层通用逻辑,避免底层重复实现
3、底层驱动只负责硬件相关的访问

这就是面向对象的设计!

三、总线驱动

Linux 的总线(bus)、驱动(driver)和设备(device)模型的总线,比如 I2C、 SPI、 USB 等总线是实体的。但是有些外设是没有总线这个概念的,为了解决此问题, Linux 提出了 platform 这个虚拟总线,相应的就有 platform_driver 和 platform_device。

1.platform 总线

Linux系统内核使用 bus_type结构体表示总线,platform 总线是 bus_type 的一个具体实例:

struct bus_type platform_bus_type = {
	.name = "platform",
	.dev_groups = platform_dev_groups,
	.match = platform_match,
	.uevent = platform_uevent,
	.pm = &platform_dev_pm_ops,
};

platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数,采用3种匹配方法,1)设备树 2)ACPI 3)id_table 匹配

2.platform 驱动

* 字符设备驱动操作集
*/
static struct file_operations xxx_fops = {
	.owner = THIS_MODULE,
	.open = xxx_open,
	.write = xxx_write,
};

 /*
 * platform 驱动的 probe 函数
 * 驱动与设备匹配成功以后此函数就会执行
 */
 static int xxx_probe(struct platform_device *dev)
 {
	 ......
	 cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
	 /* 函数具体内容 */
	 return 0;
 }

 static int xxx_remove(struct platform_device *dev)
 {
	 ......
	 cdev_del(&xxxdev.cdev);/* 删除 cdev */
	 /* 函数具体内容 */
	 return 0;
 }

 /* 匹配列表 */
 static const struct of_device_id xxx_of_match[] = {
	 { .compatible = "xxx-gpio" },
	 { /* Sentinel */ }
 };

 /*
 * platform 平台驱动结构体
 */
 static struct platform_driver xxx_driver = {
	 .driver = {
	 .name = "xxx",
	 .of_match_table = xxx_of_match,
	 },
	 .probe = xxx_probe,
	 .remove = xxx_remove,
 };

 /* 驱动模块加载 */
 static int __init xxxdriver_init(void)
 {
	return platform_driver_register(&xxx_driver);
 }

 /* 驱动模块卸载 */
 static void __exit xxxdriver_exit(void)
 {
	platform_driver_unregister(&xxx_driver);
 }

 module_init(xxxdriver_init);
 module_exit(xxxdriver_exit);

3.platform 设备

1、 platform 设备框架

/* 资源 */
static struct resource xxx_resources[] = {
	[0] = {
		.start = xx,
		.end = xxx,
		.flags = xxxx,
	 },
};

/* platform 设备结构体 */
static struct platform_device xxxdevice = {
	.name = "xxx-",
	.id = -1,
	.num_resources = ARRAY_SIZE(xxx_resources),
	.resource = xxx_resources,
};

 /* 设备模块加载 */
 static int __init xxxdevice_init(void)
 {
	return platform_device_register(&xxxdevice);
 }

 /* 设备模块注销 */
 static void __exit xxx_resourcesdevice_exit(void)
 {
	platform_device_unregister(&xxxdevice);
 }

 module_init(xxxdevice_init);
 module_exit(xxxdevice_exit);

2、 设备树

	gpioled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "atkalpha-gpioled";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led>;
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
		status = "okay";
	};

总结

上述就是字符设备学习的笔记,后面I2C和SPI都是按照设备总线驱动来的,大部分是调用接口,总线驱动基本由厂家完成,设备驱动需要对不同的设备进行适配,设备树都是按照官方给到的原版进行修改适配,目前学完只是会改,不禁感叹前辈们的对此的贡献,才有如此简单的使用方式。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用以下代码对字符设备进行操作: #include <linux/fs.h> #include <linux/cdev.h> #include <linux/module.h> #include <linux/uaccess.h> static int dev_open(struct inode *inode, struct file *file) { printk(KERN_INFO "Device opened\n"); return ; } static int dev_release(struct inode *inode, struct file *file) { printk(KERN_INFO "Device released\n"); return ; } static ssize_t dev_read(struct file *file, char *buf, size_t count, loff_t *pos) { printk(KERN_INFO "Reading from device\n"); return ; } static ssize_t dev_write(struct file *file, const char *buf, size_t count, loff_t *pos) { printk(KERN_INFO "Writing to device\n"); return count; } static struct file_operations fops = { .owner = THIS_MODULE, .open = dev_open, .release = dev_release, .read = dev_read, .write = dev_write, }; static dev_t dev; static struct cdev c_dev; static int __init chardev_init(void) { if (alloc_chrdev_region(&dev, , 1, "chardev") < ) { return -1; } cdev_init(&c_dev, &fops); if (cdev_add(&c_dev, dev, 1) < ) { unregister_chrdev_region(dev, 1); return -1; } printk(KERN_INFO "Chardev module loaded\n"); return ; } static void __exit chardev_exit(void) { cdev_del(&c_dev); unregister_chrdev_region(dev, 1); printk(KERN_INFO "Chardev module unloaded\n"); } module_init(chardev_init); module_exit(chardev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple character device driver"); ### 回答2: 在Linux内核中,对字符设备的操作涉及到以下几个关键的数据结构和函数: 1. file_operations结构体:该结构体定义了字符设备驱动程序所支持的操作函数,例如open、read、write、release等。具体定义如下: ``` struct file_operations { int (*open) (struct inode *, struct file *); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); int (*release) (struct inode *, struct file *); // 其他的操作函数,例如seek、flush等 }; ``` 2. register_chrdev函数:该函数用于向内核注册字符设备驱动,将file_operations结构体中定义的操作函数与字符设备关联起来,使得用户态程序可以通过文件系统API进行对字符设备的操作。注册字符设备的代码如下: ``` int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops); ``` 3. file结构体:该结构体表示一个打开的文件,其中包含了指向对应inode的指针,以及与该文件相关的文件操作函数。用户态程序通过系统调用打开字符设备时,内核会创建一个file结构体实例。 4. inode结构体:该结构体表示一个文件的索引节点,在字符设备中用于保存设备的状态信息。 5. cdev结构体:该结构体表示字符设备设备和file_operations结构体的对应关系。可以使用cdev_init和cdev_add函数将cdev结构体与字符设备驱动程序关联起来。 总结起来,对字符设备的操作主要包括定义和实现file_operations结构体中的操作函数,然后通过register_chrdev函数将驱动程序注册到Linux内核中。注册成功后,用户态程序可以通过系统调用打开设备文件,并调用read、write等API与字符设备进行交互。 ### 回答3: 在Linux内核中,对字符设备的操作主要涉及以下几个步骤: 1. 注册字符设备:在内核中使用register_chrdev函数来注册字符设备。该函数接受参数包括主设备设备名称和字符设备驱动的file_operations结构体指针等。 2. 实现file_operations结构体:使用file_operations结构体中的成员函数来处理字符设备的操作。常见的成员函数包括open、release、read和write等。通过实现这些函数,可以在用户空间与字符设备之间进行数据传输和交互。 3. 分配设备:可以通过alloc_chrdev_region函数或者自动分配来获取字符设备的主设备和次设备。 下面是一个简单示例,演示了如何在Linux内核中操作字符设备: ```c #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #define DEVICE_NAME "mychardev" #define DEV_MAJOR 222 #define DEV_MINOR 0 struct cdev my_cdev; dev_t devno; int my_open(struct inode *inode, struct file *filp) { printk(KERN_ALERT "Device opened\n"); return 0; } int my_release(struct inode *inode, struct file *filp) { printk(KERN_ALERT "Device released\n"); return 0; } ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { printk(KERN_ALERT "Read from device\n"); // 数据读取操作 return 0; } ssize_t my_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { printk(KERN_ALERT "Write to device\n"); // 数据写入操作 return 0; } struct file_operations my_fops = { .open = my_open, .release = my_release, .read = my_read, .write = my_write, }; static int __init my_init(void) { int ret; devno = MKDEV(DEV_MAJOR, DEV_MINOR); ret = register_chrdev_region(devno, 1, DEVICE_NAME); if (ret < 0) { printk(KERN_ALERT "Failed to register device\n"); return ret; } cdev_init(&my_cdev, &my_fops); ret = cdev_add(&my_cdev, devno, 1); if (ret < 0) { printk(KERN_ALERT "Failed to add device\n"); unregister_chrdev_region(devno, 1); return ret; } printk(KERN_ALERT "Device registered\n"); return 0; } static void __exit my_exit(void) { cdev_del(&my_cdev); unregister_chrdev_region(devno, 1); printk(KERN_ALERT "Device unregistered\n"); } module_init(my_init); module_exit(my_exit); MODULE_LICENSE("GPL"); ``` 上述代码中,我们定义了一个名为"mychardev"的字符设备,通过实现open、release、read和write等函数来处理设备的打开、关闭、读取和写入操作。在初始化函数中,我们注册了一个字符设备,指定名为"mychardev"的设备名称,并将操作函数和字符设备绑定在一起。在卸载函数中,我们释放了字符设备。需要注意的是,该示例仅为演示目的,具体实现可能会根据实际需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值