Linux驱动|cdev_init、cdev_alloc区别

332次阅读  |  发布于3年以前

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

一、字符设备架构

下面我们以两个设备:LED、MPU6050为例来讲解字符设备的架构

由上图所示:

1、硬件

外设有MPU6050、LED两个设备,他们通过外设电路连接到SOC的对应的引脚上。程序要操作外设,就要通过设置soc中对应的SFR来与外设交互。

2、驱动层

unsigned int major;
struct cdev *cdev; 

major : 是主设备号 cdev : 指向该字符设备号对应的cdev结构体

3、应用层、VFS层

mknod  /dev/led c 250 0
 mknod 创建设备文件,可以使字符设备,也可以是块设备
 /dev/led 设备文件名
 c  字符设备
 250  主设备号
 0    次设备号

字符设备文件属性中最重要的属性就是字符设备号,该设备号和chedevs的下标有一定对应关系

二、字符设备创建的流程

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

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

  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)

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8