понедельник, 18 октября 2010 г.

3.7. read and write

Работа продолжается... по адресу: http://notabenoid.com/book/11832 

The read and write methods both perform a similar task, that is, copying data from and to application code. Therefore, their prototypes are pretty similar, and it's worth introducing them at the same time:
ssize_t read(struct file *filp, char _ _user *buff,
    size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char _ _user *buff,
    size_t count, loff_t *offp);

For both methods, filp is the file pointer and count is the size of the requested data transfer. The buff argument points to the user buffer holding the data to be written or the empty buffer where the newly read data should be placed. Finally, offp is a pointer to a "long offset type" object that indicates the file position the user is accessing. The return value is a "signed size type"; its use is discussed later.
Let us repeat that the buff argument to the read and write methods is a user-space pointer. Therefore, it cannot be directly dereferenced by kernel code. There are a few reasons for this restriction:

Depending on which architecture your driver is running on, and how the kernel was configured, the user-space pointer may not be valid while running in kernel mode at all. There may be no mapping for that address, or it could point to some other, random data.

Even if the pointer does mean the same thing in kernel space, user-space memory is paged, and the memory in question might not be resident in RAM when the system call is made. Attempting to reference the user-space memory directly could generate a page fault, which is something that kernel code is not allowed to do. The result would be an "oops," which would result in the death of the process that made the system call.

The pointer in question has been supplied by a user program, which could be buggy or malicious. If your driver ever blindly dereferences a user-supplied pointer, it provides an open doorway allowing a user-space program to access or overwrite memory anywhere in the system. If you do not wish to be responsible for compromising the security of your users' systems, you cannot ever dereference a user-space pointer directly.
Obviously, your driver must be able to access the user-space buffer in order to get its job done. This access must always be performed by special, kernel-supplied functions, however, in order to be safe. We introduce some of those functions (which are defined in ) here, and the rest in the Section 6.1.4; they use some special, architecture-dependent magic to ensure that data transfers between kernel and user space happen in a safe and correct way.
The code for read and write in scull needs to copy a whole segment of data to or from the user address space. This capability is offered by the following kernel functions, which copy an arbitrary array of bytes and sit at the heart of most read and write implementations:
unsigned long copy_to_user(void _ _user *to, 
                           const void *from, 
                           unsigned long count);
unsigned long copy_from_user(void *to, 
                             const void _ _user *from, 
                             unsigned long count);

Although these functions behave like normal memcpy functions, a little extra care must be used when accessing user space from kernel code. The user pages being addressed might not be currently present in memory, and the virtual memory subsystem can put the process to sleep while the page is being transferred into place. This happens, for example, when the page must be retrieved from swap space. The net result for the driver writer is that any function that accesses user space must be reentrant, must be able to execute concurrently with other driver functions, and, in particular, must be in a position where it can legally sleep. We return to this subject in Chapter 5.
The role of the two functions is not limited to copying data to and from user-space: they also check whether the user space pointer is valid. If the pointer is invalid, no copy is performed; if an invalid address is encountered during the copy, on the other hand, only part of the data is copied. In both cases, the return value is the amount of memory still to be copied. The scull code looks for this error return, and returns -EFAULT to the user if it's not 0.
The topic of user-space access and invalid user space pointers is somewhat advanced and is discussed in Chapter 6. However, it's worth noting that if you don't need to check the user-space pointer you can invoke _ _copy_to_user and _ _copy_from_user instead. This is useful, for example, if you know you already checked the argument. Be careful, however; if, in fact, you do not check a user-space pointer that you pass to these functions, then you can create kernel crashes and/or security holes.
As far as the actual device methods are concerned, the task of the read method is to copy data from the device to user space (using copy_to_user), while the write method must copy data from user space to the device (using copy_from_user). Each read or write system call requests transfer of a specific number of bytes, but the driver is free to transfer less data—the exact rules are slightly different for reading and writing and are described later in this chapter.
Whatever the amount of data the methods transfer, they should generally update the file position at *offp to represent the current file position after successful completion of the system call. The kernel then propagates the file position change back into the file structure when appropriate. The pread and pwrite system calls have different semantics, however; they operate from a given file offset and do not change the file position as seen by any other system calls. These calls pass in a pointer to the user-supplied position, and discard the changes that your driver makes.
Figure 3-2 represents how a typical read implementation uses its arguments.
Figure 3-2. The arguments to read

_______________________________________________________________________________


_______________________________________________________________________________

Both the read and write methods return a negative value if an error occurs. A return value greater than or equal to 0, instead, tells the calling program how many bytes have been successfully transferred. If some data is transferred correctly and then an error happens, the return value must be the count of bytes successfully transferred, and the error does not get reported until the next time the function is called. Implementing this convention requires, of course, that your driver remember that the error has occurred so that it can return the error status in the future.
Although kernel functions return a negative number to signal an error, and the value of the number indicates the kind of error that occurred (as introduced in Chapter 2), programs that run in user space always see -1 as the error return value. They need to access the errno variable to find out what happened. The user-space behavior is dictated by the POSIX standard, but that standard does not make requirements on how the kernel operates internally.
3.7.1. The read Method
The return value for read is interpreted by the calling application program:

If the value equals the count argument passed to the read system call, the requested number of bytes has been transferred. This is the optimal case.

If the value is positive, but smaller than count, only part of the data has been transferred. This may happen for a number of reasons, depending on the device. Most often, the application program retries the read. For instance, if you read using the fread function, the library function reissues the system call until completion of the requested data transfer.

If the value is 0, end-of-file was reached (and no data was read).

A negative value means there was an error. The value specifies what the error was, according to . Typical values returned on error include -EINTR (interrupted system call) or -EFAULT (bad address).
What is missing from the preceding list is the case of "there is no data, but it may arrive later." In this case, the read system call should block. We'll deal with blocking input in Chapter 6.
The scull code takes advantage of these rules. In particular, it takes advantage of the partial-read rule. Each invocation of scull_read deals only with a single data quantum, without implementing a loop to gather all the data; this makes the code shorter and easier to read. If the reading program really wants more data, it reiterates the call. If the standard I/O library (i.e., fread) is used to read the device, the application won't even notice the quantization of the data transfer.
If the current read position is greater than the device size, the read method of scull returns 0 to signal that there's no data available (in other words, we're at end-of-file). This situation can happen if process A is reading the device while process B opens it for writing, thus truncating the device to a length of 0. Process A suddenly finds itself past end-of-file, and the next read call returns 0.
Here is the code for read (ignore the calls to down_interruptible and up for now; we will get to them in the next chapter):
ssize_t scull_read(struct file *filp, char _ _user *buf, size_t count,
                loff_t *f_pos)
{
    struct scull_dev *dev = filp->private_data; 
    struct scull_qset *dptr;    /* the first listitem */
    int quantum = dev->quantum, qset = dev->qset;
    int itemsize = quantum * qset; /* how many bytes in the listitem */
    int item, s_pos, q_pos, rest;
    ssize_t retval = 0;
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    if (*f_pos >= dev->size)
        goto out;
    if (*f_pos + count > dev->size)
        count = dev->size - *f_pos;
    /* 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 (defined elsewhere) */
    dptr = scull_follow(dev, item);
    if (dptr =  = NULL || !dptr->data || ! dptr->data[s_pos])
        goto out; /* don't fill holes */
    /* read only up to the end of this quantum */
    if (count > quantum - q_pos)
        count = quantum - q_pos;
    if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
        retval = -EFAULT;
        goto out;
    }
    *f_pos += count;
    retval = count;
  out:
    up(&dev->sem);
    return retval;
}


3.7.2. The write Method
write, like read, can transfer less data than was requested, according to the following rules for the return value:

If the value equals count, the requested number of bytes has been transferred.

If the value is positive, but smaller than count, only part of the data has been transferred. The program will most likely retry writing the rest of the data.

If the value is 0, nothing was written. This result is not an error, and there is no reason to return an error code. Once again, the standard library retries the call to write. We'll examine the exact meaning of this case in Chapter 6, where blocking write is introduced.

A negative value means an error occurred; as for read, valid error values are those defined in .
Unfortunately, there may still be misbehaving programs that issue an error message and abort when a partial transfer is performed. This happens because some programmers are accustomed to seeing write calls that either fail or succeed completely, which is actually what happens most of the time and should be supported by devices as well. This limitation in the scull implementation could be fixed, but we didn't want to complicate the code more than necessary.
The scull code for write deals with a single quantum at a time, as the read method does:
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;
    struct scull_qset *dptr;
    int quantum = dev->quantum, qset = dev->qset;
    int itemsize = quantum * qset;
    int item, s_pos, q_pos, 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;
}


