Linux字符设备驱动——经典标准字符模型

矫情吗;* 2021-12-19 19:45 445阅读 0赞

文章目录

  • 特征
  • 头文件
  • 注册函数
  • 注销函数
  • 设备号,设备驱动节点需要手动创建
  • 经典标准字符驱动模型
    • 驱动程序zx_chrdev_led.c源代码
    • 应用程序app.c源代码
    • 实例代码测试
    • 测试次设备号是否是全部占用

经典标准字符模型,即为Linux2.6之前的早期经典标准字符模型。

特征

  1. 没有使用一个核心的结构体,把需要的信息进行封装
  2. 安装驱动后,不会在/dev/目录下创建设备节点,需要使用mknod创建。
  3. 一个主设备号只能被注册一次,一次注册0~255的次设备号就被占用了。
    杂项设备,每次注册,只占用了一个次设备号,(主设备号固定为10)

头文件

#include

注册函数

注册一个标准字符设备
int register_chrdev(unsigned int major,const char *name, const struct file_operations *fops)
参数:

  1. major 主设备号
  2. mane 设备名,不需要和/dev 下对应节点名相同
  3. fops 文件操作集合
  4. 返回值
  5. 1)、当major0时,表示动态分配
  6. 成功:返回分配的主设备号
  7. 失败:返回负数
  8. 2)、当major>0时,表示静态注册,使用用户定义的主设备号
  9. 成功:返回0
  10. 失败:返回负数

注销函数

void unregister_chrdev(unsigned int major, const char *name)
参数

  1. major:主设备号
  2. name:设备名,使用register_chrdev 注册的设备名。
  3. 返回值:无

设备号,设备驱动节点需要手动创建

主设备号:0~255,10为杂项设备号
次设备号:0~255

注意:
早期经典字符设备不会在dev目录下创建设备驱动节点,需要用户手动创建,并且注册后占用一个主设备号,因此次设备号被全部占用。

注册字符设备后,都会在/proc/decices目录下出现主设备号。

创建主设备号250,次设备号100的设备节点
mknod /dev/abc c 250 100

打印等级,数字越小,等级越高
查看开发板等级
cat /proc/sys/kernel/prink

早期模型,注册了主设备号,就会占用其下的次设备号。

经典标准字符驱动模型

