Linux字符设备驱动——Linux2.6标准字符设备驱动模型

你的名字 2021-12-12 07:25 537阅读 0赞

文章目录

  • Linux2.6标准字符设备特征
  • Linux2.6标准字符设备定义
    • 字符设备驱动结构体
    • 头文件
    • 设备号
    • 分配核心结构函数
    • 静态设备号注册函数
    • 动态设备号注册函数
    • 设备号注销函数(释放设备号函数)
    • 核心结构初始化函数
      • 注册函数
      • 注销函数
  • 编写Linux2.6字符设备模型步骤
    • 第一步:变量定义
    • 第二步到第八步在入口函数完成
      • 第二步:分配一个核心结构
      • 第三步:申请设备号
      • 第四步:初始化核心结构
      • 第五步:注册核心结构
      • 第六步和第七步增加自动创建设备文件功能
      • 第六步:创建一个设备类
      • 第七步:创建一个设备,报告设备信息
      • 第八步:出错处理
    • 第九步:注销核心结构,释放设备号
  • Linux2.6标准字符设备驱动模型源码
    • 驱动程序zx_linux26_led.c源代码
    • 应用程序app.c源代码
    • 示例代码测试

Linux2.6标准字符设备特征

  1. 使用一个核心结构体把需要的信息进行封装struct cdev;
  2. 安装驱动后,不会在/dev/目录下创建设备节点,需要使用mknod创建;
  3. 主设备号可以被注册多次,每次注册只会占用指定数量的次设备;
  4. 设备号使用要先使用专门的函数申请,分为静态和动态.

动态:不知道哪个号可以用,由内核分配
静态:用用户指定的号进行申请

Linux2.6标准字符设备定义

字符设备驱动结构体

  1. struct cdev
  2. {
  3. struct kobject kobj;
  4. struct module *owner;
  5. const struct file_operations *ops; //设备文件操作方法
  6. struct list_head list;
  7. dev_t dev; //32位设备号,包含主次
  8. unsigned int count; //需要占用的连续次设备号的个数
  9. };

ops:文件操作集合指针
dev:起始设备号,(包含主次设备号,dev_t实际上是一个u32类型)

头文件

#include

设备号

使用32位数据类型表示,其中12位表示主设备号,低20位表示次设备号。
主:0~4096-1
次:0~1M-1
专门定义了dev_t类型来存放设备号类型,实际就是unsigned long类型

由于主次设备号都在一个数据中,内核提供了一些宏来合成设备号和分离设备号

MKDEV(ma,mi) ma主设备号,mi此设备号,这个宏是合成设备号
MAJOR(devnr) 从设备号devnr中得到主设备号
MANOR(devor) 从设备号devnr中得到次设备号

分配核心结构函数

struct cdev *cdev_alloc(void)
功能:在堆区空间中分配一个核心结构cdev,注意,不使用时候要使用 kfree 函数释放。

返回值:返回分配到struct cdev结构空间首地址。

注意:说明:用完记得释放,否则会造成内存泄漏。
如果在静态存储区中定义全局变量,可以不适用这个函数,需要输入以下代码:

  1. struct cdev dev;
  2. dev.count = 1;

如果想在堆空间中分配 cdev结构空间,就使用这个函数。出于内存利用率考虑建议使用这个函数进行分配核心结构空间。
输入以下代码实现:

  1. struct cdev *pdev;
  2. pdev = cdev_alloc();
  3. pdev->count = 1;

静态设备号注册函数

  1. int register_chrdev_region(
  2. dev_t from //起始设备号(主、次)
  3. unsigned count, //连续的次设备号数量
  4. const char *name ) //设备名,不需要和/dev/的设备文件名相同

功能:注册一个设备号范围
参数:

  1. from 起始设备号(主、次)
  2. count 连续的次设备号数量
  3. name 设备名,不需要和/dev/的设备文件名相同,是/proc/device文件的名字
  4. 返回值: 成功;返回0,失败:返回负数

动态设备号注册函数

  1. int alloc_chrdev_region(dev_t *dev, //存放分配到的第一个设备(包含主次)
  2. unsigned baseminor, //要分配起始次设备号
  3. unsigned count, // 连续的次设备号数量
  4. const char *name) // 设备名,不需要和/dev/的设备文件名相同

