quejing
发布于 2023-12-26 / 259 阅读
0

11 设备定位和访问控制

本节是本书中《高级字符驱动程序操作》章节的第四节内容,也是最后一节,主要是关于设备读写位置定位和设备文件的访问控制相关的概念和内容。

本节主要有以下内容:

  • llseek的实现
  • 设备文件的访问控制

1. llseek的实现

llseek操作只对于具有明确定义的数据区的设备有意义,而对于提供数据流的设备(如键盘、串口等)是无效的。

对于提供了数据区的设备,如我们之前的scull设备,如果要提供定位功能,需要我们去实现llseek方法。llseek主要用来操作文件的指针,其实质是内核依赖filp->f_ops执行文件的定位。

书中提供的代码示例如下:

loff_t scull_llseek(struct file *filp, loff_t offset, int whence)
{
    struct scull_dev *dev = filp->private_data;
    loff_t newpos;
 
    switch(whence) {
      case 0: /* SEEK_SET */
        newpos = offset;
        break;
 
      case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;
        break;
 
      case 2: /* SEEK_END */
        newpos = dev->size + offset;
        break;
 
      default: /* can't happen */
        return -EINVAL;
    }
    if ((newpos<0) return -EINVAL;
    filp->f_pos = newpos;
    return newpos;
}

对于不提供数据区的设备,不能简单地不声明llseek操作,因为默认方法是允许定位的,我们需要在open方法中调用nonseekable_open:

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

除此之外,我们还需要将file_operations结构体中的llseek域设置为辅助函数no_llseek,该函数位于<linux/fs.h>中。

2. 备文件的访问控制

设备文件的访问控制主要包括:独享设备、限制一个用户访问、阻塞open以及在打开时复制设备。

独享设备

这是最容易实现的一种控制方法,也是最生硬的一种控制方法,一次只允许一个进程打开设备。

书中提供的示例代码维护一个atomic_t变量,初始化为1,每次打开设备时减1,释放设备时加1。当为1时表示设备可用,打开成功,否则返回-EBUSY。

示例代码如下:

static atomic_t scull_s_available = ATOMIC_INIT(1);
static int scull_s_open(struct inode *inode, struct file *filp)
{
  struct scull_dev *dev = &scull_s_device;
  if (! atomic_dec_and_test (&scull_s_available)) {
    atomic_inc(&scull_s_available);
    return -EBUSY;
  }
  if ( (file->f_flags & O_ACCMODE) == O_WRONLY)
    scull_trim(dev);
  filp->private_data = dev;
  return 0;
}

static int scull_s_release(struct inode *inode, struct file *filp)
{
  atomic_inc(&scull_s_available);
  return 0;
}

限制一个用户访问

该方法需要维护两个数据项:一个是打开计数器,一个是设备属主的UID。

在open调用第一次打开时授权,记录设备的属主,这意味着一个用户可以多次打开设备,允许几个互相协作的进程并发地在设备上操作,同时其他用户不能打开这个设备,避免外部干扰。

部分代码如下:

static int scull_open(struct inode *inode, struct file *filp)
{
  ......
  spin_lock(&scull_u_lock);
  if (scull_u_count && (scull_u_owner != current->uid) && /* 允许用户 */
      (scull_u_owner != current->euid) && /* 允许个执行su命令的用户 */
      !capable(CAP_DAC_OVERRIDE)) { /* 允许root用户 */
    spin_unlock(&scull_u_lock);
    return -EBUSY;
  }
  if (scull_u_count == 0)
    scull_u_owner = current->uid; /* 获得所有者 */
  scull_u_count++;
  spin_unlock(&scull_u_lock);
  ......
}
  
static int scull_u_release(struct inode *inode, struct file *filp)
{
    spin_lock(&scull_u_lock);
    scull_u_count--;
    spin_unlock(&scull_u_lock);
    return 0;
}

另外两种设备文件的访问控制方法(阻塞open和在打开时复制设备)我没怎么接触过,这里就不说了,有兴趣的可以自己去查找相关资料。