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

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

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

手把手教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. Mybatis、Mybatis Generator、Mybatis-Plus、Mybatis Plus Generator

    1.介绍 Mybatis Mybatis 是操作数据库的框架:提供一种Mapper类,支持用Java代码对数据库进行增删改查. 缺点:需要先在xml中写好SQL语句: Mybatis Generato ...

  2. RIP总结

    RIP     两种更新方式:定期更新和触发更新     管理距离为120,更新使用UDP520,更新周期30s,使用跳数作为度量值,最大15     RIP有三个版本RIPv1,RIPv2,RIPn ...

  3. ecnuoj 5042 龟速飞行棋

    5042. 龟速飞行棋 题目链接:5042. 龟速飞行棋 赛中没过,赛后补题时由于题解有些抽象,自己写个题解. 可以发现每次转移的结果只跟后面两个点的胜负状态有关. 不妨设 \(f_{u,a,b}\) ...

  4. Nuxt3 的生命周期和钩子函数(二)

    title: Nuxt3 的生命周期和钩子函数(二) date: 2024/6/26 updated: 2024/6/26 author: cmdragon excerpt: 摘要:本文深入介绍了Nu ...

  5. 松灵机器人scout mini小车 自主导航(2)——仿真指南

    松灵机器人Scout mini小车仿真指南 之前介绍了如何通过CAN TO USB串口实现用键盘控制小车移动.但是一直用小车测试缺乏安全性.而松灵官方贴心的为我们准备了gazebo仿真环境,提供了完整 ...

  6. 动手学深度学习——CNN应用demo

    CNN应用demo CNN实现简单的手写数字识别 import torch import torch.nn.functional as F from torchvision import datase ...

  7. oeasy教您玩转python - 008 - # ascii码表

    ​ ASCII 码表 回忆上次内容 通过 help()可以从 python 命令行模式进入到帮助模式 通过 q 退出 ord(c)和 chr(i) 这是俩函数 这俩是一对,相反相成的 ord 通过字符 ...

  8. linux的一些常用端口

    hdfs:9870 yarn:8088 sparkMaster的端口是:8080 worker的端口是:8081 历史服务器的默认端口是: 18080

  9. c# 多线程环境下控制对共享资源访问的办法

    Monitor: 定义:Monitor 是 C# 中最基本的同步机制,通过 Enter 和 Exit 方法来控制对共享资源的访问.它提供了排他锁的功能,确保在任何时刻只有一个线程可以访问共享资源. 优 ...

  10. js实现动态表格的添加

    <!DOCTYPE html> <html lang="en"> <head> <title>Table_Simple CSS fo ...