功能: 注册一个设备号范围
参数:

  1. dev :存放分配到的第一个设备(包含主次设备号)
  2. baseminor :要分配起始次设备号
  3. count :连续的次设备号数量
  4. name :设备名,不需要和/dev/的设备文件名相同
  5. 返回值 :成功:返回0,失败:返回负数

设备号注销函数(释放设备号函数)

  1. void unregister_chrdev_region( dev_t from, //起始设备号(主、次)
  2. unsigned int count) //连续的次设备号数量

功能: 释放一个范围的设备号
参数

  1. from :起始设备号(主、次) (包含主次设备号)
  2. count:连续的次设备号数量
  3. 返回值:无

核心结构初始化函数

  1. void cdev_init( struct cdev *cdev, //需要初始化的核心结构指针
  2. const struct file_operations *fops) //文件操作集合

功能:初始化核心结构,具体做的是清0 核心结构,初始化核心结构的list,kobj,ops成员
参数:

  1. cdev :需要初始化的核心结构指针
  2. fops :文件操作方法结构指针
  3. 返回值 :无

注意:说明:写这种种驱动模型时候,不需要在定义struct cdev结合变量初始化,因为调用cdev_init函数时候会把它清0,定义时候的初始无效。

源代码:

  1. void cdev_init(struct cdev *cdev, /* 需要初始化的核心结构指针*/
  2. const struct file_operations *fops) /* 文件操作集合*/
  3. {
  4. memset(cdev, 0, sizeof *cdev); /* 清0 核心结构 */
  5. INIT_LIST_HEAD(&cdev->list); /* 初始化核心结构的list,kobj,ops成员*/
  6. kobject_init(&cdev->kobj, &ktype_cdev_default);
  7. cdev->ops = fops; /* 填充文件操作方法成员*/
  8. }

注册函数

  1. int cdev_add(struct cdev *p, //已经初始化的核心结构指针
  2. dev_t dev, //始设备号(包含主次设备号在内)
  3. unsigned count) //连续次设备号数量

功能:注册一个cdev结构
参数:

  1. p:已经初始化的核心结构指针
  2. dev:起始设备号(包含主次设备号在内)
  3. count:连续次设备号数量
  4. 返回值:成功:返回0,失败:返回负数

注销函数

void cdev_del(struct cdev *p)

功能:注销一个cdev结构
参数:

  1. p:前面注册的struct cdev结构指针
  2. 返回值:无

编写Linux2.6字符设备模型步骤

第一步:变量定义

  1. static char * pchrdev_name = DEV_NAME; //设备名
  2. static struct cdev *pcdev = NULL; //分配到struct cdev结构空间首地址
  3. static unsigned int major = 0; //主设备号,想动态设置0,否则设置正数1~255 (自己保证可用)
  4. static unsigned int minor = 0; //次设备号
  5. static dev_t devnr = 0; //设备号
  6. static struct class *linux26_class = NULL; //Linux2.6标准字符设备类
  7. static struct device *this_device = NULL; //在设备类中的设备

第二步到第八步在入口函数完成

第二步:分配一个核心结构

  1. int ret = 0
  2. pcdev = cdev_alloc();
  3. if(pcdev == NULL)
  4. {
  5. printk(KERN_EMERG "cdev_alloc error\n");
  6. ret = -1;
  7. goto cdev_alloc_err; //分配一个核心结构出错
  8. }

第三步:申请设备号

  1. if(major) //如果用户定义了主设备号,使用静态分配的方式
  2. {
  3. devnr = MKDEV(major, minor); //合成设备号
  4. ret = register_chrdev_region( devnr , 2, pchrdev_name);
  5. if ( ret < 0 ) { //返回0表示成功
  6. printk(KERN_EMERG "register_chrdev_region error\n");
  7. goto register_chrdev_region_err; //静态申请设备号出错
  8. }
  9. }
  10. else { //使用动态分配设备号方式
  11. ret = alloc_chrdev_region(&devnr, minor, 2, pchrdev_name);
  12. if ( ret < 0 ){
  13. printk(KERN_EMERG "alloc_chrdev_region error\n");
  14. goto alloc_chrdev_region_err; //动态申请设备号出错
  15. }
  16. major = MAJOR(devnr); //获得主设备号
  17. minor = MINOR(devnr); //获得次设备号
  18. }