3.7.3. readv and writev
Unix systems have long supported two system calls named readv and writev. These "vector" versions of read and write take an array of structures, each of which contains a pointer to a buffer and a length value. A readv call would then be expected to read the indicated amount into each buffer in turn. writev, instead, would gather together the contents of each buffer and put them out as a single write operation.
If your driver does not supply methods to handle the vector operations, readv and writev are implemented with multiple calls to your read and write methods. In many situations, however, greater efficiency is acheived by implementing readv and writev directly.
The prototypes for the vector operations are:
ssize_t (*readv) (struct file *filp, const struct iovec *iov, 
                  unsigned long count, loff_t *ppos);
ssize_t (*writev) (struct file *filp, const struct iovec *iov, 
                  unsigned long count, loff_t *ppos);

Here, the filp and ppos arguments are the same as for read and write. The iovec structure, defined in , looks like:
struct iovec
{
    void _  _user *iov_base;
    _ _kernel_size_t iov_len;
};

Each iovec describes one chunk of data to be transferred; it starts at iov_base (in user space) and is iov_len bytes long. The count parameter tells the method how many iovec structures there are. These structures are created by the application, but the kernel copies them into kernel space before calling the driver.
The simplest implementation of the vectored operations would be a straightforward loop that just passes the address and length out of each iovec to the driver's read or write function. Often, however, efficient and correct behavior requires that the driver do something smarter. For example, a writev on a tape drive should write the contents of all the iovec structures as a single record on the tape.
Many drivers, however, gain no benefit from implementing these methods themselves. Therefore, scull omits them. The kernel emulates them with read and write, and the end result is the same.

3.6. Использование памяти в scull

 Перевод с английского на русский

Использование памяти в scull

Перед знакомством с операциями read и write мы рассмотрим получше, как и почему scull выполняет выделение памяти. "Как" необходимо, чтобы глубоко понимать код, а "почему" демонстрирует варианты выбора, который должен делать автор драйвера, хотя scull, безусловно, не типичен, как устройство.

Этот раздел имеет дело только с политикой распределения памяти в scull и не показывает навыки аппаратного управления, необходимые для написания реальных драйверов. Эти навыки будут введены в Главах 9 и 10. Таким образом, вы можете пропустить этот раздел, если вы не заинтересованы в понимании внутренней работы ориентированного на память драйвера scull.

Область памяти, используемая scull, также называемая устройством, имеет переменную длину. Чем больше вы пишете, тем больше она растёт; укорачивание производится перезаписью устройства более коротким файлом.

Драйвер scull знакомит с двумя основными функциями, используемыми для управления памятью в ядре Linux. Вот эти функции, определённые в slab.h>:

void *kmalloc(size_t size, int flags);
void kfree(void *ptr);

Вызов kmalloc пытается выделить size байт памяти; возвращаемая величина - указатель на эту память или NULL, если выделение не удаётся. Аргумент flags используется, чтобы описать, как должна быть выделена память; мы изучим эти флаги подробно в Главе 8. Сейчас мы всегда используем GFP_KERNEL. Выделенная память должна быть освобождена kfree. Вы никогда не должны передавать kfree что-то, что не было получено от kmalloc. Однако, правомерно передать kfree указатель NULL.

kmalloc это не самый эффективный способ распределения больших областей памяти (смотрите Главу 8), поэтому сделанный для scull выбор не особенно умный. Исходный код для изящной реализации будет более трудным для восприятия и целью этого раздела является показать read и write, а не управление памятью. Вот почему код просто использует kmalloc и kfree, без пересортировки выделенных целых страниц, хотя такой подход был бы более эффективным.

С другой стороны, мы не хотим ограничивать размер области "устройства" по философским и практическим соображениям. Философски, это всегда плохая идея - поставить произвольные ограничения на управляемые объекты данных. Практически, scull может быть использован для временного поедания всей памяти вашей системы в целях выполнения тестов в условиях малого количества памяти. Выполнение таких тестов могло бы помочь вам понять внутренности системы. Вы можете использовать команду cp /dev/zero /dev/scull0, чтобы съесть всю реальную оперативную память с помощью scull, и вы можете использовать утилиту dd, чтобы выбрать, какой объём данных скопируется на устройство scull.
В scull каждое устройство представляет собой связный список указателей, каждый из которых указывает на структуру scull_dev. По умолчанию каждая такая структура может ссылаться на более четыре миллиона байт через массив промежуточных указателей. Реализованный исходник использует массив из 1000 указателей на области по 4000 байта. Мы называем каждую область памяти квантом (quantum), а массив (или его длину) - набором квантов (quantum set). Устройств scull и его области памяти показаны на Рисунке 3-1.

Рисунок 3-1. Схема устройства scull


Выбранные числа таковы, что запись одного байта в scull потребляет 8000 или 12000 тысяч байт памяти: 4000 для кванта и 4000 или 8000 для набора квантов (в зависимости от того, является ли на целевой платформе указатель 32-х разрядным или 64-х разрядным). Если, наоборот, вы пишете большое количество данных, накладные расходы на связный список не такие большие. Существует только один список элементов для каждых четырёх мегабайт данных и максимальный размер устройства ограничен объёмом памяти компьютера.

Выбор соответствующего значения для кванта и квантового набора - это вопрос политики, а не механизма, и их оптимальные размеры зависят от того, как используется устройство. Таким образом, драйвер scull не должен заставлять использовать какие-то определённые значения для размеров кванта и набора квантов. В scull пользователь может изменять значения размеров несколькими способами: путём изменения макросов SCULL_QUANTUM и SCULL_QSET в scull.h во время компиляции, устанавливая целочисленные значения scull_quantum и scull_qset во время загрузки модуля или изменяя текущее значение и значение по умолчанию с помощью ioctl во время выполнения.

Использование макроса и целой величины позволяют делать конфигурацию и во время компиляции и во время загрузки и напоминают выбор старшего номера. Мы используем эту технику для любого, произвольного или связанного с политикой, значения в драйвере.

Остался только вопрос, как были выбраны номера по умолчанию. В данном случае проблема состоит в нахождении лучшего соотношения между потерей памяти в результате полузаполненного кванта и квантового набора и накладных расходов на выделение, освобождения, и указатель связывания, что происходит, если кванты и наборы малы. Кроме того, должен быть принят во внимание внутренний дизайн kmalloc. (Однако, мы не будем исследовать это теперь; внутренности kmalloc рассматриваются в Главе 8.) Выбор чисел по умолчанию делается из предположения, что во время тестирования в scull могут быть записаны большие объёмы данных, хотя при обычном использовании устройства, скорее всего, будут передаваться только несколько килобайт данных.

Мы уже видели структуру scull_dev, которая является внутренним представлением нашего устройства. Это поля структуры quantum и qset, содержащие квант и размер квантового набора устройства, соответственно. Фактические данные, однако, отслеживаются другой структурой, которую мы назвали структурой scull_qset:

struct scull_qset {
void **data;
struct scull_qset *next;
};

Следующий фрагмент кода показывает на практике, как структуры scull_dev и scull_qset используются для хранения данных. Функция scull_trim отвечает за освобождение всей области данных и вызывается из scull_open, когда файл открывается для записи. Это просто прогулка по списку и освобождение любого кванта и набора квантов при их нахождении.

int scull_trim(struct scull_dev *dev)
{
    struct scull_qset *next, *dptr;
    int qset = dev->qset; /* "dev" является не-null */
    int i;
    for (dptr = dev->data; dptr; dptr = next) { /* все элементы списка */
        if (dptr->data) {
            for (i = 0; i < qset; i++)
                kfree(dptr->data[i]);
            kfree(dptr->data);
            dptr->data = NULL;
        }
        next = dptr->next;
        kfree(dptr);
    }
    dev->size = 0;
    dev->quantum = scull_quantum;
    dev->qset = scull_qset;
    dev->data = NULL;
    return 0;
}

scull_trim также используется в функции очистки модуля, чтобы вернуть системе память, используемую scull.

Переведено на сайте www.notabenoid.com
http://notabenoid.com/book/11832/38274
Внимание! Этот перевод, возможно, ещё не готов,
так как модераторы установили для него статус
"перевод редактируется"

