原创|Linux驱动|cdev_init、cdev_alloc区别
这两个函数是字符设备初始化相关的内核函数。
要想了解这两个函数,必须要知道字符设备的架构,以及字符设备创建的流程。
关于字符设备可以参考下面这篇文章
《手把手教Linux驱动3-之字符设备架构详解,有这篇就够了》
一、字符设备架构 下面我们以两个设备:LED、MPU6050为例来讲解字符设备的架构
文章图片
由上图所示:
1、硬件
外设有MPU6050、LED两个设备,他们通过外设电路连接到SOC的对应的引脚上。
程序要操作外设,就要通过设置soc中对应的SFR来与外设交互。
2、驱动层
- 每一个字符设备都必须首先定义一个结构体变量struct cdev,并注册到内核中
- 所有的该变量在内核中会通过链表进程管理,其中成员list用于将所有链表串接起来
- 用于操作外设的功能函数全部被封装在struct file_operations中,包括read、write等
- 每一个字符设备都必须要有一个设备号,保存在成员dev中,
- 主、次设备号只能被分配一次
- 所有的字符设备号,都由数组chrdevs统一管理
- chrdevs是一个指针数组,成员类型为**struct char_device_struct ***,下标与字符设备号有一定的对应关系,
- **struct char_device_struct **中有成员:
unsigned int major;
struct cdev *cdev;
major : 是主设备号
cdev : 指向该字符设备号对应的cdev结构体
3、应用层、VFS层
- 用户如果想操作硬件,必须调用内核中的struct file_operations中的操作函数,
- 那么如何才能找到该结构体呢?
必须要依赖文件节点来查找,可以通过以下命令来创建
mknod/dev/led c 250 0
mknod 创建设备文件,可以使字符设备,也可以是块设备
/dev/led 设备文件名
c字符设备
250主设备号
0次设备号
字符设备文件属性中最重要的属性就是字符设备号,该设备号和chedevs的下标有一定对应关系
- 【原创|Linux驱动|cdev_init、cdev_alloc区别】通过mknod创建的文件,VFS层会分配一个结构体变量来维护该文件,类型为struct inode
- 每新建1个文件内核都会创建不同的结构体变量与之对应
- 应用程序要操作某个字符设备,那么必须先通过系统调用open()来打开该字符设备
- 该函数会返回一个唯一的整型文件描述符,同时内核中会分配结构体变量,类型为struct file,并与文件描述符一一对应,该结构体维护在struct task_struct中
- 每次打开某个文件,都会分配不同的文件描述符,所以需要用不同的变量来保存文件描述符
如下图所示,字符设备的创建主要包括以下三个步骤:
- 申请设备号
- 初始化cdev
- 注册cdev
调用的函数见右侧
文章图片
下面是一个最简单的额字符设备创建的实例
/*
*一口Linux
*2021.6.21
*version: 1.0.0
*/#include
#include
#include
#include
#include static int major = 237;
static int minor = 0;
static dev_t devno;
static struct cdev cdev;
static int hello_open (struct inode *inode, struct file *filep)
{ printk("hello_open()\n");
return 0;
}
static struct file_operations hello_ops =
{ .open = hello_open,
};
static int hello_init(void)
{ int result;
int error;
printk("hello_init \n");
devno = MKDEV(major,minor);
result = register_chrdev_region(devno, 1, "test");
if(result<0)
{printk("register_chrdev_region fail \n");
return result;
}
cdev_init(&cdev,&hello_ops);
error = cdev_add(&cdev,devno,1);
if(error < 0)
{printk("cdev_add fail \n");
unregister_chrdev_region(devno,1);
return error;
}
return 0;
}
static void hello_exit(void)
{ printk("hello_exit \n");
cdev_del(cdev);
unregister_chrdev_region(devno,1);
return;
}
module_init(hello_init);
module_exit(hello_exit);
该实例代码主要功能:
- 申请了字符设备号237
- 初始化cdev,并注册了cdev
mknod /dev/test c 237 0
这样应用程序就可以通过设备节点/dev/test 调用到对应的内核操作函数
.open = hello_open,
/*
*一口Linux
*2021.6.21
*version: 1.0.0
*/#include
#include
#include
#include
main()
{ int fd;
fd = open("/dev/test",O_RDWR);
if(fd<0)
{perror("open fail \n");
return;
}
printf("open ok \n ");
}
三、函数功能和定义 搞懂上面字符设备创建步骤之后,我们就可以来真正分析cdev_init、cdev_alloc这两个函数了
1. cdev_init()
原型
void cdev_init(struct cdev *cdev, const struct file_operations *fops)功能
用于初始化cdev结构体,并填充其成员ops
参数
cdev:字符设备
fops :驱动操作函数集合
返回值
无
该函数实现如下:
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{ memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
2. cdev_alloc
原型
struct cdev *cdev_alloc(void)功能
用于分配cdev结构体,并添加到内核中
参数
返回值
成功:返回分配的cdev结构体变量指针
失败: 返回NULL
该函数实现如下:
/**
* cdev_alloc() - allocate a cdev structure
*
* Allocates and returns a cdev structure, or NULL on failure.
*/
struct cdev *cdev_alloc(void)
{ struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
注意,该函数分配的cdev需要free掉
该函数没有初始化cdev->ops成员
四、cdev_alloc()的使用 该函数主要用于让用户省去操作cdev的操作,只需要提供**struct file_operations **变量就可以通过以下函数注册字符设备
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{ return __register_chrdev(major, 0, 256, name, fops);
}
其中函数__register_chrdev()定义如下:
/**
* __register_chrdev() - create and register a cdev occupying a range of minors
* @major: major device number or 0 for dynamic allocation
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: name of this range of devices
* @fops: file operations associated with this devices
*
* If @major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If @major > 0 this function will attempt to reserve a device with the given
* major number and will return zero on success.
*
* Returns a -ve errno on failure.
*
* The name of this device has nothing to do with the name of the device in
* /dev. It only helps to keep track of the different owners of devices. If
* your module name has only one type of devices it's ok to use e.g. the name
* of the module here.
*/
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{ struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
可以看到该函数,复用了cdev_alloc()、cdev_add(),我们只需要提供以下3个参数即可:
unsigned int major主设备号
const char *name设备号名字
const struct file_operations *fops 驱动操作函数集合
五、结论 cdev_alloc()函数相当于
struct cdev cdev;
cdev_init($cdev,&hello_ops)
推荐阅读
- Linux下面如何查看tomcat已经使用多少线程
- 绘本讲师训练营【24期】14/21阅读原创《小黑鱼》
- 绘本讲师训练营7期9/21阅读原创《蜗牛屋|绘本讲师训练营7期9/21阅读原创《蜗牛屋 》
- Beego打包部署到Linux
- 【原创】君子之心,常怀敬畏
- 《偶得》
- 两感一练
- 绘本讲师训练营【28期】15/21阅读原创《活了100万次的猫》
- 你命令我爱你吧(原创)
- [原创]能见沂山一棵树,胜读十年无用书!