ldd3学习之十二(4):高级字符驱动程序操作--llseek、设备文件的访问控制

1.llseek

llseek方法实现了lseek和llseek的系统调用,如果设备操作为定义llseek方法,内核默认通过修改filp->f_pos而执行定位,filp->f_pos是文件的当前读取/写入位置,为了使lseek系统调用能正确工作,read和write方法必须通过更新他们收到的偏移量参数来配合。

Scrull的驱动例子

  1. loff_t scull_llseek(struct file *filp, loff_t off, int whence)
  2. {
  3.     struct scull_dev *dev = filp->private_data;
  4.     loff_t newpos;

  5.     switch(whence) {
  6.      case 0: /* SEEK_SET */
  7.         newpos = off;
  8.         break;

  9.      case 1: /* SEEK_CUR */
  10.         newpos = filp->f_pos + off;
  11.         break;

  12.      case 2: /* SEEK_END */
  13.         newpos = dev->size + off;
  14.         break;

  15.      default: /* can't happen */
  16.         return -EINVAL;
  17.     }
  18.     if (newpos < 0) return -EINVAL;
  19.     filp->f_pos = newpos;
  20.     return newpos;
  21. }

大多数设备只提供了数据流(比如串口和键盘),而不是数据区,定义这些设备没有意义,在这中情况下,不能简单的不声明llseek操作,因为默认方法是允许定位的,相反,应该在open方法中调用nonseekable_open,以便通知内核设备不支持llseek,

int nonseekable_open(struct inode *inode, struct file *filp)

该函数会吧给定的filp标记为不可定位;这样内核就不会让这种文件上的lseek调用成功。通过这种方式标记文件,还可以确保通过pread和pwrite系统调用也不能定位文件。

为了完整期间,我们还应该将file_operations结构中的llseek方法设备为特殊的辅助函数no_llseek(定义在 )

2.设备文件的访问控制

提供访问控制对于一个设备节点来的可靠性来说有时是至关重要的。这部分的内容只是在open和release方法上做些修改,增加一些检查机制既可。

(1)独享设备

最生硬的访问控制方法是一次只允许一个进程打开设备(独享),最好避免使用这种技术,因为它制约了用户的灵活性。scullsingle设备维护一个atomic_t变量,称为scull_s_available,该变量初值为1,表明设备真正可用,open调用会减小并测试scull_s_available,并在其他进程已经打开该设备拒绝访问

  1. static int scull_s_open(struct inode *inode, struct file *filp)
  2. {
  3.     struct scull_dev *dev; /* device information */
  4.     dev = container_of(inode->i_cdev, struct scull_dev, cdev);

  5.     if (! atomic_dec_and_test (&scull_s_available)) {
  6.         atomic_inc(&scull_s_available);
  7.         return -EBUSY; /* already open */
  8.     }

  9.     /* then, everything else is copied from the bare scull device */
  10.     if ( ((filp->f_flags & O_ACCMODE) && O_WRONLY))
  11.         scull_trim(dev);
  12.     filp->private_data = dev;
  13.     return 0; /* success */
  14. }

release则标记设备为不忙

  1. static int scull_s_release(struct inode *inode, struct file *filp)
  2. {
  3. atomic_inc(&scull_s_available); /* release the device */
  4. return 0;
  5. }

建议吧打开标志scull_s_available放在私有设备结构里

(2)单用户访问

open调用在第一次打开授权,但它记录下设备的属主,这意味着一个用户可以多次打开设备,晕血多个进程并发的在设备上操作。其他用户不能打开这个设备,这样就避免了外部干扰。

  1. spin_lock(&scull_u_lock);
  2.     if (scull_u_count &&
  3.             (scull_u_owner != current->uid) && /* allow user */
  4.             (scull_u_owner != current->euid) && /* allow whoever did su */
  5.             !capable(CAP_DAC_OVERRIDE)) { /* still allow root */
  6.         spin_unlock(&scull_u_lock);
  7.         return -EBUSY; /* -EPERM would confuse the user */
  8.     }

  9.     if (scull_u_count == 0)
  10.         scull_u_owner = current->uid; /* grab it */

  11.     scull_u_count++;
  12.     spin_unlock(&scull_u_lock);

相应的释放函数

  1. static int scull_u_release(struct inode *inode, struct file *filp)
  2. {
  3.     spin_lock(&scull_u_lock);
  4.     scull_u_count--; /* nothing else */
  5.     spin_unlock(&scull_u_lock);
  6.     return 0;
  7. }

(3)替代EBUSY的阻塞型open

当设备不能访问时返回一个错误,通常这是最合理的方式,但有些情况下可能需要让进程等待设备。