3.6. scull's Memory Usage

Before introducing the read and write operations, we'd better look at how and why scull performs memory allocation. "How" is needed to thoroughly understand the code, and "why" demonstrates the kind of choices a driver writer needs to make, although scull is definitely not typical as a device.
This section deals only with the memory allocation policy in scull and doesn't show the hardware management skills you need to write real drivers. These skills are introduced in Chapter 9 and Chapter 10. Therefore, you can skip this section if you're not interested in understanding the inner workings of the memory-oriented scull driver.
The region of memory used by scull, also called a device, is variable in length. The more you write, the more it grows; trimming is performed by overwriting the device with a shorter file.
The scull driver introduces two core functions used to manage memory in the Linux kernel. These functions, defined in , are:
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);

A call to kmalloc attempts to allocate size bytes of memory; the return value is a pointer to that memory or NULL if the allocation fails. The flags argument is used to describe how the memory should be allocated; we examine those flags in detail in Chapter 8. For now, we always use GFP_KERNEL. Allocated memory should be freed with kfree. You should never pass anything to kfree that was not obtained from kmalloc. It is, however, legal to pass a NULL pointer to kfree.
kmalloc is not the most efficient way to allocate large areas of memory (see Chapter 8), so the implementation chosen for scull is not a particularly smart one. The source code for a smart implementation would be more difficult to read, and the aim of this section is to show read and write, not memory management. That's why the code just uses kmalloc and kfree without resorting to allocation of whole pages, although that approach would be more efficient.
On the flip side, we didn't want to limit the size of the "device" area, for both a philosophical reason and a practical one. Philosophically, it's always a bad idea to put arbitrary limits on data items being managed. Practically, scull can be used to temporarily eat up your system's memory in order to run tests under low-memory conditions. Running such tests might help you understand the system's internals. You can use the command cp /dev/zero /dev/scull0 to eat all the real RAM with scull, and you can use the dd utility to choose how much data is copied to the scull device.
In scull, each device is a linked list of pointers, each of which points to a scull_dev structure. Each such structure can refer, by default, to at most four million bytes, through an array of intermediate pointers. The released source uses an array of 1000 pointers to areas of 4000 bytes. We call each memory area a quantum and the array (or its length) a quantum set . A scull device and its memory areas are shown in Figure 3-1.
Figure 3-1. The layout of a scull device



___________________________________________________________________________________

___________________________________________________________________________________
The chosen numbers are such that writing a single byte in scull consumes 8000 or 12,000 thousand bytes of memory: 4000 for the quantum and 4000 or 8000 for the quantum set (according to whether a pointer is represented in 32 bits or 64 bits on the target platform). If, instead, you write a huge amount of data, the overhead of the linked list is not too bad. There is only one list element for every four megabytes of data, and the maximum size of the device is limited by the computer's memory size.
Choosing the appropriate values for the quantum and the quantum set is a question of policy, rather than mechanism, and the optimal sizes depend on how the device is used. Thus, the scull driver should not force the use of any particular values for the quantum and quantum set sizes. In scull, the user can change the values in charge in several ways: by changing the macros SCULL_QUANTUM and SCULL_QSET in scull.h at compile time, by setting the integer values scull_quantum and scull_qset at module load time, or by changing both the current and default values using ioctl at runtime.
Using a macro and an integer value to allow both compile-time and load-time configuration is reminiscent of how the major number is selected. We use this technique for whatever value in the driver is arbitrary or related to policy.
The only question left is how the default numbers have been chosen. In this particular case, the problem is finding the best balance between the waste of memory resulting from half-filled quanta and quantum sets and the overhead of allocation, deallocation, and pointer chaining that occurs if quanta and sets are small. Additionally, the internal design of kmalloc should be taken into account. (We won't pursue the point now, though; the innards of kmalloc are explored in Chapter 8.) The choice of default numbers comes from the assumption that massive amounts of data are likely to be written to scull while testing it, although normal use of the device will most likely transfer just a few kilobytes of data.
We have already seen the scull_dev structure that represents our device internally. That structure's quantum and qset fields hold the device's quantum and quantum set sizes, respectively. The actual data, however, is tracked by a different structure, which we call struct scull_qset :
struct scull_qset {
    void **data;
    struct scull_qset *next;
};

The next code fragment shows in practice how struct scull_dev and struct scull_qset are used to hold data. The function scull_trim is in charge of freeing the whole data area and is invoked by scull_open when the file is opened for writing. It simply walks through the list and frees any quantum and quantum set it finds.
int scull_trim(struct scull_dev *dev)
{
    struct scull_qset *next, *dptr;
    int qset = dev->qset;   /* "dev" is not-null */
    int i;
    for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
        if (dptr->data) {
            for (i = 0; i < qset; i++)
                kfree(dptr->data[i]);
            kfree(dptr->data);
            dptr->data = NULL;
        }
        next = dptr->next;
        kfree(dptr);
    }
    dev->size = 0;
    dev->quantum = scull_quantum;
    dev->qset = scull_qset;
    dev->data = NULL;
    return 0;
}

scull_trim is also used in the module cleanup function to return memory used by scull to the system.

3.5. Методы open и release

Перевод с английского на русский

Методы open и release

Теперь, после быстрого взгляда на поля, мы начнём использовать их в реальных функциях scull.

Метод open

Чтобы сделать любые инициализации при подготовке к поздним операциям для драйвера предусмотрен метод open. В большинстве драйверов open должен выполнять следующие задачи:

Проверку зависимых от устройства ошибок (таких, как "устройство не готово" или аналогичных проблем с оборудованием);

Проинициализировать устройство, если оно открывается в первый раз;
Обновить в случае необходимости указатель f_op;

Создать и заполнить любые структуры данных для размещения в filp->private_data; Первым делом, однако, обычно определяется, какое устройство открывается в настоящий момент. Вспомните прототип метода open:

int (*open)(struct inode *inode, struct file *filp);

Аргумент inode имеет информацию, которая нам необходима, в форме его поля i_cdev, в котором содержится структура cdev, которую мы создали раньше. Единственной проблемой является то, что обычно мы не хотим саму структуру cdev, мы хотим структуру scull_dev, которая содержит эту структуру cdev. Язык Си позволяет программистам использовать всевозможные трюки, чтобы выполнить такое преобразование, однако, программирование таких трюков чревато ошибками и приводит к коду, который труден для чтения и понимания другими. К счастью, в данном случае программисты ядра сделали сложную работу за нас, в виде макроса container_of, определённого в kernel.h>:

container_of(pointer, container_type, container_field);

Этот макрос получает указатель на поле типа container_field, находящееся внутри структуры типа container_type, и возвращает указатель на эту структуру. В scull_open этот макрос используется, чтобы найти соответствующую структуру устройства:

struct scull_dev *dev; /* информация об устройстве */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* для других методов */

Как только он нашел структуру scull_dev, для облегчения доступа в будущем scull сохраняет указатель на неё в поле private_data структуры file.

Другим способом идентификации открываемого устройства является поиск младшего номера, сохранённого в структуре inode. Если вы регистрируете устройство с register_chrdev, вы должны использовать эту технику.

Обязательно используйте iminor, чтобы получить младший номер из структуры inode, а также убедитесь, что он соответствует устройству, которое ваш драйвер действительно готов обслужить.

(Немного упрощённый) код для scull_open:

int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev; /* информация об устройстве */

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev; /* для других методов */

    /* теперь установим в 0 длину устройства, если открытие было только для записи */
    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
        scull_trim(dev); /* игнорирование ошибок */
    }
    return 0; /* успешно */
}

Код выглядит довольно разбросанным, поскольку не делает какого-либо обращения к определённому устройству, когда вызывается open. В этом нет необходимости, потому что устройство scull по дизайну является глобальным и стойким. В частности, нет никаких действий, таких, как "инициализации устройств при первом открытии", поэтому мы не храним счётчик открытий для устройств scull.

Единственная реальная операция, выполняемая на устройстве, это усечение его длины до 0, когда устройство открывается для записи. Это выполняется, так как по дизайну перезапись устройства scull более коротким файлом образует более короткую область данных устройства. Это подобно тому, как открытие обычного файла для записи обрезает его до нулевой длины. Операция ничего не делает, если устройство открыто для чтения.

Мы увидим позже, как работает настоящая инициализация, когда посмотрим на код других вариантов scull.

Метод  release

