Какие данные в драйвере устройства могут быть разделены между процессами? - PullRequest
2 голосов
/ 08 марта 2019

В драйверах устройств, как мы можем определить, какие данные распределяются между процессами, а какие локальны для процесса?В Книге драйверов устройств Linux упоминается

Каждый раз, когда аппаратный или программный ресурс используется совместно за пределами одного потока выполнения, и существует вероятность того, что один поток может столкнуться с противоречивым представлениемэтого ресурса вы должны явно управлять доступом к этому ресурсу.

Но какие программные ресурсы могут быть разделены между потоками, а какие типы данных не могут совместно использоваться?Я знаю, что глобальные переменные обычно рассматриваются как разделяемая память, но какие еще виды нужно защищать?

Например, передаются ли типы struct inode и struct file в файловых операциях, как open,release, read, write и т. Д. Считаются общими?При вызове open внутри main.c , почему dev (в строке dev = container_of(inode->i_cdev, struct scull_dev, cdev);) не защищен блокировкой, если он указывает на запись struct scull_dev в глобальном массиве scull_devicesscull_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);
}

1 Ответ

2 голосов
/ 09 марта 2019

Все данные в памяти можно считать «общим ресурсом», если оба потока могут получить к нему доступ *. Единственный ресурс, которым они не будут делиться между процессорами, это данные в регистрах, которые абстрагированы в C.

Есть две причины, по которым вы бы практически не рассматривали возможность совместного использования двух ресурсов (даже если они на самом деле не означают, что два потока теоретически не могут получить к ним доступ, какой-то кошмарный код иногда может их обойти).

  1. Только один поток может / делает доступ к нему. Ясно, что если только один поток обращается к переменной, тогда не может быть условий гонки. По этой причине локальные переменные и однопоточные программы не нуждаются в механизмах блокировки.
  2. Значение постоянное. Вы не можете получить разные результаты в зависимости от порядка доступа, если значение никогда не изменится.

Программа, которую вы показали здесь, является неполной, поэтому трудно сказать, но каждая переменная, к которой обращаются без блокировки, должна соответствовать одному из критериев для этой программы, чтобы быть потокобезопасной.

Существуют некоторые неочевидные способы удовлетворения критериям, например, если переменная постоянна или ограничена одним потоком только в определенном контексте.

Вы привели два примера строк, которые не были заблокированы. Для первой строки.

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

Эта строка на самом деле не обращается к каким-либо переменным, она просто вычисляет, где должна быть структура, содержащая cdev. Не может быть условий гонки, потому что никто не имеет доступа к вашим указателям (хотя они имеют доступ к тому, на что они указывают), они доступны только внутри функции (это не соответствует тому, на что они указывают). Это соответствует критериям (1).

Другой пример:

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

Это немного сложнее сказать без контекста, но я думаю, что предполагается, что dev->quantum и dev->qset никогда не изменятся во время вызова функции. Это подтверждается тем фактом, что они вызываются только в scull_init_module, который должен вызываться только один раз в самом начале. Я считаю, что это соответствует критериям (2).

, который предлагает другой способ изменения общей переменной без блокировки, если вы знаете, что другие потоки не будут пытаться получить к ней доступ, пока вы не закончили по какой-то другой причине (например, они еще не существуют)

Короче говоря, вся память распределяется, но иногда вы можете сойти с рук, как будто это не так.


* Возможно, может существовать система, в которой каждый процессор имеет некоторый объем ОЗУ, который может использовать только он, но это не типичный случай.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...