第四步:初始化核心结构

  1. cdev_init(pcdev, &chrdev_fops);

第五步:注册核心结构

  1. ret = cdev_add(pcdev, devnr, 2);
  2. if ( ret < 0 ) {
  3. printk(KERN_EMERG "cdev_add error\n");
  4. goto cdev_add_err; //注册核心结构出错
  5. }

第六步和第七步增加自动创建设备文件功能

第六步:创建一个设备类

Linux2.6内核支持一个udev的程序,这个程序是一个运行在用户空间的应用程序,这个程序会检测内存上报的热拔插的事件。
在开发板上输入以下代码:
ls /sys/class
/sys/class/类名/设备名/uevent
通过cat /sys/class/zxdev26/zxlinux26leds/uevent查看内容
包含的头文件:
#include

创建设备类步骤:
1、创建一个类
2、创建设备
3、报告事件

  1. linux26_class = class_create(THIS_MODULE, CLASS_NAME); //取一个不同于设备名的类名
  2. if ( IS_ERR(linux26_class) )
  3. {
  4. ret = PTR_ERR(linux26_class);
  5. goto class_create_err; //创建一个设备类出错
  6. }

第七步:创建一个设备,报告设备信息

  1. this_device = device_create(linux26_class,NULL ,devnr,NULL,"%s", pchrdev_name);
  2. if ( IS_ERR(this_device) ) {
  3. ret = PTR_ERR(this_device);
  4. goto device_create_err; //创建一个设备出错
  5. }

创建成功后会显示注册的信息,即报告的事件

  1. [root@ZX20150811 /home]# ls -l /dev
  2. crw-rw---- 1 root root 10, 130 Aug 12 2015 watchdog
  3. crw-rw---- 1 root root 252, 0 Aug 12 2015 watchdog0
  4. crw-rw---- 1 root root 1, 5 Aug 12 2015 zero
  5. crw-rw---- 1 root root 150, 23 Aug 17 2015 zxlinux26leds
  6. [root@ZX20150811 /home]# ls /sys/class/ | grep zx
  7. zxdev26
  8. [root@ZX20150811 /home]# cat /sys/class/zxdev26/zxlinux26leds/uevent
  9. MAJOR=150
  10. MINOR=23
  11. DEVNAME=zxlinux26leds
  12. [root@ZX20150811 /home]#

第八步:出错处理

如果其中一个流程没有完成,中间出错了,需要进行出错处理,每写一步,处理一步

  1. device_create_err: //创建一个设备出错
  2. class_destroy(linux26_class);
  3. class_create_err: //创建一个设备类出错
  4. cdev_del(pcdev);
  5. cdev_add_err: //注册核心结构出错
  6. unregister_chrdev_region(devnr, 2);
  7. alloc_chrdev_region_err: //动态申请设备号出错
  8. register_chrdev_region_err: //静态申请设备号出错
  9. kfree(pcdev);
  10. cdev_alloc_err: //分配一个核心结构出错
  11. return ret ;

第九步:注销核心结构,释放设备号

(注册时顺序步骤执行,而释放是逆序执行)

  1. device_destroy(linux26_class, devnr);
  2. class_destroy(linux26_class);
  3. cdev_del(pcdev);
  4. unregister_chrdev_region(devnr, 2);
  5. kfree(pcdev);

Linux2.6标准字符设备驱动模型源码