Роль метода release обратна open. Иногда вы обнаружите, что реализация метода называется device_close вместо device_release. В любом случае, метод устройства должен выполнять следующие задачи:

Освободить всё, что open разместил в filp->private_data;

Выключить устройство при последнем закрытии;

Базовая форма scull не работает с аппаратурой, которую надо выключать, так что код необходим минимальный:

(* Другие разновидности устройства закрываются другими функциями, потому что scull_open заменяет различные filp->f_op для каждого устройства. Мы будем обсуждать их при знакомстве с каждой разновидностью.)

int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}

Вы можете быть удивлены, что происходит, когда файл устройства закрывается большее число раз, чем открывается. В конце концов, системные вызовы dup и fork создают копии открытых файлов без вызова open; каждая из этих копий затем закрывается при завершении программы. Например, большинство программ не открывают свой файл stdin (или устройство), но все они в конечном итоге его закрывают. Как драйвер узнает, когда открытый файл устройства действительно был закрыт?
Ответ прост: не каждый системный вызов close вызывает метод release. Только вызовы, которые действительно освобождают структуру данных устройства, запускают этот метод, отсюда его название. Ядро хранит счётчик, сколько раз используется структура file. Ни fork, ни dup не создают новую структуру file (только open делает это); они просто увеличивают счётчик в существующей структуре. Системный вызов call запускает метод release только тогда, когда счётчик для структуры file падает до 0, что происходит, когда структура уничтожена. Эта взаимосвязь между методом release и системным вызовом close гарантирует, что ваш драйвер видит только один вызов release для каждого open.

Обратите внимание, что метод flush вызывается каждый раз, когда приложение вызывает close. Однако, очень немногие драйверы реализуют flush, потому что обычно нечего делать во время закрытия, пока не вызван release.

Как вы можете себе представить, предыдущее обсуждение применяется даже тогда, когда приложение завершается без явного закрытия открытых файлов: ядро автоматически закрывает все файлы во время завершения процесса внутренне с помощью системного вызова close.

Переведено на сайте www.notabenoid.com
http://notabenoid.com/book/11832/38273
Внимание! Этот перевод, возможно, ещё не готов,
так как модераторы установили для него статус
"перевод редактируется"

3.5. open and release

3.5. open and release
Now that we've taken a quick look at the fields, we start using them in real scull functions.
3.5.1. The open Method
The open method is provided for a driver to do any initialization in preparation for later operations. In most drivers, open should perform the following tasks:

Check for device-specific errors (such as device-not-ready or similar hardware problems)

Initialize the device if it is being opened for the first time

Update the f_op pointer, if necessary

Allocate and fill any data structure to be put in filp->private_data
The first order of business, however, is usually to identify which device is being opened. Remember that the prototype for the open method is:
int (*open)(struct inode *inode, struct file *filp);

The inode argument has the information we need in the form of its i_cdev field, which contains the cdev structure we set up before. The only problem is that we do not normally want the cdev structure itself, we want the scull_dev structure that contains that cdev structure. The C language lets programmers play all sorts of tricks to make that kind of conversion; programming such tricks is error prone, however, and leads to code that is difficult for others to read and understand. Fortunately, in this case, the kernel hackers have done the tricky stuff for us, in the form of the container_of macro, defined in :
container_of(pointer, container_type, container_field);

This macro takes a pointer to a field of type container_field, within a structure of type container_type, and returns a pointer to the containing structure. In scull_open, this macro is used to find the appropriate device structure:
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */

Once it has found the scull_dev structure, scull stores a pointer to it in the private_data field of the file structure for easier access in the future.
The other way to identify the device being opened is to look at the minor number stored in the inode structure. If you register your device with register_chrdev, you must use this technique. Be sure to use iminor to obtain the minor number from the inode structure, and make sure that it corresponds to a device that your driver is actually prepared to handle.
The (slightly simplified) code for scull_open is:
int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev; /* device information */
    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) {
        scull_trim(dev); /* ignore errors */
    }
    return 0;          /* success */
}

The code looks pretty sparse, because it doesn't do any particular device handling when open is called. It doesn't need to, because the scull device is global and persistent by design. Specifically, there's no action such as "initializing the device on first open," because we don't keep an open count for sculls.
The only real operation performed on the device is truncating it to a length of 0 when the device is opened for writing. This is performed because, by design, overwriting a scull device with a shorter file results in a shorter device data area. This is similar to the way opening a regular file for writing truncates it to zero length. The operation does nothing if the device is opened for reading.
We'll see later how a real initialization works when we look at the code for the other scull personalities.
3.5.2. The release Method
The role of the release method is the reverse of open. Sometimes you'll find that the method implementation is called device_close instead of device_release. Either way, the device method should perform the following tasks:

Deallocate anything that open allocated in filp->private_data

Shut down the device on last close
The basic form of scull has no hardware to shut down, so the code required is minimal:[7]
[7] The other flavors of the device are closed by different functions because scull_open substituted a different filp->f_op for each device. We'll discuss these as we introduce each flavor.

int scull_release(struct inode *inode, struct file *filp)
{
    return 0;
}

You may be wondering what happens when a device file is closed more times than it is opened. After all, the dup and fork system calls create copies of open files without calling open; each of those copies is then closed at program termination. For example, most programs don't open their stdin file (or device), but all of them end up closing it. How does a driver know when an open device file has really been closed?
The answer is simple: not every close system call causes the release method to be invoked. Only the calls that actually release the device data structure invoke the method—hence its name. The kernel keeps a counter of how many times a file structure is being used. Neither fork nor dup creates a new file structure (only open does that); they just increment the counter in the existing structure. The close system call executes the release method only when the counter for the file structure drops to 0, which happens when the structure is destroyed. This relationship between the release method and the close system call guarantees that your driver sees only one release call for each open.
Note that the flush method is called every time an application calls close. However, very few drivers implement flush, because usually there's nothing to perform at close time unless release is involved.
As you may imagine, the previous discussion applies even when the application terminates without explicitly closing its open files: the kernel automatically closes any file at process exit time by internally using the close system call.

3.4. Регистрация символьного устройства

Перевод с английского на русский

Регистрация символьных устройств

Как уже упоминалось, ядро использует структуры типа cdev для внутреннего представления символьных устройств. Перед тем, как ядро вызовет операции устройства, вы должны выделить и зарегистрировать одну или больше этих структур. Чтобы сделать это, ваш код должен подключить cdev.h>, где определены структура и связанные с ней вспомогательные функции.

(* Существует старый механизм, который избегает применения структур cdev (которые мы обсудим в разделе "Старый способ"). Однако, новый код должен использовать новую технику.)

Есть два способа создания и инициализации каждой из этих структур. Если вы хотите получить автономную структуру cdev во время выполнения, вы можете сделать это таким кодом:

struct cdev *my_cdev = cdev_alloc( );
my_cdev->ops = &my_fops;

Однако, скорее всего, вы захотите вставлять свои собственные устройство-зависимые структуры cdev; это то, что делает scull. В этом случае вы должны проинициализировать те структуры, что уже созданы с помощью:
void cdev_init(struct cdev *cdev, struct file_operations *fops);

В любом случае есть ещё одно поле структуры cdev, которое необходимо проинициализировать. Так же как в структуре file_operations, структура cdev имеет поле owner (владелец), которое должно быть установлено в THIS_MODULE.

После создания структуры cdev, последний шаг - сказать об этом ядру вызовом:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

Здесь, dev является структурой cdev, num - первый номер устройства, на который реагирует данное устройство, и count - количество номеров устройств, которые должны быть связаны с устройством. Часто count единица, но бывают ситуации, когда имеет смысл иметь более одного номера устройства, соответствующего определённому устройству. Рассмотрим, например, драйвер ленточного SCSI накопителя, который позволяет пользовательскому пространству выбирать режимы работы (например, плотность) путём назначения нескольких младших номеров для каждого физического устройства.

При использовании cdev_add необходимо иметь в виду несколько важных вещей. Первым является то, что этот вызов может потерпеть неудачу. Если он вернул код ошибки, ваше устройство не было добавлено в систему. Однако, это почти всегда удаётся, что вызывает другой момент: как только cdev_add возвращается, устройство становится "живым" и его операции могут быть вызваны ядром. Вы не должны вызывать cdev_add, пока драйвер не готов полностью проводить операции на устройстве.

Чтобы удалить символьное устройство из системы, вызовите:

void cdev_del(struct cdev *dev);

Очевидно, что вы не должны обращаться к структуре cdev после передачи её в cdev_del.

