Linux驅動 | cdev_init、cdev_alloc的區(qū)別
這兩個函數(shù)是字符設備初始化相關的內(nèi)核函數(shù)。
要想了解這兩個函數(shù),必須要知道字符設備的架構,以及字符設備創(chuàng)建的流程。
關于字符設備可以參考下面這篇文章 《手把手教Linux驅動3-之字符設備架構詳解,有這篇就夠了》
一、字符設備架構
下面我們以兩個設備:LED、MPU6050為例來講解字符設備的架構
由上圖所示:
1、硬件
外設有MPU6050、LED兩個設備,他們通過外設電路連接到SOC的對應的引腳上。程序要操作外設,就要通過設置soc中對應的SFR來與外設交互。
2、驅動層
- 每一個字符設備都必須首先定義一個結構體變量struct cdev,并注冊到內(nèi)核中
- 所有的該變量在內(nèi)核中會通過鏈表進程管理,其中成員list用于將所有鏈表串接起來
- 用于操作外設的功能函數(shù)全部被封裝在struct file_operations中,包括read、write等
- 每一個字符設備都必須要有一個設備號,保存在成員dev中,
- 主、次設備號只能被分配一次
- 所有的字符設備號,都由數(shù)組chrdevs統(tǒng)一管理
- chrdevs是一個指針數(shù)組,成員類型為**struct char_device_struct ***,下標與字符設備號有一定的對應關系,
- **struct char_device_struct **中有成員:
- unsigned int major;
- struct cdev *cdev;
major : 是主設備號 cdev : 指向該字符設備號對應的cdev結構體
3、應用層、VFS層
- 用戶如果想操作硬件,必須調(diào)用內(nèi)核中的struct file_operations中的操作函數(shù),
- 那么如何才能找到該結構體呢?必須要依賴文件節(jié)點來查找,可以通過以下命令來創(chuàng)建
- mknod /dev/led c 250 0
- mknod 創(chuàng)建設備文件,可以使字符設備,也可以是塊設備
- /dev/led 設備文件名
- c 字符設備
- 250 主設備號
- 0 次設備號
字符設備文件屬性中最重要的屬性就是字符設備號,該設備號和chedevs的下標有一定對應關系
- 通過mknod創(chuàng)建的文件,VFS層會分配一個結構體變量來維護該文件,類型為struct inode
- 每新建1個文件內(nèi)核都會創(chuàng)建不同的結構體變量與之對應
- 應用程序要操作某個字符設備,那么必須先通過系統(tǒng)調(diào)用open()來打開該字符設備
- 該函數(shù)會返回一個唯一的整型文件描述符,同時內(nèi)核中會分配結構體變量,類型為struct file,并與文件描述符一一對應,該結構體維護在struct task_struct中
- 每次打開某個文件,都會分配不同的文件描述符,所以需要用不同的變量來保存文件描述符
二、字符設備創(chuàng)建的流程
了解了架構之后,那么我們來看一下內(nèi)核中完整的創(chuàng)建字符設備的流程及對應的函數(shù)調(diào)用關系:
如下圖所示,字符設備的創(chuàng)建主要包括以下三個步驟:
- 申請設備號
- 初始化cdev
- 注冊cdev 調(diào)用的函數(shù)見右側
下面是一個最簡單的額字符設備創(chuàng)建的實例
- /*
- *一口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);
該實例代碼主要功能:
- 申請了字符設備號237
- 初始化cdev,并注冊了cdev
應用程序如果要想使用,還必須創(chuàng)建字符設備節(jié)點
- mknod /dev/test c 237 0
這樣應用程序就可以通過設備節(jié)點/dev/test 調(diào)用到對應的內(nèi)核操作函數(shù).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 ");
- }
三、函數(shù)功能和定義
搞懂上面字符設備創(chuàng)建步驟之后,我們就可以來真正分析cdev_init、cdev_alloc這兩個函數(shù)了
1. cdev_init()
- 原型
- void cdev_init(struct cdev *cdev, const struct file_operations *fops)
- 功能
- 用于初始化cdev結構體,并填充其成員ops
- 參數(shù)
- cdev:字符設備
- fops :驅動操作函數(shù)集合
- 返回值
- 無
該函數(shù)實現(xiàn)如下:
- /**
- * 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結構體,并添加到內(nèi)核中
- 參數(shù)
- 返回值
- 成功:返回分配的cdev結構體變量指針
- 失?。?nbsp;返回NULL
該函數(shù)實現(xiàn)如下:
- /**
- * 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;
- }
注意,該函數(shù)分配的cdev需要free掉 該函數(shù)沒有初始化cdev->ops成員
四、cdev_alloc()的使用
該函數(shù)主要用于讓用戶省去操作cdev的操作,只需要提供**struct file_operations **變量就可以通過以下函數(shù)注冊字符設備
- static inline int register_chrdev(unsigned int major, const char *name,
- const struct file_operations *fops)
- {
- return __register_chrdev(major, 0, 256, name, fops);
- }
其中函數(shù)__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;
- }
可以看到該函數(shù),復用了cdev_alloc()、cdev_add(),我們只需要提供以下3個參數(shù)即可:
- unsigned int major 主設備號
- const char *name 設備號名字
- const struct file_operations *fops 驅動操作函數(shù)集合
五、結論
cdev_alloc()函數(shù)相當于
- struct cdev cdev;
- cdev_init($cdev,&hello_ops)
本文轉載自微信公眾號「一口Linux」