驱动程序zx_linux26_led.c源代码

  1. #include <linux/module.h> //模块包含的头文件
  2. #include <linux/init.h>
  3. #include <linux/fs.h>
  4. #include <linux/cdev.h>
  5. #include <linux/slab.h>
  6. #include <linux/device.h>
  7. #include <asm/uaccess.h> //copy_from_user ,copy_to_user
  8. #include<linux/gpio.h> // GPIO
  9. #define CLASS_NAME "zxdev26"
  10. #define DEV_NAME "zx_linux26_led"
  11. #define LED_NUM (4)
  12. static unsigned int major = 0; //主设备号,想动态设置0,否则设置正数1~255 (自己保证可用)
  13. static unsigned int minor = 0; //次设备号
  14. static dev_t devnr = 0; //设备号
  15. static char * pchrdev_name = DEV_NAME;
  16. static struct cdev *pcdev = NULL;
  17. static struct class *linux26_class = NULL;
  18. static struct device *this_device = NULL;
  19. static unsigned int LED_pin[LED_NUM] = {
  20. EXYNOS4X12_GPM4(0),
  21. EXYNOS4X12_GPM4(1),
  22. EXYNOS4X12_GPM4(2),
  23. EXYNOS4X12_GPM4(3),
  24. };
  25. static ssize_t chrdev_read (struct file *pfile, char __user *buff, size_t size, loff_t *off)
  26. {
  27. printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  28. return 0;
  29. }
  30. static ssize_t chrdev_write(struct file *filp,const char __user *user_buf,size_t count,loff_t *off)
  31. {
  32. int ret = 0;
  33. char buf[LED_NUM] = {0};
  34. int i = 0;
  35. printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  36. if(count == 0){
  37. return 0;
  38. }
  39. if(count > LED_NUM){ //板子只有4盏灯。
  40. count = LED_NUM;
  41. }
  42. ret = copy_from_user(buf, user_buf, count); //用户空间传数据到内核空间
  43. if(ret){
  44. return ret;
  45. }
  46. for(i = 0; i < count; i++)
  47. {
  48. if(buf[i] == 1){
  49. gpio_set_value(LED_pin[i],0);/* 亮 */
  50. }else if(buf[i] == 0){
  51. gpio_set_value(LED_pin[i],1);/* 灭 */
  52. }
  53. }
  54. return count;
  55. }
  56. static int chrdev_open(struct inode *pinode, struct file *pfile)
  57. {
  58. printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  59. return 0;
  60. }
  61. static int chrdev_release(struct inode *pinode, struct file *pfile)
  62. {
  63. printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  64. return 0;
  65. }
  66. static const struct file_operations chrdev_fops ={
  67. .read = chrdev_read,
  68. .write = chrdev_write,
  69. .release = chrdev_release,
  70. .open = chrdev_open,
  71. };
  72. static int __init zx_linux26_led_init(void)
  73. {
  74. int ret = 0;
  75. int i = 0;
  76. for(i = 0;i < LED_NUM; i++)
  77. {
  78. ret = gpio_request(LED_pin[i],"leds");
  79. if(ret < 0){
  80. printk(KERN_EMERG "gpio_request is error\n");
  81. goto gpio_request_err;
  82. }
  83. gpio_direction_output(LED_pin[i],1); //四盏灯设置为输出功能,并且输出高电平
  84. }
  85. pcdev = cdev_alloc(); //第1步:分配核心结构
  86. if(pcdev == NULL) {
  87. printk(KERN_EMERG "cdev_alloc error\n");
  88. ret = -1;
  89. goto cdev_alloc_err;
  90. }
  91. if(major) { //第2步:申请设备号
  92. devnr = MKDEV(major, minor);
  93. ret = register_chrdev_region( devnr , 2, pchrdev_name); //使用静态分配设备号方式
  94. if ( ret < 0 ) {
  95. printk(KERN_EMERG "register_chrdev_region error\n");
  96. goto register_chrdev_region_err;
  97. }
  98. }else {
  99. ret = alloc_chrdev_region(&devnr, minor, 2, pchrdev_name); //使用动态分配设备号方式
  100. if ( ret < 0 ) {
  101. printk(KERN_EMERG "alloc_chrdev_region error\n");
  102. goto alloc_chrdev_region_err;
  103. }
  104. major = MAJOR(devnr);
  105. minor = MINOR(devnr);
  106. }
  107. printk(KERN_EMERG "major:%d,minor:%d\n", major, minor);
  108. cdev_init(pcdev, &chrdev_fops); //第3步:初始化cdev结构
  109. ret = cdev_add(pcdev, devnr, 2); //第4步:注册核对结构
  110. if ( ret < 0 ){
  111. printk(KERN_EMERG "cdev_add error\n");
  112. goto cdev_add_err;
  113. }
  114. /* 增加自动创建设备文件功能 */
  115. //第5步:创建一个设备类
  116. // linux26_class = class_create(THIS_MODULE,pchrdev_name);//可以和设备名相同
  117. linux26_class = class_create(THIS_MODULE, CLASS_NAME); //取一个不同于设备名的类名
  118. if ( IS_ERR(linux26_class) ) {
  119. ret = PTR_ERR(linux26_class);
  120. goto class_create_err;
  121. }
  122. this_device = device_create(linux26_class,NULL,devnr,NULL,"%s", pchrdev_name); //第6步:创建一个设备,报告设备信息
  123. if ( IS_ERR(this_device) ) {
  124. ret = PTR_ERR(this_device);
  125. goto device_create_err;
  126. }
  127. printk(KERN_EMERG "%s is OK!!!\n",__FUNCTION__);
  128. return 0;
  129. device_create_err: //出错处理
  130. class_destroy(linux26_class);
  131. class_create_err:
  132. cdev_del(pcdev);
  133. cdev_add_err:
  134. unregister_chrdev_region(devnr, 2);
  135. alloc_chrdev_region_err:
  136. register_chrdev_region_err:
  137. kfree(pcdev);
  138. cdev_alloc_err:
  139. return ret ;
  140. gpio_request_err:
  141. for(--i;i >=0 ; i--)
  142. {
  143. gpio_free(LED_pin[i]);
  144. }
  145. return ret ;
  146. }
  147. static void __exit zx_linux26_led_exit(void)
  148. {
  149. int i = 0;
  150. for(i=0;i<LED_NUM;i++)
  151. {
  152. gpio_set_value(LED_pin[i],1); //灭灯
  153. gpio_free(LED_pin[i]); //释放GPIO
  154. }
  155. device_destroy(linux26_class, devnr);
  156. class_destroy(linux26_class);
  157. cdev_del(pcdev);
  158. unregister_chrdev_region(devnr, 2);
  159. printk(KERN_EMERG "Goodbye,zx_linux26_led\n");
  160. }
  161. module_init(zx_linux26_led_init);
  162. module_exit(zx_linux26_led_exit);
  163. MODULE_LICENSE("GPL");