Регистрация устройства в scull

Внутри scull представляет каждое устройство структурой типа scull_dev. Эта структура определена как:

struct scull_dev {
    struct scull_qset *data; /* Указатель, установленный на первый квант */
    int quantum;             /* размер текущего кванта */
    int qset;                /* размер текущего массива */
    unsigned long size;      /* количество данных, хранимых здесь */
    unsigned int access_key; /* используется sculluid и scullpriv */
    struct semaphore sem;    /* семафор взаимного исключения */
    struct cdev cdev;        /* структура символьного устройства */
};

Мы обсуждаем различные поля этой структуры, когда приходим к ним, но сейчас мы обращаем внимание на cdev, структуру типа cdev, которая является интерфейсами нашего устройства к ядру. Эта структура должна быть проинициализирована и добавлена в систему, как описано выше; код scull, который решает эту задачу:

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;
    err = cdev_add (&dev->cdev, devno, 1);
    /* Терпите неудачу изящно, если это необходимо */
    if (err)
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

Поскольку структуры cdev внедрены в структуру scull_dev, cdev_init должна быть вызвана, чтобы выполнить инициализацию этой структуры.

Старый способ

Если вы пороетесь во многих драйверных кодах в ядре версии 2.6, то сможете заметить, что довольно много символьных драйверов не используют интерфейс cdev, который мы только что описали. То, что вы видите, является старым кодом, который ещё не был обновлён до интерфейса версии 2.6. Так как этот код работает, это обновление может не произойти в течение длительного времени. Для полноты картины мы описываем старый интерфейс регистрации символьных устройств, но новый код не должен его использовать; этот механизм, вероятно, уйдёт в будущем ядре.

Классический способ зарегистрировать символьный драйвер устройства:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

Здесь major является запрашиваемым старшим номером, name - это имя драйвера (оно появляется в /proc/devices) и fops является структурой по умолчанию file_operations. Вызов register_chrdev регистрирует младшие номера 0 - 255 для данного старшего и устанавливает для каждого структуру по умолчанию cdev. Драйверы, использующие этот интерфейс, должны быть готовы для обработки вызовов open по всем 256 младшим номерам (независимо от того, соответствуют ли они реальным устройствам или нет) и они не могут использовать старший или младший номера больше 255.

Если вы используете register_chrdev, правильная функция удаления устройств(а) из системы:

int unregister_chrdev(unsigned int major, const char *name);

major и name должны быть такими же, как передаваемые в register_chrdev, или вызов будет ошибочным.

Переведено на сайте www.notabenoid.com
http://notabenoid.com/book/11832/38272
Внимание! Этот перевод, возможно, ещё не готов,
так как модераторы установили для него статус
"перевод редактируется"

3.4. Char Device Registration

As we mentioned, the kernel uses structures of type struct cdev to represent char devices internally. Before the kernel invokes your device's operations, you must allocate and register one or more of these structures.[6] To do so, your code should include , where the structure and its associated helper functions are defined.
[6] There is an older mechanism that avoids the use of cdev structures (which we discuss in Section 3.4.2). New code should use the newer technique, however.
There are two ways of allocating and initializing one of these structures. If you wish to obtain a standalone cdev structure at runtime, you may do so with code such as:
struct cdev *my_cdev = cdev_alloc(  );
my_cdev->ops = &my_fops;

Chances are, however, that you will want to embed the cdev structure within a device-specific structure of your own; that is what scull does. In that case, you should initialize the structure that you have already allocated with:
void cdev_init(struct cdev *cdev, struct file_operations *fops);

Either way, there is one other struct cdev field that you need to initialize. Like the file_operations structure, struct cdev has an owner field that should be set to THIS_MODULE.
Once the cdev structure is set up, the final step is to tell the kernel about it with a call to:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);

Here, dev is the cdev structure, num is the first device number to which this device responds, and count is the number of device numbers that should be associated with the device. Often count is one, but there are situations where it makes sense to have more than one device number correspond to a specific device. Consider, for example, the SCSI tape driver, which allows user space to select operating modes (such as density) by assigning multiple minor numbers to each physical device.
There are a couple of important things to keep in mind when using cdev_add. The first is that this call can fail. If it returns a negative error code, your device has not been added to the system. It almost always succeeds, however, and that brings up the other point: as soon as cdev_add returns, your device is "live" and its operations can be called by the kernel. You should not call cdev_add until your driver is completely ready to handle operations on the device.
To remove a char device from the system, call:
void cdev_del(struct cdev *dev);

Clearly, you should not access the cdev structure after passing it to cdev_del.
3.4.1. Device Registration in scull
Internally, scull represents each device with a structure of type struct scull_dev. This structure is defined as:
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      */
};

