主设备号--驱动模块与设备节点联系的纽带

一、如何对设备操作
linux中对设备进行操作是通过文件的方式进行的,包括open、read、write。
对于设备文件,一般称其为设备节点,
节点有一个属性是设备号(主设备号、次设备号),其中主设备号将设备文件与驱动模块对应起来
当我们open一个设备节点时,告诉了kernel要操作的是是主设备号为xx的节点,然后kernel会通过过xx来寻找合适的内存模块,进而调用内存模块中定义的open函数
由于操作节点之前kernel就需要有主设备号的信息,因此主设备号的申请、具有该主设备号的字符设备的添加都需要在驱动模块的初始化函数中执行
二、主设备号的申请
建议采用动态申请的主设备号的方式,linux中有很多设备,每一个设备对应着一个主设备号,动态申请是由内核分配一个没用的主
设备号,
动态申请函数为alloc_chrdev_region,相对应的释放函数为unregister_chrdev_region。
申请完后,可以从/proc/devices中读到分配的主设备号,后面建立设备节点时还需要用到
三、向kernle添加字符设备
上一步向内核申请了主设备号,就可以向kernel中添加字符设备了
kennel中一个字符设备对应了一个结构体cdev,这个结构体中定义了对字符设备的操作方式file_operations(包括open、read、write),这些操作方式也需要在驱动模块中事先定义好。
字符设备结构体cdev的添加步骤:
cdev初始化:cdev_init,该函数将file_operations与cdev对应起来
向kernel添加:cdev_add,该函数将主设备号与cdev结构体对应起来
当对open设备节点时,首先通过节点找到主设备号,然后再kernel中搜索与主设备号相对应的字符设备cdev,然后动过cdev中file_operations结构体定义的open方法(这个open是需要自己实现的)
四、3个重要的结构体
一个是file_operations,这里面主要包含了驱动的主要实现方法
一个是inode,这个是节点的信息,包含了主设备号和cdev结构体
一个是file,当节点首次被打开时,就会在内核中创建一个file结构体,file结构其充当了file_operations中方法的纽带,要不然read和wirte方法怎么知道操作的是那个设备的数据。
file中的自定义内容(驱动需要的数据)一般是在open中定义,然后read和write就可以操作自定义的数据了。
下面是一个简单的实例,可以看到驱动是怎样把自定义的open方法和主设备号对应起来的
#include /*它定义了模块的 api、类型和宏(module_license、module_author等等),所有的内核模块都必须包含这个头文件。*/
#include
#include //设备号相关函数
#include //内存分配相关函数
#include
#include //设备号相关函数
#include //字符设备头文件
#include
struct char_dev
{
int size;
char *data;
struct cdev cdev;//内核中的字符设备
};
int major = 0;
int minor = 0;
struct char_dev char_devices;
int char_open(struct inode *inode, struct file *filep)
{
int major = 0;
major = major(inode-》i_rdev);
printk(“open my_char_dev major: %d\n”, major);
return 0;
}
struct file_operations char_fops = {
.owner = this_module,
.open = char_open,
};
static void char_exit(void) //如果init函数中调用了该函数,则不应有 __exit
{
dev_t dev;
printk(“char device driver exit \n”);
//释放设备号
dev = mkdev(major, minor);
unregister_chrdev_region(dev, 1);
printk(“release major %d\n”, major);
//释放内存
if(char_devices.data){
kfree(char_devices.data);
}
//从内核中删除字符设备
cdev_del(&(char_devices.cdev));
}
static int __init char_init(void)//__init一个标记,表明是初始化函数
{
//初始化的代码
dev_t dev;
int result;
printk(“char device driver init \n”);
//动态向内核申请设备号
result = alloc_chrdev_region(&dev, 0, 1, “my_char_dev”);
major = major(dev);
minor = minor(dev);
printk(“alloc major %d\n”, major);
if (result 《 0) {
printk(kern_warning “my_char_dev: can‘t get major %d\n”, major);
return result;
}
//为设备分配一块内存
char_devices.size = 100;
char_devices.data = (char*)kmalloc(char_devices.size, gfp_kernel);
if (!char_devices.data) {
result = -enomem;
goto fail; //不能直接退出函数,需要释放设备号
}
//向内核中添加字符设备cdev
cdev_init(&(char_devices.cdev), &char_fops);
char_devices.cdev.owner = this_module;
char_devices.cdev.ops = &char_fops;
result = cdev_add(&(char_devices.cdev), dev, 1);
if((result 《 0)) {
printk(kern_warning “error %d adding my_char_dev\n”, result);
goto fail;
}
return 0; //成功
fail:
char_exit();
return result;
}
module_license(“dual bsd/gpl”);
//当模块被加载时,执行moudle_init函数,该函数会调用初始化函数
module_init(char_init);
//模块卸载时,调用,释放资源
module_exit(char_exit);
kdir=/usr/src/linux-headers-$(shell uname -r)
pwd=$(shell pwd)
obj-m = chardevice.o
all:
$(make) -c $(kdir) m=$(pwd)
注:驱动insmod后,通过/proc/devices查看主设备号,然后通过mknod在/dev下创建设备节点,注意保持主设备好的一致,当前的节点只支持open方法,可以在demsg中进行验证。

车联网是构建汽车“新四化”基石
一文搞明白各类总线
介绍一些实用的Linux命令
薄膜表面缺陷检测设备的相关功能介绍
如何理解和使用Update bit呢?
主设备号--驱动模块与设备节点联系的纽带
TCP协议网络安全攻击
典型的模拟心电图拓扑结构和ECG信号链分析
中亚五国的科技发展状况分析
插座安装的四大误区盘点
车灯高效率同步降压恒流驱动方案
泰凌微电子应邀参加“OFweek 2022第七届物联网产业大会”
市场需求决定创新技术的选择
自动插拔试验机的校准方法、功能用途及优点
大功率绕线电感的基本作用以及应用分析
无人机是如何为松树看病的
长鑫存储“半导体设备生产效率的评估方法及装置”专利获授权
全球首款18GB手机红魔6 Pro氘锋透明版登场
拓普联科荣获韶音科技“2023年度供应商质量卓越奖”
飞兆全新6通道集成视频滤波器支持1080p高清(HD)标准