【问题标题】:What kinds of data in a device driver can be shared among processes?设备驱动程序中的哪些数据可以在进程之间共享?
【发布时间】:2019-03-08 00:38:33
【问题描述】:

在设备驱动程序中,我们如何知道哪些数据在进程之间共享,哪些数据是进程本地的? Linux Device Drivers book 提到了

任何时候硬件或软件资源在单个执行线程之外共享,并且存在一个线程可能遇到该资源的不一致视图的可能性,您必须明确管理对该资源的访问。

但是哪些软件资源可以在线程之间共享,哪些数据不能共享呢?我知道全局变量通常被认为是共享内存,但是还有哪些其他类型的东西需要保护呢?

例如,openreleasereadwrite 等文件操作中传递的struct inodestruct file 类型是否被认为是共享的? 在open 内部的main.c 调用中,如果dev(在dev = container_of(inode->i_cdev, struct scull_dev, cdev); 行中)指向全局数组scull_devices 中的struct scull_dev 条目,为什么它不受锁保护? 在scull_write 中,为什么int quantum = dev->quantum, qset = dev->qset; 行没有被信号量锁定,因为它正在访问一个全局变量?

/* In scull.h */

struct scull_qset {
    void **data;   /* pointer to an array of pointers which each point to a quantum buffer */
    struct scull_qset *next;
};

struct scull_dev {
    struct scull_qset *data;  /* Pointer to first quantum set */
    int quantum;              /* the current quantum size */
    int qset;                 /* the current array size */
    unsigned long size;       /* amount of data stored here */
    unsigned int access_key;  /* used by sculluid and scullpriv */
    struct semaphore sem;     /* mutual exclusion semaphore */
    struct cdev cdev;        /* Char device structure */
};



/* In main.c */

struct scull_dev *scull_devices;    /* allocated in scull_init_module */
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS;
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;


ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
    struct scull_dev *dev = filp->private_data;  /*  flip->private_data assigned in scull_open */
    struct scull_qset *dptr;
    int quantum = dev->quantum, qset = dev->qset;
    int itemsize = quantum * qset;
    int item;    /* item in linked list */
    int s_pos;    /* position in qset data array */
    int q_pos;    /* position in quantum */
    int rest;
    ssize_t retval = -ENOMEM; /* value used in "goto out" statements */

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;

    /* find listitem, qset index and offset in the quantum */
    item = (long)*f_pos / itemsize;
    rest = (long)*f_pos % itemsize;
    s_pos = rest / quantum;
    q_pos = rest % quantum;

    /* follow the list up to the right position */
    dptr = scull_follow(dev, item);
    if (dptr == NULL)
        goto out;
    if (!dptr->data) {
        dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
        if (!dptr->data)
            goto out;
        memset(dptr->data, 0, qset * sizeof(char *));
    }
    if (!dptr->data[s_pos]) {
        dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
        if (!dptr->data[s_pos])
            goto out;
    }
    /* write only up to the end of this quantum */
    if (count > quantum - q_pos)
        count = quantum - q_pos;

    if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
        retval = -EFAULT;
        goto out;
    }
    *f_pos += count;
    retval = count;

        /* update the size */
    if (dev->size < *f_pos)
        dev->size = *f_pos;

  out:
    up(&dev->sem);
    return retval;
}


int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev; /* device information */

    /* Question: Why was the lock not placed here? */

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev; /* for other methods */

    /* now trim to 0 the length of the device if open was write-only */
    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        scull_trim(dev); /* ignore errors */
        up(&dev->sem);
    }
    return 0;          /* success */
}


int scull_init_module(void)
{
    int result, i;
    dev_t dev = 0;

    /* assigns major and minor numbers (left out for brevity) */


    /* 
     * allocate the devices -- we can't have them static, as the number
     * can be specified at load time
     */
    scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
    if (!scull_devices) {
        result = -ENOMEM;
        goto fail;  /* isn't this redundant? */
    }
    memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

    /* Initialize each device. */
    for (i = 0; i < scull_nr_devs; i++) {
        scull_devices[i].quantum = scull_quantum;
        scull_devices[i].qset = scull_qset;
        init_MUTEX(&scull_devices[i].sem);
        scull_setup_cdev(&scull_devices[i], i); 
    }

    /* some other stuff (left out for brevity) */
    return 0; /* succeed */

  fail:         
    scull_cleanup_module();  /* left out for brevity */
    return result;
}