We discuss the various fields in this structure as we come to them, but for now, we call attention to cdev, the struct cdev that interfaces our device to the kernel. This structure must be initialized and added to the system as described above; the scull code that handles this task is:
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;
    err = cdev_add (&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
    printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

Since the cdev structure is embedded within struct scull_dev, cdev_init must be called to perform the initialization of that structure.
3.4.2. The Older Way
If you dig through much driver code in the 2.6 kernel, you may notice that quite a few char drivers do not use the cdev interface that we have just described. What you are seeing is older code that has not yet been upgraded to the 2.6 interface. Since that code works as it is, this upgrade may not happen for a long time. For completeness, we describe the older char device registration interface, but new code should not use it; this mechanism will likely go away in a future kernel.
The classic way to register a char device driver is with:
int register_chrdev(unsigned int major, const char *name,
                    struct file_operations *fops);

Here, major is the major number of interest, name is the name of the driver (it appears in /proc/devices), and fops is the default file_operations structure. A call to register_chrdev registers minor numbers 0-255 for the given major, and sets up a default cdev structure for each. Drivers using this interface must be prepared to handle open calls on all 256 minor numbers (whether they correspond to real devices or not), and they cannot use major or minor numbers greater than 255.
If you use register_chrdev, the proper function to remove your device(s) from the system is:
int unregister_chrdev(unsigned int major, const char *name);

major and name must be the same as those passed to register_chrdev, or the call will fail.

3.3. Несколько важных структур данных

Перевод с английского на русский

Некоторые важные структуры данных

Как вы можете себе представить, регистрация номера устройства является лишь первой из многих задач, которые должен выполнять код драйвера. В скором времени мы рассмотрим другие важные компоненты драйвера, но одно отступление необходимо в первую очередь. Большинство основных операций драйвера включает три важных структуры данных ядра, называемых file_operations, file и inode. Требуется базовое знакомство с этими структурами, чтобы быть способным делать много всего интересного, так что сейчас мы бросим быстрый взгляд на каждую из них, прежде чем углубляться в подробности того, как осуществляются основные операции драйвера.

Файловые операции

До этого мы зарезервировали некоторые номера устройств для нашего использования, но мы ещё не подключили никаких своих драйверных операций на эти номера. Структура file_operations это то, как символьный драйвер устанавливает эту связь. Структура определена в fs.h>, это коллекция указателей на функции. Каждое открытие файла (представленное внутри структуры file, которую мы рассмотрим в ближайшее время) связано с собственным набором функций (включая поле, названное f_op, которое указывает на структуру file_operations). Операции в основном отвечают за осуществление системных вызовов и, таким образом, названы open (открыть), read (читать) и так далее. Мы можем рассматривать файл как "объект" и функции, работающие с ним, будут его "методами", используя терминологию объектно-ориентированного программирования для обозначения действий, декларируемых исполняющим их объектом. Это первый признак объектно-ориентированного программирования, видимого нами в ядре Linux, и мы увидим больше в последующих главах.

Обычно, структура file_operations или указатель на неё называется fops (или как-то похоже). Каждое поле структуры должно указывать на функцию в драйвере, которая реализует соответствующие операции, или оставить NULL для неподдерживаемых операций. Точное поведение ядра, когда задан указатель NULL, отличается для каждой функции, список показан позже в этом разделе.

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

Прочитав список методов file_operations, вы заметите, что некоторое число параметров включает строку __user. Эта аннотация является одной из форм документации, отмечая, что указатель является адресом пространства пользователя, который не может быть разыменовываться непосредственно. Для нормальной компиляции __user не имеет никакого эффекта, но может быть использована внешним проверяющим программным обеспечением, чтобы найти злоупотребление адресами пользовательского пространства.
Остальная часть главы, после описания некоторых других важных структур данных, объясняет роль наиболее важных операций и предлагает подсказки, предостережения и реальные примеры кода. Мы отложим обсуждение наиболее сложных операций для последующих глав, потому что мы ещё совсем не готовы углубиться в такие темы, как управление памятью, блокирующие операции, а также асинхронные уведомления.

struct module *owner

Первое поле file_operations вовсе не операция, это указатель на модуль, который "владеет" структурой. Это поле используется для предотвращения выгрузки модуля во время использования его операций. Почти всегда оно просто инициализируется THIS_MODULE, макрос определён в module.h>.

loff_t (*llseek) (struct file *, loff_t, int);

Метод llseek используется для изменения текущей позиции чтения/записи в файле, а новая позиция передаётся как (положительное) возвращаемое значение. Параметром loff_t является "длинное смещение" и он, по крайней мере, 64 бита даже на 32-х разрядных платформах. Об ошибках сигнализируют отрицательные возвращаемые значения. Если указатель на эту функцию NULL, вызовы поиска позиции будут изменять счётчик позиции в структуре file (описанной в разделе "Структура file") потенциально непредсказуемым образом.

ssize_t (*read) (struct file *, char _ _user *, size_t, loff_t *);

Используется для получения данных от устройства. Указатель NULL в этой позиции заставляет системный вызов read вернуться с ошибкой -EINVAL (“Invalid argument”, "Недопустимый аргумент"). Неотрицательное возвращаемое значение представляет собой число успешно считанных байт (возвращаемое значение является типом "размером со знаком", обычно родным (нативным) целочисленным типом для целевой платформы).

ssize_t (*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);

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

ssize_t (*write) (struct file *, const char _ _user *, size_t, loff_t *);

Отправляет данные на устройство. Если NULL, -EINVAL возвращается в программу, сделавшую системный вызов write. Возвращаемое значение, если оно неотрицательное, представляет собой число успешно записанных байт.

ssize_t (*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *);

Начинает асинхронную операции записи на устройство.

int (*readdir) (struct file *, void *, filldir_t);

Это поле должно быть NULL для файлов устройства; оно используется для чтения каталогов и используется только для файловых систем.

unsigned int (*poll) (struct file *, struct poll_table_struct *);

Метод poll (опрос) является внутренним для трёх системных вызовов: poll, epoll и select, все они используются для запросов чтения или записи, чтобы заблокировать один или несколько файловых дескрипторов. Метод poll должен вернуть битовую маску, показывающую, возможны ли неблокирующие чтение или запись, и, возможно, предоставить ядру информацию, которая может быть использована вызывающим процессом для ожидания, когда ввод/вывод станет возможным. Если драйвер оставляет метод poll NULL, предполагается, что устройство читаемо и записываемо без блокировки.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

Системный вызов ioctl (управление вводом-выводом) открывает путь к выполнению зависящих от устройства команд (например, форматирование дорожки гибкого диска, которая и не чтение, и не запись). Кроме того, несколько команд ioctl распознаются ядром без ссылки на таблицу fops. Если устройство не обеспечивает метод ioctl, системный вызов возвращает ошибку на любой запрос, который не является предопределенным (-ENOTTY, “No such ioctl for device”, "Нет такого управления вводом-выводом для устройства").

int (*mmap) (struct file *, struct vm_area_struct *);

mmap (отобразить в память) используется, чтобы запросить отображение памяти устройства на адресное пространство процесса. Если этот метод NULL, системный вызов mmap возвращает -ENODEV.

int (*open) (struct inode *, struct file *);

Хотя это всегда первая операция, выполняющаяся с файлом устройства, драйверу не требуется декларировать соответствующий метод. Если этот параметр NULL, открытие устройства всегда успешно, но ваш драйвер не уведомляется.

int (*flush) (struct file *);

Операция flush (сбросить на диск) вызывается, когда процесс закрывает свою копию файла дескриптора устройства; она должна выполнить (и ожидает это) любую ожидающую выполнения операцию на устройстве. Не следует путать это с операцией fsync, запрашиваемой программами пользователя. В настоящее время flush используется в очень редких драйверах; например, драйвер ленточного накопителя SCSI использует её, чтобы гарантировать, что все данные записаны на ленту, прежде чем устройство закроется. Если flush NULL, ядро просто игнорирует запрос пользовательского приложения.

int (*release) (struct inode *, struct file *);

Эта операция (отключение) вызывается, когда файловая структура освобождается. Как и open, release может быть NULL.

(* Обратите внимание, что release не вызывается каждый раз, когда процесс вызывает close. Когда файловая структура используется несколькими процессами (например, после fork или dup), release не будет вызван, пока не будут закрыты все копии. Если вам необходимо при завершении копирования сбросить на диск ожидающие данные, вы должны реализовать метод flush.)

int (*fsync) (struct file *, struct dentry *, int);

Этот метод является внутренним системным вызовом fsync, который пользователь вызывает для сброса на диск любых ожидающих данных. Если этот указатель NULL, системный вызов возвращает -EINVAL.

int (*aio_fsync)(struct kiocb *, int);

Это асинхронная версия метода fsync.

int (*fasync) (int, struct file *, int);

Эта операция используется, чтобы уведомить устройство, изменив его флаг FASYNC. Асинхронные уведомления - сложная тема и она описана в Главе 6. Поле может быть NULL, если драйвер не поддерживает асинхронные уведомления.

int (*lock) (struct file *, int, struct file_lock *);

Метод lock (блокировка) используется для реализации блокировки файла; блокировка является неотъемлемой функцией для обычных файлов, но почти никогда не реализуется драйверами устройств.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

Эти методы осуществляют разбросанные (scatter/gather, разборка/сборка) операции чтения и записи. Приложениям иногда необходимо сделать одну операцию чтения или записи с участием нескольких областей памяти; эти системные вызовы позволяют им сделать это без необходимости дополнительных операций копирования данных. Если эти указатели на функции остаются NULL, вместо них вызываются методы read и write (возможно, несколько раз).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);

Этот метод реализует читающую сторону системного вызова sendfile (послать файл), который перемещает данные из одного файлового дескриптора на другой с минимумом копирования. Используется, например, веб-сервером, которому необходимо отправить содержимое файла из сети наружу. Драйверы устройств обычно оставляют sendfile NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

sendpage (послать страницу) - это другая половина sendfile, она вызывается ядром для передачи данных, одну страницу за один раз, в соответствующий файл. Драйверы устройств, как правило, не реализуют sendpage.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

Цель этого метода - найти подходящее места в адресном пространстве процесса, чтобы отобразить в сегменте памяти нижележащее устройство. Эта задача обычно выполняется кодом управления памятью; этот метод существует, чтобы разрешить драйверу реализовать любое согласование требований, которое может иметь специфичное устройство. Большинство драйверов может оставить этот метод NULL.

int (*check_flags)(int)

Этот метод позволяет модулю проверить флаги, передаваемые вызову fcntl(F_SETFL...).

int (*dir_notify)(struct file *, unsigned long);

Этот метод вызывается, когда приложение использует fcntl для запроса уведомлений об изменении директории. Это полезно только для файловых систем; драйверам нет необходимости реализовывать dir_notify.

Драйвер устройства scull реализует только самые важные методы устройства. Его структура file_operations инициализируется следующим образом:

struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};

Эта декларация использует синтаксис инициализации стандартной маркированной структуры языка Си. Этот синтаксис является предпочтительным, поскольку делает драйвера более переносимыми через изменения в определениях структуры и, возможно, делает код более компактным и читабельным.

Маркированная инициализация разрешает переназначение членов структуры; в некоторых случаях были реализованы существенные улучшения производительности путём размещения указателей к наиболее часто используемым членам в одной строке кэша оборудования.

Структура file

Структура file, определённая в fs.h>, является второй наиболее важной структурой данных, используемой драйверами устройств. Обратите внимание, что file не имеет ничего общего с указателями FILE программ пространства пользователя. FILE определён в библиотеке Си и никогда не появляется в коде ядра. Структура file, с другой стороны, это структура ядра, которая никогда не появляется в пользовательских программах.
Структура file представляет открытый файл. (Это не специфично для драйверов устройств; каждый открытый файл в системе имеет ассоциированную структуру file в пространстве ядра.) Она создаётся ядром при открытии и передаётся в любую функцию, которая работает с файлом, до последнего закрытия. После закрытия всех экземпляров файла ядро освобождает структуру данных.

