内核驱动程序的“发布”文件操作处理程序是否等待其他 fop 完成?

如何解决内核驱动程序的“发布”文件操作处理程序是否等待其他 fop 完成?

对于 linux 内核设备驱动程序,有 file_operations 结构体,或 fops 结构体,它允许驱动程序为各种文件操作定义处理程序。

我的问题是关于 .release fop 处理程序。

我知道 release 处理程序将file 对象的最后一个文件描述符 (fd) 关闭(或 munmapped)时被调用。这是在 fput 上调用 file 并且 file->f_count 达到 0 时完成的。

但是 - 我不清楚在输入 release 时是否可以在另一个线程中同时运行其他文件操作。

例如:

一个进程的 1 个线程是否可以在 ioctl(或 fd)的 file 处理程序内部,而同一进程的另一个线程在 release 处理程序内部?

>

release 能否成为 file 对象竞争条件的一个因素?

解决方法

一个进程的 1 个线程是否可以在文件(或 fd)的 ioctl 处理程序中,而同一进程的另一个线程在发布处理程序中?

没有。 release 入口点在引用计数器上被调用 文件条目为 0。ioctl() 增加文件上的引用计数器。因此,当 ioctl() 在轨道上时,不会调用 release 入口点。

前言

下面讨论的源代码是:

  • GLIBC 2.31
  • Linux 5.4

GLIBC 的 pthread 管理

GLIBC 的 pthread_create() 实际上涉及一个 clone() 系统调用 以下标志:

CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID

根据clone()manualCLONE_FILES标志使进程的线程

共享相同的文件描述符表。任何由

创建的文件描述符

一个线程在其他线程中也有效。类似地,如果一个线程关闭文件描述符,或更改其关联标志(使用 fcntl() F_SETFD 操作),其他线程也会受到影响。

内核端的clone()

clone() 被传递CLONE_FILES 时,files_struct 不会重复,但引用计数器会增加。因此,两个线程的任务结构都指向同一个 files_structfiles 字段):

。任务结构在include/linux/sched.h中定义:

struct task_struct {
[...]
    /* Open file information: */
    struct files_struct     *files; /// <==== Table of open files shared between thread
[...]

。在 kernel/fork.c 中,clone() 服务调用 copy_files() 来增加 files_struct 上的引用计数器em>

static int copy_files(unsigned long clone_flags,struct task_struct *tsk)
{
    struct files_struct *oldf,*newf;
    int error = 0;

    /*
     * A background process may not have any files ...
     */
    oldf = current->files;
    if (!oldf)
        goto out;

    if (clone_flags & CLONE_FILES) {
      atomic_inc(&oldf->count);  // <==== Ref counter incremented: files_struct is shared
        goto out;
    }

    newf = dup_fd(oldf,&error);
    if (!newf)
        goto out;

    tsk->files = newf;
    error = 0;
out:
    return error;
}

files_structinclude/linux/fdtable.h 中定义:

/*
 * Open file table structure
 */
struct files_struct {
  /*
   * read mostly part
   */
        atomic_t count;  // <==== Reference counter
    bool resize_in_progress;
    wait_queue_head_t resize_wait;

    struct fdtable __rcu *fdt;
    struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
    spinlock_t file_lock ____cacheline_aligned_in_smp;
    unsigned int next_fd;
    unsigned long close_on_exec_init[1];
    unsigned long open_fds_init[1];
    unsigned long full_fds_bits_init[1];
    struct file __rcu * fd_array[NR_OPEN_DEFAULT];

ioctl() 操作

ioctl() 系统调用被定义为 fs/ioctl.c。它首先调用 fdget() 来增加文件条目上的引用计数器,执行请求的操作,然后调用 fdput()

int ksys_ioctl(unsigned int fd,unsigned int cmd,unsigned long arg)
{
    int error;
    struct fd f = fdget(fd);

    if (!f.file)
        return -EBADF;
    error = security_file_ioctl(f.file,cmd,arg);
    if (!error)
        error = do_vfs_ioctl(f.file,fd,arg);
    fdput(f);
    return error;
}

SYSCALL_DEFINE3(ioctl,unsigned int,unsigned long,arg)
{
    return ksys_ioctl(fd,arg);
}

file 条目定义在 include/linux/fs.h 中。它的引用计数器是 f_count 字段:

struct file {
    union {
        struct llist_node   fu_llist;
        struct rcu_head     fu_rcuhead;
    } f_u;
    struct path     f_path;
    struct inode        *f_inode;   /* cached value */
    const struct file_operations    *f_op;

