zoukankan      html  css  js  c++  java
  • [Linux驱动]字符设备驱动学习笔记(一)

    一,主设备号和次设备号代表的含义?linu内核是如果根据主设备号找驱动,次设备号找设备的。

    答:通常一个主设备号代表一个驱动,比如在block设备中,一个主设备号代表一个emmc设备,不同次设备号代表的是不同的分区

    Linux内核允许多个驱动共享一个主设备号,但更多的设备都遵循一个驱动对一个主设备号的原则。内核维护者一个以主设备号为key的全局哈希表,而哈希表中数据部分则为与该主设备号设备对应的驱动程序(只有一个次设备)的指针或者多个次设备驱动程序组成的数组的指针(次设备共享主设备号)

    二,编写字符设备的一般顺序

    一,调用kmalloc memset函数对相关结构体(比如设备结构体)进行初始化的动作
    二,注册相应的驱动,如平台驱动则调用platform_driver_register(&driver)进行注册,driver是一个全部静态变量。

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. struct test_dev{  
    2.     struct cdev cdev;  
    3.     char *test_devname;  
    4.     char actname[32];  
    5.     unsigned int index;  
    6.     struct semphare sem;  
    7. }  
    8. static struct platform_driver test_driver = {  
    9.     .probe = test_probe,  
    10.     .remove = test_remove,  
    11.     .driver = {  
    12.         .name = "test_char",  
    13.         .owner = THIS_MODULE,  
    14.     },  
    15. };  
    16.       
    17.           
    18. #define TEST_NUM 10  
    19. static struct test_dev *test_devices;  
    20. static int __init test_init(void)  
    21. {  
    22.     int result;  
    23.     test_devices=kmalloc(TEST_NUM*sizeof(struct test_dev),GFP_KERNEL);  
    24.     if(!test_devices)  
    25.     {  
    26.         result = -ENOMEM  
    27.         printk(“alloc test_devices fail ”);  
    28.         goto fail_malloc;  
    29.     }  
    30.     memset(test_devices,0,TEST_NUM*sizeof(struct test_dev));  
    31.     result = platform_driver_register(&test_driver);  
    32.     if (result)  
    33.     {  
    34.         printk("fail to register test_driver");  
    35.         goto fail_driver_register;  
    36.     }  
    37.     return 0  
    38. fail_driver_register:  
    39.     return result;  
    40. fail_malloc:  
    41.     return result;  
    42. }  
    43. module_init(test_init);  



    三,接来下会调用到test_probe()函数,该函数首先alloc_chrdev_region()函数分配主设备号和次设备号,然后调用cdev_init()函数来注册真正的字符设备,void cdev_init(struct cdev*cdev,struct file_operations *fops)最后调用cdev_add()函数来告诉内核该结构体的信息init cdev_add(struct cdev*cdev,dev_t num,unsigned int count)

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. int test_probe(struct platform_device *dev)  
    2. {  
    3.     int result;  
    4.     dev_t devno;  
    5.     result = alloc_chrdev_region(&devno,0,TEST_NUM,"testchar");  
    6.     if(result<0)  
    7.     {  
    8.         printk();  
    9.         goto fail_alloc_chrdev;  
    10.     }  
    11.     major = MAJOR(devno);  
    12.     for(int i=0;i<TEST_NUM;I++)  
    13.     {  
    14.         devno=MKDEV(major,i);  
    15.         cdev_init(&test_devices[i].cdev,&test_fops);  
    16.         test_devices[i].cdev.owner = THIS_MODULE;  
    17.         test_devices[i].cdev.ops = &test_fops;  
    18.         result = cdev_add(&test_devices[i].cedv,devno,1);  
    19.         if(result)  
    20.         {  
    21.             printk("cdev add fail ");  
    22.             goto fail_register_chrdev;  
    23.         }  
    24.   
    25.     }  
    26.     return 0;  
    27. fail_register_chrdev:  
    28.     cdev_del(&test_devices[i].cdev);  
    29.     unregister_chrdev_region(MKDEV(major,0),TEST_NUM);  
    30.   
    31. }  



    三,阻塞型I/O

    如果在调用字符设备的read write方法中,设备没有准备好可能导致用户层要去读取的进程阻塞(默认),将其置入休眠直到请求可以继续。将进程置入休眠要注意的两点
    (1)不要再原子上下文中进行休眠,驱动程序不能再拥有自旋锁,RCU锁时候休眠,拥有信号量的进程休眠是合法的,但是等待此信号量的其它进程也必须休眠,因此拥有信号量的进程休眠时间要足够短
    (2)当进程唤醒的时候不知道发生过什么,所以检查以确保我们等待的条件真正为真。
    让进程休眠的方法:
    wait_event()相关的函数
    唤醒进程方法
    wake_up()相关的函数

    如何实现非阻塞的I/O操作
    答:填充filp->f_flags中的O_NONBLOCK flag,如果以非阻塞方式打开,如果此时设备没有就绪好的数据,那么会返回-EAGAIN错误。

    四,设备文件的访问控制

    1,让一个进程独享设备,通过维护一个原子变量

    使用实例:adb的驱动程序,每次只让adbd一个进程使用该设备。

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static int scull_s_poen(struct inode*inode,struct file*filp)  
    2. {  
    3.     struct scull_dev *dev = &scull_s_device;  
    4.     if(!atomic_dec_and_test(&scull_s_available)){  
    5.         atomic_inc(&scull_s_available);  
    6.         return -EBUSY;  
    7.     }  
    8.     if((filp->flags & O_ACCMODE)==O_WRONLY)  
    9.         SCULL_trim(dev);  
    10.     filp->priate_data=dev;  
    11.     return 0;  
    12. }  


    2,限制每次只由一个用户访问

    [cpp] view plaincopy在CODE上查看代码片派生到我的代码片
     
      1. spin_lock(&scull_u_lock);       //scull_u_lock是全局变量,所以用自旋锁,自选锁使用过程不能睡眠  
      2. if(scull_u_count && (scull_u_owner != current->uid) &&(scull_u_owner != current->euid) && !capable(CAP_DAC_OVERRIDE) )  
      3. {  
      4.     spin_unlock(&scull_u_lock);  
      5.     return -EBUSY;  
      6. }  
      7. if(scull_u_count == 0)  
      8. {  
      9.     scull_u_owner=current->uid;  //第一个访问设备的用户为属主用户  
      10. }  
      11. scull_u_count++;  
      12. spin_unlock(&scull_u_lock);  
  • 相关阅读:
    精准测试
    git 管理
    git
    代码覆盖率测试
    vue 前端视频
    jenkins
    go学习资料
    4-4 求自定类型元素的平均
    4-3 简单求和
    4-2 多项式求值
  • 原文地址:https://www.cnblogs.com/zhiliao112/p/4237226.html
Copyright © 2011-2022 走看看