В исходных текстах ядра указатель на структуру file обычно назван или file или filp ("указатель на файл"). Мы будем использовать название указателя filp для предотвращения путаницы с самой структурой. Таким образом, file относится к структуре, а filp - указатель на структуру.
Здесь показаны наиболее важные поля структуры file. Как и в предыдущем разделе, список может быть пропущен при первом чтении. Тем не менее, позже в этой главе, когда мы столкнёмся с некоторым реальным кодом Си, мы обсудим эти поля более подробно.

mode_t f_mode;

Определяет режим файла как или читаемый или записываемый (или и то и другое) с помощью битов FMODE_READ и FMODE_WRITE. Вы можете захотеть проверить это поле для разрешения чтения/записи в своей функции open или ioctl, но вам не нужно проверять разрешения для read и write, потому что ядро проверяет это перед вызовом вашего метода. Попытка чтения или записи, если файл не был открыт для этого типа доступа, отклоняется, так что драйвер даже не узнаёт об этом.

loff_t f_pos;

Текущая позиция чтения или записи. loff_t является 64-х разрядным значением на всех платформах (long long в терминологии gcc). Драйвер может читать это значение, чтобы узнать текущую позицию в файле, но обычно не должен изменять его; read и write должны обновить позицию с помощью указателя, который они получают в качестве последнего аргумента, вместо воздействия на filp->f_pos напрямую. Единственным исключением из этого правила является метод llseek, целью которого является изменение позиции файла.

unsigned int f_flags;

Существуют флаги файлов, такие как O_RDONLY, O_NONBLOCK и O_SYNC. Драйвер должен проверить флаг O_NONBLOCK, чтобы увидеть, была ли запрошена неблокирующая операция (мы обсудим неблокирующий ввод/вывод в разделе "Блокирующие и неблокирующие операции" в Главе 6); другие флаги используются редко. В частности, разрешение на чтение/запись должно быть проверено с помощью f_mode, а не f_flags. Все флаги определены в заголовке fcntl.h>.

struct file_operations *f_op;

Операции, связанные с файлом. Ядро присваивает указатель как часть своей реализации open, а затем считывает его, когда необходимо выполнить любые операции. Значение в filp->f_op никогда не сохраняется ядром для дальнейшего использования; это означает, что вы можете изменить файловые операции, связанные с вашим файлом, и новые методы будут действовать после вашего возвращения к вызвавшей программе. Например, код для open, связанный со старшим номером 1 (/dev/null, /dev/zero, и так далее), заменяет операции в filp->f_op в зависимости от открытого младшего номера. Эта практика позволяет реализовать несколько поведений под тем же основным номером без дополнительных накладных расходов при каждом системном вызове. Возможность заменить файловые операции является в ядре эквивалентом "переопределения метода” в объектно-ориентированном программировании.

void *private_data;

Системный вызов open устанавливает этот указатель в NULL для драйвера перед вызовом метода open. Вы можете сделать своё собственное использование поля или игнорировать его; вы можете использовать поле как указатель на выделенные данные, но тогда вы должны помнить об освобождении памяти в методе release до уничтожения структуры file ядром. private_data является полезным ресурсом для сохранения информации о состоянии через системные вызовы и используется в большинстве наших примеров модулей.

struct dentry *f_dentry;

труктура элемента каталога (directory entry, dentry), связанного с файлом. Авторам драйверов устройств обычно не  требуется заботиться о структуре dentry, помимо доступа к структуре inode через filp->f_dentry->d_inode.
Реальная структура имеет несколько больше полей, но они не являются полезными для драйверов устройств. Мы можем смело игнорировать эти поля, поскольку драйверы никогда не создают структуры файлов; они только обращаются к структурам, созданным другими.

Структура inode

Структура inode (индексный дескриптор) используется ядром внутренне для представления файлов. Поэтому она отличается от файловой структуры, которая представляет открытый файловый дескриптор. Может быть большое число файловых структур, представляющих собой множество открытых дескрипторов одного файла, но все они указывают на единственную структуру inode.

Структура inode содержит большое количество информации о файле. По общему правилу, только два поля этой структуры представляют интерес для написания кода драйвера:

dev_t i_rdev;

Это поле содержит фактический номер устройства для индексных дескрипторов, которые представляют файлы устройств.

struct cdev *i_cdev;

Структура cdev является внутренней структурой ядра, которая представляет символьные устройства; это поле содержит указатель на ту структуру, где inode ссылается на файл символьного устройства.

Тип i_rdev изменился в течение серии разработки ядра версии 2.5, поломав множество драйверов. В качестве средства поощрения более переносимого программирования, разработчики ядра добавили два макроса, которые могут быть использованы для получения старшего и младшего номера inode:

unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

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

Переведено на сайте www.notabenoid.com
http://notabenoid.com/book/11832/38271
Внимание! Этот перевод, возможно, ещё не готов,
так как модераторы установили для него статус
"перевод редактируется"

3.3. Some Important Data Structures