    /*
     * Protects f_ep_links,f_flags.
     * Must not be taken from IRQ context.
     */
    spinlock_t      f_lock;
    enum rw_hint        f_write_hint;
        atomic_long_t       f_count;  // <===== Reference counter
    unsigned int        f_flags;
[...]
} __randomize_layout
  __attribute__((aligned(4)));

示例

这是一个简单的设备驱动程序,其中文件操作在触发时仅显示一条消息。 ioctl() 条目使调用者休眠 5 秒:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/delay.h>


MODULE_LICENSE("GPL");

#define DEVICE_NAME "device"

static int device_open(struct inode *,struct file *);
static int device_release(struct inode *,struct file *);
static ssize_t device_read(struct file *,char *,size_t,loff_t *);
static ssize_t device_write(struct file *,const char *,loff_t *);
static long int device_ioctl(struct file *,unsigned long);
static int device_flush(struct file *,fl_owner_t);

static const struct file_operations fops = {
    .owner = THIS_MODULE,.read = device_read,.write = device_write,.unlocked_ioctl = device_ioctl,.open = device_open,.flush = device_flush,.release = device_release
};

struct cdev *device_cdev;
dev_t deviceNumbers;

static  int __init init(void)
{
  // This returns the major number chosen dynamically in deviceNumbers
  int ret = alloc_chrdev_region(&deviceNumbers,1,DEVICE_NAME);

  if (ret < 0) {
    printk(KERN_ALERT "Error registering: %d\n",ret);
    return -1;
  }

  device_cdev = cdev_alloc();

  cdev_init(device_cdev,&fops);

  ret = cdev_add(device_cdev,deviceNumbers,1);

  printk(KERN_INFO "Device initialized (major number is %d)\n",MAJOR(deviceNumbers));

  return 0;
}

static void __exit cleanup(void)
{
  unregister_chrdev_region(deviceNumbers,1);

  cdev_del(device_cdev);

  printk(KERN_INFO "Device unloaded\n");
}

static int device_open(struct inode *inode,struct file *file)
{
  printk(KERN_INFO "Device open\n");
  return 0;
}

static int device_flush(struct file *file,fl_owner_t id)
{
  printk(KERN_INFO "Device flush\n");
  return 0;
}

static int device_release(struct inode *inode,struct file *file)
{
  printk(KERN_INFO "Device released\n");
  return 0;
}


static ssize_t device_write(struct file *filp,const char *buff,size_t len,loff_t * off)
{
  printk(KERN_INFO "Device write\n");
  return len;
}

static ssize_t device_read(struct file *filp,char *buff,loff_t * off)
{
  printk(KERN_INFO "Device read\n");
  return 0;
}

static long int device_ioctl(struct file *file,unsigned int ioctl_num,unsigned long ioctl_param)
{
  printk(KERN_INFO "Device ioctl enter\n");
  msleep_interruptible(5000);
  printk(KERN_INFO "Device ioctl out\n");
  return 0;
}

module_init(init);
module_exit(cleanup);

这是一个包含主线程和辅助线程的用户空间程序。主线程打开上述设备并等待辅助线程启动(屏障),然后在 1 秒后关闭设备。同时,辅助线程在上述设备上调用 ioctl() 使其休眠 5 秒。然后它在退出前第二次调用 ioctl()
预期行为是在辅助线程运行 ioctl() 时使主线程关闭设备文件。

#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>

static int dev_fd;

static pthread_barrier_t barrier;


void *entry(void *arg)
{
  int rc;

  printf("Thread running...\n");

  // Rendez-vous with main thread
  pthread_barrier_wait(&barrier);

  rc = ioctl(dev_fd,0);
  printf("rc = %d,errno = %d\n",rc,errno);
  
  rc = ioctl(dev_fd,errno);

  return NULL;
}

int main(void)
{
  pthread_t tid;

  dev_fd = open("/dev/device",O_RDWR);

  pthread_barrier_init(&barrier,NULL,2);

  pthread_create(&tid,entry,NULL);

  pthread_barrier_wait(&barrier);

  sleep(1);

  close(dev_fd);

  pthread_join(tid,NULL);

  return 0;
}

安装内核模块:

$ sudo insmod ./device.ko
$ dmesg
[13270.589766] Device initialized (major number is 237)
$ sudo mknod /dev/device c 237 0
$ sudo chmod 666 /dev/device 
$ ls -l /dev/device 
crw-rw-rw- 1 root root 237,0 janv.  27 10:55 /dev/device

程序的执行表明,第一个ioctl()使线程等待5秒。但是第二个使用 EBADF (9) 返回错误,因为同时设备文件已被主线程关闭:

$ gcc p1.c -lpthread
$ ./a.out
Thread running...
rc = 0,errno = 0
rc = -1,errno = 9

在内核日志中,我们可以看到主线程中的close()仅仅在设备上触发了一个flush()操作,而第一个 ioctl() 在辅助线程的轨道上。然后,一旦第一个 ioctl() 返回,内核内部就会释放文件条目(引用计数器降为 0),因此,第二个 ioctl() 没有到达设备,因为文件描述符不再引用打开的文件。因此,第二次调用时出现 EBADF 错误:

[13270.589766] Device initialized (major number is 237)
[13656.862951] Device open        <==== Open() in the main thread
[13656.863315] Device ioctl enter <==== 1st ioctl() in secondary thread
[13657.863523] Device flush       <==== 1 s later,flush() = close() in the main thread
[13661.941238] Device ioctl out   <==== 5 s later,the 1st ioctl() returns
[13661.941244] Device released    <==== The file is released because the reference counter reached 0

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res