首页 > 代码库 > [Linux驱动]字符设备驱动学习笔记(一)

[Linux驱动]字符设备驱动学习笔记(一)

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

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

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

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

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

[cpp] view plaincopy技术分享技术分享
 
  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\n”);  
  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技术分享技术分享
 
  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\n");  
  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技术分享技术分享
 
  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=http://www.mamicode.com/dev;
  11.     return 0;  
  12. }  


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

 

 

[cpp] view plaincopy技术分享技术分享
 
    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);  

[Linux驱动]字符设备驱动学习笔记(一)