这两个函数是字符设备初始化相关的内核函数。

要想了解这两个函数,必须要知道字符设备的架构,以及字符设备创建的流程。

关于字符设备可以参考下面这篇文章

手把手教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的下标有一定对应关系

  • 通过mknod创建的文件,VFS层会分配一个结构体变量来维护该文件,类型为struct inode

  • 每新建1个文件内核都会创建不同的结构体变量与之对应

  • 应用程序要操作某个字符设备,那么必须先通过系统调用open()来打开该字符设备

  • 该函数会返回一个唯一的整型文件描述符,同时内核中会分配结构体变量,类型为struct file,并与文件描述符一一对应,该结构体维护在struct task_struct中

  • 每次打开某个文件,都会分配不同的文件描述符,所以需要用不同的变量来保存文件描述符

二、字符设备创建的流程

了解了架构之后,那么我们来看一下内核中完整的创建字符设备的流程及对应的函数调用关系:

如下图所示,字符设备的创建主要包括以下三个步骤:

  1. 申请设备号
  2. 初始化cdev
  3. 注册cdev

    调用的函数见右侧

下面是一个最简单的额字符设备创建的实例

/*
*一口Linux
*2021.6.21
*version: 1.0.0
*/ #include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h> 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);

该实例代码主要功能:

  1. 申请了字符设备号237
  2. 初始化cdev,并注册了cdev

应用程序如果要想使用,还必须创建字符设备节点

mknod /dev/test c 237 0

这样应用程序就可以通过设备节点/dev/test 调用到对应的内核操作函数.open = hello_open,

/*
*一口Linux
*2021.6.21
*version: 1.0.0
*/ #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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驱动|cdev_init、cdev_alloc区别的更多相关文章

  1. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  2. linux驱动面试题整理

    1.字符型驱动设备你是怎么创建设备文件的,就是/dev/下面的设备文件,供上层应用程序打开使用的文件? 答:mknod命令结合设备的主设备号和次设备号,可创建一个设备文件. 评:这只是其中一种方式,也 ...

  3. Linux驱动面试题

    1. Linux设备中字符设备与块设备有什么主要的区别?请分别列举一些实际的设备说出它们是属于哪一类设备. 字符设备:字符设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种 ...

  4. ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程

    ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程 原文地址:http://www.cnblogs.com/NickQ/p/9026545.html 一.开发板与ds18b20的入门 ...

  5. 超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”

    版权声明:本文为博主原创文章,未经博主同意不得转载.转载联系 QQ 30952589,加好友请注明来意. https://blog.csdn.net/sleks/article/details/251 ...

  6. Linux 驱动框架---cdev字符设备驱动和misc杂项设备驱动

    字符设备 Linux中设备常见分类是字符设备,块设备.网络设备,其中字符设备也是Linux驱动中最常用的设备类型.因此开发Linux设备驱动肯定是要先学习一下字符设备的抽象的.在内核中使用struct ...

  7. linux驱动初探之字符驱动

    关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...

  8. linux 驱动学习笔记01--Linux 内核的编译

    由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...

  9. 嵌入式linux驱动开发之给linux系统添加温度传感器模块

    忙了几天,终于可以让ds18b20在自己的开发板的linux系统上跑了!虽然ds18b20不是什么新鲜玩意,但是想想知己可以给linux系统添加模块了还是有点小鸡冻呢! 虽然说现在硬件的资源非常丰富而 ...

  10. Linux驱动设计—— 中断与时钟

    中断和时钟技术可以提升驱动程序的效率 中断 中断在Linux中的实现 通常情况下,一个驱动程序只需要申请中断,并添加中断处理函数就可以了,中断的到达和中断函数的调用都是内核实现框架完成的.所以程序员只 ...

随机推荐

  1. 《从零开始学习Python爬虫:顶点小说全网爬取实战》

    顶点小说 装xpath helper GitHub - mic1on/xpath-helper-plus: 这是一个xpath开发者的工具,可以帮助开发者快速的定位网页元素. Question:加载完 ...

  2. 3.3 Y86-64的顺序实现

    将处理组织成阶段 为了实现流水线处理机制,要将指令组织成某个特殊的阶段序列,所有的指令遵循统一的序列,不同阶段放在不同硬件上进行处理.下面是对各阶段的简述. 取指(fetch):取指阶段从内存读取指令 ...

  3. [oeasy]python0025_ 顺序执行过程_流水_流程_执行次序

    顺序执行过程_流水_流程_执行次序 回忆上次内容 上次 熟悉了 vim编辑器 操作 作用 w 向前移动光标一个word b 向后移动光标一个word :r oeasy.py 读取文件到当前文件缓存 g ...

  4. [oeasy]python0024_unix时间戳_epoch_localtime_asctime_PosixTime_unix纪年法

    输出时间回忆上次内容 通过搜索 我们学会 import 导入 time 了 完整写法为 asc_time = time.asctime( time.localtime( time.time())) 内 ...

  5. AT_arc041_b 题解

    洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定一个 \(N \times M\) 的矩阵,此矩阵的每一个元素都向上.下.左.右 ...

  6. 那些血淋淋的教训——math

    1. 方程的解要写 x= 2023.12.10 晚上周测填空题第 \(2\) 题,方程的解写成了 \(7\) 而不是 \(x=7\). 2. 分类讨论 选填的最后一题. 3. 去绝对值看清楚符号(某个 ...

  7. exp解析

    1 #pragma once 2 #include <string> 3 #include <functional> 4 #include <type_traits> ...

  8. 浅谈 golang 代码规范, 性能优化和需要注意的坑

    浅谈 golang 代码规范, 性能优化和需要注意的坑 编码规范 [强制] 声明slice 申明 slice 最好使用 var t []int 而不是使用 t := make([]int, 0) 因为 ...

  9. [春秋云镜] Initial

    [春秋云镜] Initial **整套网络环境拓扑:** ​​ 一.打进内网 开局一个ip:39.101.184.25,fscan扫一下 ​​ 存在thinkphp5.0.23的漏洞,可以rce,我们 ...

  10. 如何实现对ELK各组件的监控?试试Metricbeat

    一.前言 开发排查系统问题用得最多的手段就是查看系统日志,ELK 是 Elastic 公司开源的实时日志处理与分析解决方案,已经为日志处理方案的主流选择. 而在生产环境中,如何针对 ELK 进行监控, ...