应用程序app.c源代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>
  8. #define DEV_NAME "/dev/zx_linux26_led"
  9. #define LED_NUM (4)
  10. int main(void)
  11. {
  12. char buf[] = {1, 0, 0, 0}; //1,亮,0表示灭,
  13. int i = 0;
  14. int fd;
  15. //打开设备文件 O_RDWR, O_RDONLY, O_WRONLY,
  16. fd = open(DEV_NAME, O_RDWR);
  17. if(fd < 0){
  18. printf("open :%s failt!\r\n", DEV_NAME);
  19. }
  20. while(1)
  21. {
  22. write(fd, buf, LED_NUM);
  23. memset(buf, 0, LED_NUM);
  24. buf[i++ % LED_NUM] = 1;
  25. sleep(1);
  26. }
  27. return 0;
  28. }

示例代码测试

  1. [root@ZX20150811 /home]# ls
  2. app zx_linux26_led.ko
  3. [root@ZX20150811 /home]# insmod zx_linux26_led.ko
  4. [ 6594.735000] major:249,minor:0
  5. [ 6594.745000] zx_linux26_led_init is OK!!!
  6. [root@ZX20150811 /home]# ./app
  7. [ 6597.395000] line:68,chrdev_open is call
  8. [ 6597.395000] line:42,chrdev_write is call
  9. [ 6598.395000] line:42,chrdev_write is call
  10. [ 6599.395000] line:42,chrdev_write is call
  11. [ 6600.400000] line:42,chrdev_write is call
  12. ^C[ 6601.390000] line:75,chrdev_release is call
  13. [root@ZX20150811 /home]#

发表评论

表情:
评论列表 (有 0 条评论,537人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Linux字符设备驱动模型(一)

    从事Linux开发也有几年时间了,期间也写了一些比较简单的驱动,但一直没有做系统的整理,今天终于想静下心来做一些整理,首先就从最基本的字符设备驱动开始。先贴出一个简单的字符设备

    相关 Linux字符设备驱动基础(二)

    Linux字符设备驱动基础(二) 5 设备号相关操作 设备号由主设备号和次设备号组成。主设备号用来标识与设备文件相连的驱动程序,用以反映设备类型;次设备号是用于驱动