• 驱动开发3 ioctl函数的使用+3个实例(不传递第三个参数、第三个参数为整型、第三个参数为地址)


    • 开发板:stm32mp157aaa(Cortex-A7*2 + Cortex-M4*1)
    • 开发环境:vscode、串口工具、ubuntu 18.04

    vscode编译过程:

    1 引入ioctl函数的意义

          linux操作系统中有意将数据的读写读写功能的选择分别交给不同的函数去完成。就让read/write函数只进行数据的读写即可,让一些其他功能的设置和选择交给ioctl函数来实现。比如串口通信时,需要设置波特率,需要设置数据格式,也需要最终选择数据收发,让这些都由ioctl函数来完成。read()write()只进行串口数据收发即可。

    2 ioctl函数分析

    1. *********系统调用函数的分析**********
    2. #include
    3. int ioctl(int fd, unsigned long request, ...);
    4. 功能:进行io功能的设置
    5. 参数:
    6. fd:文件描述符
    7. request:io控制的功能码
    8. ...:可以加,也可以不加。如果第三个参数传递数值,只能传递整型数据和指针
    9. 返回值:成功返回0,失败返回错误码
    10. *********驱动中操作方法的分析********
    11. long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg)
    12. {
    13. 参数分析:
    14. file:文件指针
    15. cmd:应用程序中的ioctl第二个参数传递过来
    16. arg:应用程序中的ioctl第三个参数传递过来
    17. }

    3 ioctl功能码的构建

    为了让实现不同功能的功能码尽量不一样,我们对功能码进行了编码

    1. 查询内核的说明手册:~/linux-5.10.61/Documentation/userspace-api/ioctl
    2. vi ioctl-decoding.rst  //功能码的编码说明文档
    1. ====== ==================================
    2. bits meaning
    3. ====== ==================================
    4. 31-30 00 - no parameters: uses _IO macro
    5. 10 - read: _IOR
    6. 01 - write: _IOW
    7. 11 - read/write: _IOWR
    8. 29-16 size of arguments
    9. 15-8 ascii character supposedly
    10. unique to each driver
    11. 7-0 function #
    12. ====== ==================================

    1. #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
    2. #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
    3. #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
    4. #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)
    5. #define _IOC(dir,type,nr,size) \
    6. ((unsigned int) \
    7. (((dir) << _IOC_DIRSHIFT) | \
    8. ((type) << _IOC_TYPESHIFT) | \
    9. ((nr) << _IOC_NRSHIFT) | \
    10. ((size) << _IOC_SIZESHIFT)))
    11. ex:构建LED开关的功能码:ioctl函数无第三个参数
    12. 开灯 #define LED_ON _IO('l',1)
    13. 关灯 #define LED_OFF _IO('l',0)
    14. ex:构建LED开关的功能码:ioctl函数有第三个参数
    15. 开灯 #define LED_ON _IOW('l',1,int)
    16. 关灯 #define LED_OFF _IOW('l',0,int)

    4 ioctl实例①——不传递第三个参数

    应用程序

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include "head.h"
    10. int main(int argc, char const *argv[])
    11. {
    12. char buf[128]={0};
    13. int a;
    14. int fd=open("/dev/myled0",O_RDWR);
    15. if(fd<0)
    16. {
    17. printf("打开设备文件失败\n");
    18. exit(-1);
    19. }
    20. while(1)
    21. {
    22. //从终端读取
    23. printf("请输入要实现的功能 ");
    24. printf("0(关灯) 1(开灯)\n");
    25. printf("请输入>");
    26. scanf("%d",&a);
    27. switch(a)
    28. {
    29. case 1:
    30. ioctl(fd,LED_ON);
    31. break;
    32. case 0:
    33. ioctl(fd,LED_OFF);
    34. break;
    35. }
    36. }
    37. close(fd);
    38. return 0;
    39. }

    头文件

    1. #ifndef __HEAD_H__
    2. #define __HEAD_H__
    3. typedef struct
    4. {
    5. unsigned int MODER;
    6. unsigned int OTYPER;
    7. unsigned int OSPEEDR;
    8. unsigned int PUPDR;
    9. unsigned int IDR;
    10. unsigned int ODR;
    11. } gpio_t;
    12. #define PHY_LED1_ADDR 0X50006000
    13. #define PHY_LED2_ADDR 0X50007000
    14. #define PHY_LED3_ADDR 0X50006000
    15. #define PHY_RCC_ADDR 0X50000A28
    16. // 构建开灯关灯的功能码
    17. #define LED_ON _IO('l', 1)
    18. #define LED_OFF _IO('l', 0)
    19. #endif

    驱动程序

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include"head.h"
    7. int major;
    8. char kbuf[128]={0};
    9. gpio_t *vir_led1;
    10. gpio_t *vir_led2;
    11. gpio_t *vir_led3;
    12. unsigned int *vir_rcc;
    13. struct class *cls;
    14. struct device *dev;
    15. int mycdev_open(struct inode *inode, struct file *file)
    16. {
    17. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    18. return 0;
    19. }
    20. long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    21. {
    22. switch(cmd)
    23. {
    24. case LED_ON://开灯
    25. vir_led1->ODR |= (0X1<<10);
    26. vir_led2->ODR |= (0X1<<10);
    27. vir_led3->ODR |= (0X1<<8);
    28. break;
    29. case LED_OFF://关灯
    30. vir_led1->ODR &= (~(0X1<<10));
    31. vir_led2->ODR &= (~(0X1<<10));
    32. vir_led3->ODR &= (~(0X1<<8));
    33. break;
    34. }
    35. return 0;
    36. }
    37. int mycdev_close(struct inode *inode, struct file *file)
    38. {
    39. printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    40. return 0;
    41. }
    42. //定义操作方法结构体变量并赋值
    43. struct file_operations fops={
    44. .open=mycdev_open,
    45. .unlocked_ioctl=mycdev_ioctl,
    46. .release=mycdev_close,
    47. };
    48. int all_led_init(void)
    49. {
    50. //寄存器地址的映射
    51. vir_led1=ioremap(PHY_LED1_ADDR,sizeof(gpio_t));
    52. if(vir_led1==NULL)
    53. {
    54. printk("ioremap filed:%d\n",__LINE__);
    55. return -ENOMEM;
    56. }
    57. vir_led2=ioremap(PHY_LED2_ADDR,sizeof(gpio_t));
    58. if(vir_led2==NULL)
    59. {
    60. printk("ioremap filed:%d\n",__LINE__);
    61. return -ENOMEM;
    62. }
    63. vir_led3=vir_led1;
    64. vir_rcc=ioremap(PHY_RCC_ADDR,4);
    65. if(vir_rcc==NULL)
    66. {
    67. printk("ioremap filed:%d\n",__LINE__);
    68. return -ENOMEM;
    69. }
    70. printk("物理地址映射成功\n");
    71. //寄存器的初始化
    72. //rcc
    73. (*vir_rcc) |= (3<<4);
    74. //led1
    75. vir_led1->MODER &= (~(3<<20));
    76. vir_led1->MODER |= (1<<20);
    77. vir_led1->ODR &= (~(1<<10));
    78. //led2
    79. vir_led2->MODER &= (~(3<<20));
    80. vir_led2->MODER |= (1<<20);
    81. vir_led2->ODR &= (~(1<<10));
    82. //led3
    83. vir_led3->MODER &= (~(3<<16));
    84. vir_led1->MODER |= (1<<16);
    85. vir_led1->ODR &= (~(1<<8));
    86. printk("寄存器初始化成功\n");
    87. return 0;
    88. }
    89. static int __init mycdev_init(void)
    90. {
    91. //字符设备驱动注册
    92. major=register_chrdev(0,"mychrdev",&fops);
    93. if(major<0)
    94. {
    95. printk("字符设备驱动注册失败\n");
    96. return major;
    97. }
    98. printk("字符设备驱动注册成功:major=%d\n",major);
    99. //向上提交目录
    100. cls=class_create(THIS_MODULE,"mychrdev");
    101. if(IS_ERR(cls))
    102. {
    103. printk("向上提交目录失败\n");
    104. return -PTR_ERR(cls);
    105. }
    106. printk("向上提交目录成功\n");
    107. //向上提交设备节点信息
    108. int i;//向上提交三次设备节点信息
    109. for(i=0;i<3;i++)
    110. {
    111. dev=device_create(cls,NULL,MKDEV(major,i),NULL,"myled%d",i);
    112. if(IS_ERR(dev))
    113. {
    114. printk("向上提交设备节点失败\n");
    115. return -PTR_ERR(dev);
    116. }
    117. }
    118. printk("向上提交设备节点成功\n");
    119. //寄存器映射以及初始化
    120. all_led_init();
    121. return 0;
    122. }
    123. static void __exit mycdev_exit(void)
    124. {
    125. //取消地址映射
    126. iounmap(vir_led1);
    127. iounmap(vir_led2);
    128. iounmap(vir_rcc);
    129. //销毁设备节点信息
    130. int i;
    131. for(i=0;i<3;i++)
    132. {
    133. device_destroy(cls,MKDEV(major,i));
    134. }
    135. //销毁目录
    136. class_destroy(cls);
    137. //注销字符设备驱动
    138. unregister_chrdev(major,"mychrdev");
    139. }
    140. module_init(mycdev_init);
    141. module_exit(mycdev_exit);
    142. MODULE_LICENSE("GPL");

    5 ioctl实例②——第三个参数为整型

    应用程序

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include "head.h"
    10. int main(int argc, char const *argv[])
    11. {
    12. char buf[128]={0};
    13. int a,b;
    14. int fd=open("/dev/myled0",O_RDWR);
    15. if(fd<0)
    16. {
    17. printf("打开设备文件失败\n");
    18. exit(-1);
    19. }
    20. while(1)
    21. {
    22. //从终端读取
    23. printf("请输入要实现的功能 ");
    24. printf("0(关灯) 1(开灯)\n");
    25. printf("请输入>");
    26. scanf("%d",&a);
    27. printf("请选择要控制的灯:1(LED1)2(LED2) 3(LED3)\n");
    28. printf("请输入>");
    29. scanf("%d",&b);
    30. switch(a)
    31. {
    32. case 1:
    33. ioctl(fd,LED_ON,b);
    34. break;
    35. case 0:
    36. ioctl(fd,LED_OFF,b);
    37. break;
    38. }
    39. }
    40. close(fd);
    41. return 0;
    42. }

    头文件

    1. #ifndef __HEAD_H__
    2. #define __HEAD_H__
    3. typedef struct
    4. {
    5. unsigned int MODER;
    6. unsigned int OTYPER;
    7. unsigned int OSPEEDR;
    8. unsigned int PUPDR;
    9. unsigned int IDR;
    10. unsigned int ODR;
    11. } gpio_t;
    12. #define PHY_LED1_ADDR 0X50006000
    13. #define PHY_LED2_ADDR 0X50007000
    14. #define PHY_LED3_ADDR 0X50006000
    15. #define PHY_RCC_ADDR 0X50000A28
    16. // 构建开灯关灯的功能码
    17. #define LED_ON _IOW('l', 1,int)
    18. #define LED_OFF _IOW('l', 0,int)
    19. #endif

    驱动程序

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "head.h"
    7. int major;
    8. char kbuf[128] = {0};
    9. gpio_t *vir_led1;
    10. gpio_t *vir_led2;
    11. gpio_t *vir_led3;
    12. unsigned int *vir_rcc;
    13. struct class *cls;
    14. struct device *dev;
    15. int mycdev_open(struct inode *inode, struct file *file)
    16. {
    17. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    18. return 0;
    19. }
    20. long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    21. {
    22. switch (cmd)
    23. {
    24. case LED_ON: // 开灯
    25. switch (arg)
    26. {
    27. case 1: // LED1
    28. vir_led1->ODR |= (0X1 << 10);
    29. break;
    30. case 2:
    31. vir_led2->ODR |= (0X1 << 10);
    32. break;
    33. case 3:
    34. vir_led3->ODR |= (0X1 << 8);
    35. break;
    36. }
    37. break;
    38. case LED_OFF: // 关灯
    39. switch (arg)
    40. {
    41. case 1: // LED1
    42. vir_led1->ODR &= (~(0X1 << 10));
    43. break;
    44. case 2:
    45. vir_led2->ODR &= (~(0X1 << 10));
    46. break;
    47. case 3:
    48. vir_led3->ODR &= (~(0X1 << 8));
    49. break;
    50. }
    51. break;
    52. }
    53. return 0;
    54. }
    55. int mycdev_close(struct inode *inode, struct file *file)
    56. {
    57. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    58. return 0;
    59. }
    60. // 定义操作方法结构体变量并赋值
    61. struct file_operations fops = {
    62. .open = mycdev_open,
    63. .unlocked_ioctl = mycdev_ioctl,
    64. .release = mycdev_close,
    65. };
    66. int all_led_init(void)
    67. {
    68. // 寄存器地址的映射
    69. vir_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
    70. if (vir_led1 == NULL)
    71. {
    72. printk("ioremap filed:%d\n", __LINE__);
    73. return -ENOMEM;
    74. }
    75. vir_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
    76. if (vir_led2 == NULL)
    77. {
    78. printk("ioremap filed:%d\n", __LINE__);
    79. return -ENOMEM;
    80. }
    81. vir_led3 = vir_led1;
    82. vir_rcc = ioremap(PHY_RCC_ADDR, 4);
    83. if (vir_rcc == NULL)
    84. {
    85. printk("ioremap filed:%d\n", __LINE__);
    86. return -ENOMEM;
    87. }
    88. printk("物理地址映射成功\n");
    89. // 寄存器的初始化
    90. // rcc
    91. (*vir_rcc) |= (3 << 4);
    92. // led1
    93. vir_led1->MODER &= (~(3 << 20));
    94. vir_led1->MODER |= (1 << 20);
    95. vir_led1->ODR &= (~(1 << 10));
    96. // led2
    97. vir_led2->MODER &= (~(3 << 20));
    98. vir_led2->MODER |= (1 << 20);
    99. vir_led2->ODR &= (~(1 << 10));
    100. // led3
    101. vir_led3->MODER &= (~(3 << 16));
    102. vir_led1->MODER |= (1 << 16);
    103. vir_led1->ODR &= (~(1 << 8));
    104. printk("寄存器初始化成功\n");
    105. return 0;
    106. }
    107. static int __init mycdev_init(void)
    108. {
    109. // 字符设备驱动注册
    110. major = register_chrdev(0, "mychrdev", &fops);
    111. if (major < 0)
    112. {
    113. printk("字符设备驱动注册失败\n");
    114. return major;
    115. }
    116. printk("字符设备驱动注册成功:major=%d\n", major);
    117. // 向上提交目录
    118. cls = class_create(THIS_MODULE, "mychrdev");
    119. if (IS_ERR(cls))
    120. {
    121. printk("向上提交目录失败\n");
    122. return -PTR_ERR(cls);
    123. }
    124. printk("向上提交目录成功\n");
    125. // 向上提交设备节点信息
    126. int i; // 向上提交三次设备节点信息
    127. for (i = 0; i < 3; i++)
    128. {
    129. dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
    130. if (IS_ERR(dev))
    131. {
    132. printk("向上提交设备节点失败\n");
    133. return -PTR_ERR(dev);
    134. }
    135. }
    136. printk("向上提交设备节点成功\n");
    137. // 寄存器映射以及初始化
    138. all_led_init();
    139. return 0;
    140. }
    141. static void __exit mycdev_exit(void)
    142. {
    143. // 取消地址映射
    144. iounmap(vir_led1);
    145. iounmap(vir_led2);
    146. iounmap(vir_rcc);
    147. // 销毁设备节点信息
    148. int i;
    149. for (i = 0; i < 3; i++)
    150. {
    151. device_destroy(cls, MKDEV(major, i));
    152. }
    153. // 销毁目录
    154. class_destroy(cls);
    155. // 注销字符设备驱动
    156. unregister_chrdev(major, "mychrdev");
    157. }
    158. module_init(mycdev_init);
    159. module_exit(mycdev_exit);
    160. MODULE_LICENSE("GPL");

    6 ioctl实例③——第三个参数为地址

    应用程序

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include "head.h"
    10. int main(int argc, char const *argv[])
    11. {
    12. char buf[128]={0};
    13. int a,b;
    14. int fd=open("/dev/myled0",O_RDWR);
    15. if(fd<0)
    16. {
    17. printf("打开设备文件失败\n");
    18. exit(-1);
    19. }
    20. while(1)
    21. {
    22. //从终端读取
    23. printf("请输入要实现的功能 ");
    24. printf("0(关灯) 1(开灯)\n");
    25. printf("请输入>");
    26. scanf("%d",&a);
    27. printf("请选择要控制的灯:1(LED1)2(LED2) 3(LED3)\n");
    28. printf("请输入>");
    29. scanf("%d",&b);
    30. switch(a)
    31. {
    32. case 1:
    33. ioctl(fd,LED_ON,&b);
    34. break;
    35. case 0:
    36. ioctl(fd,LED_OFF,&b);
    37. break;
    38. }
    39. }
    40. close(fd);
    41. return 0;
    42. }

    头文件

    1. #ifndef __HEAD_H__
    2. #define __HEAD_H__
    3. typedef struct
    4. {
    5. unsigned int MODER;
    6. unsigned int OTYPER;
    7. unsigned int OSPEEDR;
    8. unsigned int PUPDR;
    9. unsigned int IDR;
    10. unsigned int ODR;
    11. } gpio_t;
    12. #define PHY_LED1_ADDR 0X50006000
    13. #define PHY_LED2_ADDR 0X50007000
    14. #define PHY_LED3_ADDR 0X50006000
    15. #define PHY_RCC_ADDR 0X50000A28
    16. // 构建开灯关灯的功能码
    17. #define LED_ON _IOW('l', 1,int)
    18. #define LED_OFF _IOW('l', 0,int)
    19. #endif

    驱动程序

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "head.h"
    7. int major;
    8. char kbuf[128] = {0};
    9. gpio_t *vir_led1;
    10. gpio_t *vir_led2;
    11. gpio_t *vir_led3;
    12. unsigned int *vir_rcc;
    13. struct class *cls;
    14. struct device *dev;
    15. int mycdev_open(struct inode *inode, struct file *file)
    16. {
    17. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    18. return 0;
    19. }
    20. long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    21. {
    22. int which;
    23. //获取应用程序中b的值
    24. int ret= copy_from_user(&which,(void *)arg,4);
    25. if(ret)
    26. {
    27. printk("copy_from_user filed\n");
    28. return-EIO;
    29. }
    30. switch (cmd)
    31. {
    32. case LED_ON: // 开灯
    33. switch (which)
    34. {
    35. case 1: // LED1
    36. vir_led1->ODR |= (0X1 << 10);
    37. break;
    38. case 2:
    39. vir_led2->ODR |= (0X1 << 10);
    40. break;
    41. case 3:
    42. vir_led3->ODR |= (0X1 << 8);
    43. break;
    44. }
    45. break;
    46. case LED_OFF: // 关灯
    47. switch (which)
    48. {
    49. case 1: // LED1
    50. vir_led1->ODR &= (~(0X1 << 10));
    51. break;
    52. case 2:
    53. vir_led2->ODR &= (~(0X1 << 10));
    54. break;
    55. case 3:
    56. vir_led3->ODR &= (~(0X1 << 8));
    57. break;
    58. }
    59. break;
    60. }
    61. return 0;
    62. }
    63. int mycdev_close(struct inode *inode, struct file *file)
    64. {
    65. printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    66. return 0;
    67. }
    68. // 定义操作方法结构体变量并赋值
    69. struct file_operations fops = {
    70. .open = mycdev_open,
    71. .unlocked_ioctl = mycdev_ioctl,
    72. .release = mycdev_close,
    73. };
    74. int all_led_init(void)
    75. {
    76. // 寄存器地址的映射
    77. vir_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
    78. if (vir_led1 == NULL)
    79. {
    80. printk("ioremap filed:%d\n", __LINE__);
    81. return -ENOMEM;
    82. }
    83. vir_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
    84. if (vir_led2 == NULL)
    85. {
    86. printk("ioremap filed:%d\n", __LINE__);
    87. return -ENOMEM;
    88. }
    89. vir_led3 = vir_led1;
    90. vir_rcc = ioremap(PHY_RCC_ADDR, 4);
    91. if (vir_rcc == NULL)
    92. {
    93. printk("ioremap filed:%d\n", __LINE__);
    94. return -ENOMEM;
    95. }
    96. printk("物理地址映射成功\n");
    97. // 寄存器的初始化
    98. // rcc
    99. (*vir_rcc) |= (3 << 4);
    100. // led1
    101. vir_led1->MODER &= (~(3 << 20));
    102. vir_led1->MODER |= (1 << 20);
    103. vir_led1->ODR &= (~(1 << 10));
    104. // led2
    105. vir_led2->MODER &= (~(3 << 20));
    106. vir_led2->MODER |= (1 << 20);
    107. vir_led2->ODR &= (~(1 << 10));
    108. // led3
    109. vir_led3->MODER &= (~(3 << 16));
    110. vir_led1->MODER |= (1 << 16);
    111. vir_led1->ODR &= (~(1 << 8));
    112. printk("寄存器初始化成功\n");
    113. return 0;
    114. }
    115. static int __init mycdev_init(void)
    116. {
    117. // 字符设备驱动注册
    118. major = register_chrdev(0, "mychrdev", &fops);
    119. if (major < 0)
    120. {
    121. printk("字符设备驱动注册失败\n");
    122. return major;
    123. }
    124. printk("字符设备驱动注册成功:major=%d\n", major);
    125. // 向上提交目录
    126. cls = class_create(THIS_MODULE, "mychrdev");
    127. if (IS_ERR(cls))
    128. {
    129. printk("向上提交目录失败\n");
    130. return -PTR_ERR(cls);
    131. }
    132. printk("向上提交目录成功\n");
    133. // 向上提交设备节点信息
    134. int i; // 向上提交三次设备节点信息
    135. for (i = 0; i < 3; i++)
    136. {
    137. dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
    138. if (IS_ERR(dev))
    139. {
    140. printk("向上提交设备节点失败\n");
    141. return -PTR_ERR(dev);
    142. }
    143. }
    144. printk("向上提交设备节点成功\n");
    145. // 寄存器映射以及初始化
    146. all_led_init();
    147. return 0;
    148. }
    149. static void __exit mycdev_exit(void)
    150. {
    151. // 取消地址映射
    152. iounmap(vir_led1);
    153. iounmap(vir_led2);
    154. iounmap(vir_rcc);
    155. // 销毁设备节点信息
    156. int i;
    157. for (i = 0; i < 3; i++)
    158. {
    159. device_destroy(cls, MKDEV(major, i));
    160. }
    161. // 销毁目录
    162. class_destroy(cls);
    163. // 注销字符设备驱动
    164. unregister_chrdev(major, "mychrdev");
    165. }
    166. module_init(mycdev_init);
    167. module_exit(mycdev_exit);
    168. MODULE_LICENSE("GPL");

  • 相关阅读:
    等保2.0 测评 linux服务器加固 基本安全配置手册
    CSDN每日一练 |『括号上色』『严查枪火』『数组排序』2023-09-09
    数据结构 —— 顺序表(超详细图解 & 接口函数实现)
    时间很快,我已二十
    《JavaSE-第十章》之抽象类与接口
    【无标题】
    亚马逊又出新规,多卖家又被封店
    docker-compose 部署rabbitmq 15672打不开
    双向链表C语言版本
    第八章 多线程和线程池编程
  • 原文地址:https://blog.csdn.net/Smallxu_/article/details/133979654