代理EBUSY的一个方法是实现阻塞型open。

  1. static int scull_w_open(struct inode *inode, struct file *filp)
  2. {
  3.     struct scull_dev *dev = &scull_w_device; /* device information */

  4.     spin_lock(&scull_w_lock);
  5.     while (! scull_w_available()) {
  6.         spin_unlock(&scull_w_lock);
  7.         if (filp->f_flags & O_NONBLOCK) return -EAGAIN;
  8.         if (wait_event_interruptible (scull_w_wait, scull_w_available()))
  9.             return -ERESTARTSYS; /* tell the fs layer to handle it */
  10.         spin_lock(&scull_w_lock);
  11.     }
  12.     if (scull_w_count == 0)
  13.         scull_w_owner = current->uid; /* grab it */
  14.     scull_w_count++;
  15.     spin_unlock(&scull_w_lock);

  16.     /* then, everything else is copied from the bare scull device */
  17.     if ((filp->f_flags & O_ACCMODE) == O_WRONLY)
  18.         scull_trim(dev);
  19.     filp->private_data = dev;
  20.     return 0; /* success */
  21. }

release方法唤醒所有等待的进程

  1. static int scull_w_release(struct inode *inode, struct file *filp)
  2. {
  3.     int temp;

  4.     spin_lock(&scull_w_lock);
  5.     scull_w_count--;
  6.     temp = scull_w_count;
  7.     spin_unlock(&scull_w_lock);

  8.     if (temp == 0)
  9.         wake_up_interruptible_sync(&scull_w_wait); /* awake other uid's */
  10.     return 0;
  11. }

这类问题(对同一设备的不同的,不兼容的策略)最好通过为每一种访问策略实现一个设备节点的方法来解决。

(4)在打开时复制设备

另一个实现访问控制的方法是,在进程打开设备时创建设备的不同私有副本。

显然这种方法只有在设备没有绑定到某个硬件对象时才能实现,/dev/tty内部也使用了类似的技术,以提供给它的进程一个不同于/dev入口点所表现出的“情景”,如果复制的设备是由软件驱动程序创建,我们称它们为“虚拟设备”--就像所有的虚拟终端都是用同一个物理终端设备一样。

虽然这种访问控制并不常见,但它的实现展示了内核代码可以轻松的改变应用程序看到的外部环境。

  1. /************************************************************************
  2.  *
  3.  * Finally the `cloned' private device. This is trickier because it
  4.  * involves list management, and dynamic allocation.
  5.  */

  6. /* The clone-specific data structure includes a key field */

  7. struct scull_listitem {
  8.     struct scull_dev device;
  9.     dev_t key;
  10.     struct list_head list;
  11.     
  12. };

  13. /* The list of devices, and a lock to protect it */
  14. static LIST_HEAD(scull_c_list);
  15. static spinlock_t scull_c_lock = SPIN_LOCK_UNLOCKED;

  16. /* A placeholder scull_dev which really just holds the cdev stuff. */
  17. static struct scull_dev scull_c_device;

  18. /* Look for a device or create one if missing */
  19. static struct scull_dev *scull_c_lookfor_device(dev_t key)
  20. {
  21.     struct scull_listitem *lptr;

  22.     list_for_each_entry(lptr, &scull_c_list, list) {
  23.         if (lptr->key == key)
  24.             return &(lptr->device);
  25.     }

  26.     /* not found */
  27.     lptr = kmalloc(sizeof(struct scull_listitem), GFP_KERNEL);
  28.     if (!lptr)
  29.         return NULL;

  30.     /* initialize the device */
  31.     memset(lptr, 0, sizeof(struct scull_listitem));
  32.     lptr->key = key;
  33.     scull_trim(&(lptr->device)); /* initialize it */
  34.     init_MUTEX(&(lptr->device.sem));

  35.     /* place it in the list */
  36.     list_add(&lptr->list, &scull_c_list);

  37.     return &(lptr->device);
  38. }

  39. static int scull_c_open(struct inode *inode, struct file *filp)
  40. {
  41.     struct scull_dev *dev;
  42.     dev_t key;
  43.  
  44.     if (!current->signal->tty) {
  45.         PDEBUG("Process \"%s\" has no ctl tty\n", current->comm);
  46.         return -EINVAL;
  47.     }
  48.     key = tty_devnum(current->signal->tty);

  49.     /* look for a scullc device in the list */
  50.     spin_lock(&scull_c_lock);
  51.     dev = scull_c_lookfor_device(key);
  52.     spin_unlock(&scull_c_lock);

  53.     if (!dev)
  54.         return -ENOMEM;

  55.     /* then, everything else is copied from the bare scull device */
  56.     if ( (filp->f_flags & O_ACCMODE) == O_WRONLY)
  57.         scull_trim(dev);
  58.     filp->private_data = dev;
  59.     return 0; /* success */
  60. }

  61. static int scull_c_release(struct inode *inode, struct file *filp)
  62. {
  63.     /*
  64.      * Nothing to do, because the device is persistent.
  65.      * A `real' cloned device should be freed on last close
  66.      */
  67.     return 0;
  68. }

至此,Linux高级字符驱动程序操作已经学习完毕,(其实这部分已经看过几遍,后续还要继续看,有些没理解透)这部分笔记现在才整理,一方面是因为难度,一方面是实际工作用的不是很多,没有专门花时间来整理。anyway,到目前为止,Linux字符驱动所有基础知识笔记整理完毕,这是个辛苦的过程,但收获很大。再次的,特别感谢Tekkaman Ninja大侠,LDD3笔记整理过程中参考了他的大部分,给了我很大帮助(若涉及版权相关,全都归他)。

<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
阅读(1383) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~
评论热议
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值