As you can imagine, device number registration is just the first of many tasks that driver code must carry out. We will soon look at other important driver components, but one other digression is needed first. Most of the fundamental driver operations involve three important kernel data structures, called file_operations, file, and inode. A basic familiarity with these structures is required to be able to do much of anything interesting, so we will now take a quick look at each of them before getting into the details of how to implement the fundamental driver operations.
3.3.1. File Operations
So far, we have reserved som e device numbers for our use, but we have not yet connected any of our driver's operations to those numbers. The file_operations structure is how a char driver sets up this connection. The structure, defined in , is a collection of function pointers. Each open file (represented internally by a file structure, which we will examine shortly) is associated with its own set of functions (by including a field called f_op that points to a file_operations structure). The operations are mostly in charge of implementing the system calls and are therefore, named open, read, and so on. We can consider the file to be an "object" and the functions operating on it to be its "methods," using object-oriented programming terminology to denote actions declared by an object to act on itself. This is the first sign of object-oriented programming we see in the Linux kernel, and we'll see more in later chapters.
Conventionally, a file_operations structure or a pointer to one is called fops (or some variation thereof ). Each field in the structure must point to the function in the driver that implements a specific operation, or be left NULL for unsupported operations. The exact behavior of the kernel when a NULL pointer is specified is different for each function, as the list later in this section shows.
The following list introduces all the operations that an application can invoke on a device. We've tried to keep the list brief so it can be used as a reference, merely summarizing each operation and the default kernel behavior when a NULL pointer is used.
As you read through the list of file_operations methods, you will note that a number of parameters include the string _ _user. This annotation is a form of documentation, noting that a pointer is a user-space address that cannot be directly dereferenced. For normal compilation, _ _user has no effect, but it can be used by external checking software to find misuse of user-space addresses.
The rest of the chapter, after describing some other important data structures, explains the role of the most important operations and offers hints, caveats, and real code examples. We defer discussion of the more complex operations to later chapters, because we aren't ready to dig into topics such as memory management, blocking operations, and asynchronous notification quite yet.
struct module *owner
The first file_operations field is not an operation at all; it is a pointer to the module that "owns" the structure. This field is used to prevent the module from being unloaded while its operations are in use. Almost all the time, it is simply initialized to THIS_MODULE, a macro defined in .
loff_t (*llseek) (struct file *, loff_t, int);
The llseek method is used to change the current read/write position in a file, and the new position is returned as a (positive) return value. The loff_t parameter is a "long offset" and is at least 64 bits wide even on 32-bit platforms. Errors are signaled by a negative return value. If this function pointer is NULL, seek calls will modify the position counter in the file structure (described in Section 3.3.2) in potentially unpredictable ways.
ssize_t (*read) (struct file *, char _ _user *, size_t, loff_t *);
Used to retrieve data from the device. A null pointer in this position causes the read system call to fail with -EINVAL ("Invalid argument"). A nonnegative return value represents the number of bytes successfully read (the return value is a "signed size" type, usually the native integer type for the target platform).
ssize_t (*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);
Initiates an asynchronous read—a read operation that might not complete before the function returns. If this method is NULL, all operations will be processed (synchronously) by read instead.
ssize_t (*write) (struct file *, const char _ _user *, size_t, loff_t *);
Sends data to the device. If NULL, -EINVAL is returned to the program calling the write system call. The return value, if nonnegative, represents the number of bytes successfully written.
ssize_t (*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *);
Initiates an asynchronous write operation on the device.
int (*readdir) (struct file *, void *, filldir_t);
This field should be NULL for device files; it is used for reading directories and is useful only for filesystems.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
The poll method is the back end of three system calls: poll, epoll, and select, all of which are used to query whether a read or write to one or more file descriptors would block. The poll method should return a bit mask indicating whether non-blocking reads or writes are possible, and, possibly, provide the kernel with information that can be used to put the calling process to sleep until I/O becomes possible. If a driver leaves its poll method NULL, the device is assumed to be both readable and writable without blocking.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
The ioctl system call offers a way to issue device-specific commands (such as formatting a track of a floppy disk, which is neither reading nor writing). Additionally, a few ioctl commands are recognized by the kernel without referring to the fops table. If the device doesn't provide an ioctl method, the system call returns an error for any request that isn't predefined (-ENOTTY, "No such ioctl for device").
int (*mmap) (struct file *, struct vm_area_struct *);
mmap is used to request a mapping of device memory to a process's address space. If this method is NULL, the mmap system call returns -ENODEV.
int (*open) (struct inode *, struct file *);
Though this is always the first operation performed on the device file, the driver is not required to declare a corresponding method. If this entry is NULL, opening the device always succeeds, but your driver isn't notified.
int (*flush) (struct file *);
The flush operation is invoked when a process closes its copy of a file descriptor for a device; it should execute (and wait for) any outstanding operations on the device. This must not be confused with the fsync operation requested by user programs. Currently, flush is used in very few drivers; the SCSI tape driver uses it, for example, to ensure that all data written makes it to the tape before the device is closed. If flush is NULL, the kernel simply ignores the user application request.
int (*release) (struct inode *, struct file *);
This operation is invoked when the file structure is being released. Like open, release can be NULL.[5]
[5] Note that release isn't invoked every time a process calls close. Whenever a file structure is shared (for example, after a fork or a dup), release won't be invoked until all copies are closed. If you need to flush pending data when any copy is closed, you should implement the flush method.
int (*fsync) (struct file *, struct dentry *, int);
This method is the back end of the fsync system call, which a user calls to flush any pending data. If this pointer is NULL, the system call returns -EINVAL.
int (*aio_fsync)(struct kiocb *, int);
This is the asynchronous version of the fsync method.
int (*fasync) (int, struct file *, int);
This operation is used to notify the device of a change in its FASYNC flag. Asynchronous notification is an advanced topic and is described in Chapter 6. The field can be NULL if the driver doesn't support asynchronous notification.
int (*lock) (struct file *, int, struct file_lock *);
The lock method is used to implement file locking; locking is an indispensable feature for regular files but is almost never implemented by device drivers.
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
These methods implement scatter/gather read and write operations. Applications occasionally need to do a single read or write operation involving multiple memory areas; these system calls allow them to do so without forcing extra copy operations on the data. If these function pointers are left NULL, the read and write methods are called (perhaps more than once) instead.
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
This method implements the read side of the sendfile system call, which moves the data from one file descriptor to another with a minimum of copying. It is used, for example, by a web server that needs to send the contents of a file out a network connection. Device drivers usually leave sendfile NULL.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,
int);
sendpage is the other half of sendfile; it is called by the kernel to send data, one page at a time, to the corresponding file. Device drivers do not usually implement sendpage.
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned
long, unsigned long, unsigned long);
The purpose of this method is to find a suitable location in the process's address space to map in a memory segment on the underlying device. This task is normally performed by the memory management code; this method exists to allow drivers to enforce any alignment requirements a particular device may have. Most drivers can leave this method NULL.
int (*check_flags)(int)
This method allows a module to check the flags passed to an fcntl(F_SETFL...) call.
int (*dir_notify)(struct file *, unsigned long);
This method is invoked when an application uses fcntl to request directory change notifications. It is useful only to filesystems; drivers need not implement dir_notify.
The scull device driver implements only the most important device methods. Its file_operations structure is initialized as follows:
struct file_operations scull_fops = {
    .owner =    THIS_MODULE,
    .llseek =   scull_llseek,
    .read =     scull_read,
    .write =    scull_write,
    .ioctl =    scull_ioctl,
    .open =     scull_open,
    .release =  scull_release,
};

This declaration uses the standard C tagged structure initialization syntax. This syntax is preferred because it makes drivers more portable across changes in the definitions of the structures and, arguably, makes the code more compact and readable. Tagged initialization allows the reordering of structure members; in some cases, substantial performance improvements have been realized by placing pointers to frequently accessed members in the same hardware cache line.
3.3.2. The file Structure
struct file, defined in , is the second most important data structure used in device drivers. Note that a file has nothing to do with the FILE pointers of user-space programs. A FILE is defined in the C library and never appears in kernel code. A struct file, on the other hand, is a kernel structure that never appears in user programs.
The file structure represents an open file . (It is not specific to device drivers; every open file in the system has an associated struct file in kernel space.) It is created by the kernel on open and is passed to any function that operates on the file, until the last close. After all instances of the file are closed, the kernel releases the data structure.
In the kernel sources, a pointer to struct file is usually called either file or filp ("file pointer"). We'll consistently call the pointer filp to prevent ambiguities with the structure itself. Thus, file refers to the structure and filp to a pointer to the structure.
The most important fields of struct file are shown here. As in the previous section, the list can be skipped on a first reading. However, later in this chapter, when we face some real C code, we'll discuss the fields in more detail.
mode_t f_mode;
The file mode identifies the file as either readable or writable (or both), by means of the bits FMODE_READ and FMODE_WRITE. You might want to check this field for read/write permission in your open or ioctl function, but you don't need to check permissions for read and write, because the kernel checks before invoking your method. An attempt to read or write when the file has not been opened for that type of access is rejected without the driver even knowing about it.
loff_t f_pos;
The current reading or writing position. loff_t is a 64-bit value on all platforms (long long in gcc terminology). The driver can read this value if it needs to know the current position in the file but should not normally change it; read and write should update a position using the pointer they receive as the last argument instead of acting on filp->f_pos directly. The one exception to this rule is in the llseek method, the purpose of which is to change the file position.
unsigned int f_flags;
These are the file flags, such as O_RDONLY, O_NONBLOCK, and O_SYNC. A driver should check the O_NONBLOCK flag to see if nonblocking operation has been requested (we discuss nonblocking I/O in Section 6.2.3); the other flags are seldom used. In particular, read/write permission should be checked using f_mode rather than f_flags. All the flags are defined in the header .
struct file_operations *f_op;
The operations associated with the file. The kernel assigns the pointer as part of its implementation of open and then reads it when it needs to dispatch any operations. The value in filp->f_op is never saved by the kernel for later reference; this means that you can change the file operations associated with your file, and the new methods will be effective after you return to the caller. For example, the code for open associated with major number 1 (/dev/null, /dev/zero, and so on) substitutes the operations in filp->f_op depending on the minor number being opened. This practice allows the implementation of several behaviors under the same major number without introducing overhead at each system call. The ability to replace the file operations is the kernel equivalent of "method overriding" in object-oriented programming.
void *private_data;
The open system call sets this pointer to NULL before calling the open method for the driver. You are free to make its own use of the field or to ignore it; you can use the field to point to allocated data, but then you must remember to free that memory in the release method before the file structure is destroyed by the kernel. private_data is a useful resource for preserving state information across system calls and is used by most of our sample modules.
struct dentry *f_dentry;
The directory entry (dentry) structure associated with the file. Device driver writers normally need not concern themselves with dentry structures, other than to access the inode structure as filp->f_dentry->d_inode.
The real structure has a few more fields, but they aren't useful to device drivers. We can safely ignore those fields, because drivers never create file structures; they only access structures created elsewhere.
3.3.3. The inode Structure
The inode structure is used by the kernel internally to represent files. Therefore, it is different from the file structure that represents an open file descriptor. There can be numerous file structures representing multiple open descriptors on a single file, but they all point to a single inode structure.
The inode structure contains a great deal of information about the file. As a general rule, only two fields of this structure are of interest for writing driver code:
dev_t i_rdev;
For inodes that represent device files, this field contains the actual device number.
struct cdev *i_cdev;
struct cdev is the kernel's internal structure that represents char devices; this field contains a pointer to that structure when the inode refers to a char device file.
The type of i_rdev changed over the course of the 2.5 development series, breaking a lot of drivers. As a way of encouraging more portable programming, the kernel developers have added two macros that can be used to obtain the major and minor number from an inode:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

In the interest of not being caught by the next change, these macros should be used instead of manipulating i_rdev directly.