驱动程序zx_chrdev_led.c源代码

  1. #include <linux/module.h> //驱动模块头文件
  2. #include <linux/init.h>
  3. #include <linux/fs.h>
  4. #include <asm/io.h> //开发板引脚支持头文件
  5. #include <asm/uaccess.h> //copy_from_user ,copy_to_user
  6. #include<linux/gpio.h> // GPIO头文件
  7. #define DEV_NAME "zx_chrdev_led"
  8. #define LED_NUM (4)
  9. static unsigned int LED_pin[LED_NUM] = {
  10. EXYNOS4X12_GPM4(0),
  11. EXYNOS4X12_GPM4(1),
  12. EXYNOS4X12_GPM4(2),
  13. EXYNOS4X12_GPM4(3),
  14. };
  15. static unsigned int major = 0; //动态配置设置0,否则设置正数1~255 (自己保证可用)
  16. static char * pchrdev_name = DEV_NAME;
  17. static ssize_t chrdev_read (struct file *pfile, char __user *buff, size_t size, loff_t *off)
  18. {
  19. printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  20. return 0;
  21. }
  22. static ssize_t chrdev_write(struct file *filp,
  23. const char __user *user_buf,
  24. size_t count,
  25. loff_t *off)
  26. {
  27. int ret = 0;
  28. char buf[LED_NUM] = {0};
  29. int i = 0;
  30. printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  31. if(count == 0){
  32. return 0;
  33. }
  34. if(count > LED_NUM){ //因为板子只有4盏灯
  35. count = LED_NUM;
  36. }
  37. ret = copy_from_user(buf, user_buf, count); //用户空间传数据到内核空间
  38. if(ret){
  39. return ret;
  40. }
  41. for(i = 0; i < count; i++)
  42. {
  43. if(buf[i] == 1){
  44. gpio_set_value(LED_pin[i],0);/* 亮 */
  45. }else if(buf[i] == 0){
  46. gpio_set_value(LED_pin[i],1);/* 灭 */
  47. }
  48. }
  49. return count;
  50. }
  51. static int chrdev_open(struct inode *pinode, struct file *pfile)
  52. {
  53. printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
  54. return 0;
  55. }
  56. static int chrdev_release(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 const struct file_operations chrdev_fops = {
  62. .read = chrdev_read,
  63. .write = chrdev_write,
  64. .release = chrdev_release,
  65. .open = chrdev_open,
  66. };
  67. static int __init zx_chrdev_led_init(void)
  68. {
  69. int ret;
  70. int i;
  71. for(i = 0;i < LED_NUM; i++)
  72. {
  73. ret = gpio_request(LED_pin[i],"leds");
  74. if(ret < 0){
  75. printk(KERN_EMERG "gpio_request is error\n");
  76. goto gpio_request_err;
  77. }
  78. gpio_direction_output(LED_pin[i],1); //四盏灯设置为输出功能,并且输出高电平
  79. }
  80. if(major){
  81. ret = register_chrdev(major,pchrdev_name,&chrdev_fops); //静态分设备号配方式
  82. if ( ret < 0 ){
  83. printk(KERN_EMERG "register_chrdev error\n");
  84. goto register_chrdev_err;
  85. }else{
  86. major = register_chrdev(0, pchrdev_name, &chrdev_fops); //动态分配设备号方式
  87. if ( major < 0 ){
  88. ret = major;
  89. printk(KERN_EMERG "register_chrdev error\n");
  90. goto register_chrdev_err;
  91. }
  92. }
  93. printk(KERN_EMERG "major:%d\n",major);
  94. printk(KERN_EMERG "%s is OK!!!\n",__FUNCTION__);
  95. return 0;
  96. register_chrdev_err:
  97. gpio_request_err:
  98. for(--i; i >= 0; i--)
  99. {
  100. gpio_free(LED_pin[i]);
  101. }
  102. return ret ;
  103. }
  104. static void __exit zx_chrdev_led_exit(void)
  105. {
  106. int i = 0;
  107. for(i=0;i<4;i++)
  108. {
  109. gpio_set_value(LED_pin[i],1); //灭灯
  110. gpio_free(LED_pin[i]); //释放GPIO
  111. }
  112. unregister_chrdev(major, pchrdev_name);
  113. printk(KERN_EMERG "Goodbye,zx_chrdev_led\n");
  114. }
  115. module_init(zx_chrdev_led_init);
  116. module_exit(zx_chrdev_led_exit);
  117. 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_chrdev_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. fd = open(DEV_NAME, O_RDWR);
  16. if(fd < 0){
  17. printf("open :%s failt!\r\n", DEV_NAME);
  18. }
  19. while(1)
  20. {
  21. write(fd, buf, LED_NUM);
  22. memset(buf, 0, LED_NUM);
  23. buf[i++ % LED_NUM] = 1;
  24. sleep(1);
  25. }
  26. return 0;
  27. }

对应的Makefile只需修改杂项设备对应的Makefile,修改驱动的名字就可以。

实例代码测试

  1. [root@ZX20150811 /home]# ls
  2. app zx_chrdev_led.ko
  3. [root@ZX20150811 /home]# insmod zx_chrdev_led.ko
  4. [ 3643.345000] major:249
  5. [ 3643.345000] zx_chrdev_led_init is OK!!!
  6. [root@ZX20150811 /home]# mknod /dev/zx_chrdev_led c 249 1
  7. [root@ZX20150811 /home]# ./app
  8. [ 3740.410000] line:61,chrdev_open is call
  9. [ 3740.410000] line:36,chrdev_write is call
  10. [ 3741.410000] line:36,chrdev_write is call
  11. [ 3742.410000] line:36,chrdev_write is call
  12. [ 3743.410000] line:36,chrdev_write is call
  13. [ 3744.410000] line:36,chrdev_write is call
  14. [ 3745.410000] line:36,chrdev_write is call
  15. [ 3746.410000] line:36,chrdev_write is call
  16. ^C[ 3746.990000] line:68,chrdev_release is call
  17. [root@ZX20150811 /home]#

这里的次设备号为1,因为经典标准字符模型一旦注册了主设备号,这个主设备号下的次设备号都被占用。下面测试其它的次设备号。

测试次设备号是否是全部占用

  1. [root@ZX20150811 /home]# rmmod zx_chrdev_led.ko
  2. [ 3879.105000] Goodbye,zx_chrdev_led
  3. [root@ZX20150811 /home]# rm -f /dev/zx_chrdev_led
  4. [root@ZX20150811 /home]# ls
  5. app zx_chrdev_led.ko
  6. [root@ZX20150811 /home]# insmod zx_chrdev_led.ko
  7. [ 3913.425000] major:249
  8. [ 3913.425000] zx_chrdev_led_init is OK!!!
  9. [root@ZX20150811 /home]# mknod /dev/zx_chrdev_led c 249 23
  10. [root@ZX20150811 /home]# ./app
  11. [ 3950.420000] line:61,chrdev_open is call
  12. [ 3950.420000] line:36,chrdev_write is call
  13. [ 3951.420000] line:36,chrdev_write is call
  14. [ 3952.420000] line:36,chrdev_write is call
  15. [ 3953.420000] line:36,chrdev_write is call
  16. [ 3954.420000] line:36,chrdev_write is call
  17. ^C[ 3954.925000] line:68,chrdev_release is call
  18. [root@ZX20150811 /home]#

结果可以知道,换个次设备号应用程序依然能够识别并使用。

发表评论

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

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

相关阅读

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

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