/*
 * Set up the char_dev structure for this device.
 */
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    int err, devno = MKDEV(scull_major, scull_minor + index);

    cdev_init(&dev->cdev, &scull_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &scull_fops;  /* isn't this redundant? */
    err = cdev_add (&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

【问题讨论】:

    标签: c concurrency linux-device-driver semaphore


    【解决方案1】:

    如果两个线程都能够访问内存中的所有数据,则可以将其视为“共享资源”*。它们不会在处理器之间共享的唯一资源是寄存器中的数据,这些数据在 C 中被抽象出来。

    您实际上不会考虑共享两个资源的原因有两个(即使它们实际上并不意味着两个线程理论上无法访问它们,一些噩梦般的代码有时可以绕过这些) .

    1. 只有一个线程可以/确实可以访问它。显然,如果只有一个线程访问一个变量,那么就不会存在竞争条件。这就是局部变量和单线程程序不需要锁定机制的原因。
    2. 值是恒定的。如果值永远不会改变,您将无法根据访问顺序获得不同的结果。

    你在这里展示的程序是不完整的,所以很难说,但是没有锁定访问的每个变量都必须满足这个程序线程安全的标准之一。

    有一些不明显的方法可以满足标准,例如变量是否为常量或仅在特定上下文中仅限于一个线程。

    您提供了两个未锁定行的示例。第一行。

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    

    这一行实际上并不访问任何变量,它只是计算包含cdev 的结构体的位置。不可能有竞争条件,因为没有其他人可以访问您的指针(尽管他们可以访问他们指向的内容),它们只能在函数内访问(他们指向的内容并非如此)。这符合标准 (1)。

    另一个例子是

    int quantum = dev->quantum, qset = dev->qset;
    

    如果没有上下文,这个有点难说,但我最好的猜测是假设dev-&gt;quantumdev-&gt;qset 在函数调用期间永远不会改变。这似乎得到了它们仅在scull_init_module 中被调用的事实的支持,而scull_init_module 应该只在一开始就被调用一次。我认为这符合标准 (2)。

    如果你知道其他线程不会尝试访问它,直到你完成某些其他原因(例如它们还不存在),你可能会在没有锁定的情况下更改共享变量

    简而言之,所有内存都是共享的,但有时您可以假装不存在。


    *在嵌入式系统中,每个处理器都有一定数量的 RAM,只有它可以使用,但这不是典型的情况。

    【讨论】:

    • 我没有将它添加到代码示例中,因为它会变得太长,但是在scull_ioctl中的main.c中有一行修改了scull_quantum(这是一个全局变量)。 scull_trim 反过来将 dev-&gt;quantum 设置为 scull_quantum 是什么。难道这两者的组合不会导致竞争条件吗?
    • 我不确定,但scull_trim 似乎只在scull_openscull_cleanup_module 中被调用。如果在调用 scull_write 时永远不会调用它们,则不会发生冲突。我什至没有看到任何地方调用了scull_open,所以很难说。无论哪种方式,代码似乎都在玩火,即使它是线程安全的。
    • 如何判断多个线程是否可以访问一条数据?是否有多个线程可以/不能访问的数据类型列表?为什么线程之间不能访问局部变量?
    • 没有灵丹妙药可以确定多个线程是否可以访问一条数据。您必须分析以查看是否存在并发访问的机会。对于局部变量,每个线程都有自己的堆栈,因此除非它们将指向其变量的指针存储在另一个线程可以访问的变量中(例如全局变量),否则其他线程无法访问它。可以共享的数据类型没有限制。
    • 堆空间也共享吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-19
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多