Systèmes d’exploitation Avancés

Programming · lab

Voir tous les documents en programmation

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

1/25

Lab.1: Building and Running Modules

Due Date Nov,

6

15 201

The Hello World Module

Source:

Linux Device Drivers, by Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman. Available

under the terms of the Creative Commons Attribution-ShareAlike 2.0 license. That means that you

are free to download and redistribute it.

Link: http://lwn.net/Kernel/LDD3/

Hello.c

#include <linux/init.h>

#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)

{

}

printk(KERN_ALERT "Hello, world\n");

return 0;

static void hello_exit(void)

{

}

printk(KERN_ALERT "Goodbye, cruel world\n");

module_init(hello_init);

module_exit(hello_exit);

This module defines two functions, one to be invoked when the module is loaded into the kernel

(hello_init) and one for when the module is removed (hello_exit).

The module_init and module_exit lines use special kernel macros to indicate the role of these two

functions. Another special macro (MODULE_LICENSE) is used to tell the kernel that this

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

2/25

module bears a free license; without such a declaration, the kernel complains when the module is

loaded.

The printk function is defined in the Linux kernel and made available to modules; it behaves

similarly to the standard C library function printf. The kernel needs its own printing function

because it runs by itself, without the help of the C library. The module can call printk because, after

insmod has loaded it, the module is linked to the kernel and can access the kernel’s public symbols .

The string KERN_ALERT is the priority of the message.

We’ve specified a high priority in this module, because a message with the default priority might

not show up anywhere useful, depending on the kernel version you are running, the version of the

klogd daemon, and your configuration.

You can test the module with the insmod and rmmod utilities, as shown below. Note that only the

superuser can load and unload a module.

Makefile :

If KERNELRELEASE is defined, we've been invoked from the

kernel build system and can use its language.

ifneq ($(KERNELRELEASE),)

obj­m := hello.o

Otherwise we were called directly from the command

line; invoke the kernel build system.

else

KERNELDIR ?= /lib/modules/$(shell uname ­r)/build

PWD := $(shell pwd)

default:

$(MAKE) ­C $(KERNELDIR) M=$(PWD) modules

endif

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

3/25

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

4/25

Kernel Modules Versus Applications

Before we go further, it’s worth underlining the various differences between a kernel module and an

application.

While most small and medium-sized applications perform a single task from beginning to end,

every kernel module just registers itself in order to serve future requests, and its initialization

function terminates immediately. In other words, the task of the module’s initialization function is to

prepare for later invocation of the module’s functions; it’s as though the module were saying, “Here

I am, and this is what I can do.”

The module’s exit function (hello_exit in the example) gets invoked just before the module is

unloaded. It should tell the kernel, “I’m not there anymore; don’t ask me to do anything else.” This

kind of approach to programming is similar to event-driven programming, but while not all

applications are event-driven, each and every kernel module is. Another major difference between

event-driven applications and kernel code is in the exit function: whereas an application that

terminates can be lazy in releasing resources or avoids clean up altogether, the exit function of a

module must carefully undo everything the init function built up, or the pieces remain around until

the system is rebooted.

Incidentally, the ability to unload a module is one of the features of modularization that you’ll most

appreciate, because it helps cut down development time; you can test successive versions of your

new driver without going through the lengthy shutdown/reboot cycle each time.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

5/25

As a programmer, you know that an application can call functions it doesn’t define: the linking

stage resolves external references using the appropriate library of functions. printf is one of those

callable functions and is defined in libc. A module, on the other hand, is linked only to the kernel,

and the only functions it can call are the ones exported by the kernel; there are no libraries to link

to. The printk function used in hello.c earlier, for example, is the version of printf defined within the

kernel and exported to modules. It behaves similarly to the original function, with a few minor

differences, the main one being lack of floating-point support.

Another important difference between kernel programming and application programming is in how

each environment handles faults: whereas a segmentation fault is harmless during application

development and a debugger can always be used to trace the error to the problem in the source

code, a kernel fault kills the current process at least, if not the whole system.

User Space and Kernel Space

A module runs in kernel space, whereas applications run in user space. This concept is at the base of

operating systems theory. The role of the operating system, in practice, is to provide programs with

a consistent view of the computer’s hardware. In addition, the operating system must account for

independent operation of programs and protection against unauthorized access to resources. This

nontrivial task is possible only if the CPU enforces protection of system software from the

applications.

Every modern processor is able to enforce this behavior. The chosen approach is to implement

different operating modalities (or levels) in the CPU itself. The levels have different roles, and some

operations are disallowed at the lower levels; program code can switch from one level to another

only through a limited number of gates. Unix systems are designed to take advantage of this

hardware feature, using two such levels. All current processors have at least two protection levels,

and some, like the x86 family, have more levels; when several levels exist, the highest and lowest

levels are used. Under Unix, the kernel executes in the highest level (also called supervisor mode),

where everything is allowed, whereas applications execute in the lowest level (the so-called user

mode), where the processor regulates direct access to hardware and unauthorized access to memory.

We usually refer to the execution modes as kernel space and user space. These terms encompass not

Publicité

only the different privilege levels inherent in the two modes, but also the fact that each mode can

have its own memory mapping—its own address space—as well.

Unix transfers execution from user space to kernel space whenever an application issues a system

call or is suspended by a hardware interrupt. Kernel code executing a system call is working in the

context of a process—it operates on behalf of the calling process and is able to access data in the

process’s address space. Code that handles interrupts, on the other hand, is asynchronous with

respect to processes and is not related to any particular process. The role of a module is to extend

kernel functionality; modularized code runs in kernel space. Usually a driver performs both the

tasks outlined previously: some functions in the module are executed as part of system calls, and

some are in charge of interrupt handling.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

6/25

Concurrency in the Kernel

One way in which kernel programming differs greatly from conventional application programming

is the issue of concurrency. Most applications, with the notable exception of multithreading

applications, typically run sequentially, from the beginning to the end, without any need to worry

about what else might be happening to change their environment. Kernel code does not run in such

a simple world, and even the simplest kernel modules must be written with the idea that many

things can be happening at once.

There are a few sources of concurrency in kernel programming. Naturally, Linux systems run

multiple processes, more than one of which can be trying to use your driver at the same time. Most

devices are capable of interrupting the processor; interrupt handlers run asynchronously and can be

invoked at the same time that your driver is trying to do something else. Moreover, of course,

Linux can run on symmetric multiprocessor (SMP) systems, with the result that your driver could

be executing concurrently on more than one CPU. Finally, in 2.6, kernel code has been made

preemptive; this change causes even uniprocessor systems to have many of the same concurrency

issues as multiprocessor systems. As a result, Linux kernel code, including driver code, must be

reentrant—it must be capable of running in more than one context at the same time. Data structures

must be carefully designed to keep multiple threads of execution separate, and the code must take

care to access shared data in ways that prevent corruption of the data.

Writing code that handles concurrency and avoids race conditions (situations in which an

unfortunate order of execution causes undesirable behavior) requires thought and can be tricky.

A common mistake made by driver programmers is to assume that concurrency is not a problem as

long as a particular segment of code does not go to sleep (or “block”). Even in previous kernels

(which were not preemptive), this assumption was not valid on multiprocessor systems. In 2.6,

kernel code can (almost) never assume that it can hold the processor over a given stretch of code. If

you do not write your code with concurrency in mind, it will be subject to catastrophic failures that

can be exceedingly difficult to debug.

The Current Process

Although kernel modules don’t execute sequentially as applications do, most actions performed by

the kernel are done on behalf of a specific process. Kernel code can refer to the current process by

accessing the global item current, defined in <asm/ current.h>, which yields a pointer to struct

task_struct, defined by <linux/sched.h>.

The current pointer refers to the process that is currently executing. During the execution of a

system call, such as open or read, the current process is the one that invoked the call. Kernel code

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

7/25

can use process-specific information by using current, if it needs to do so. Actually, current is not

truly a global variable. The need to support SMP systems forced the kernel developers to develop a

mechanism that finds the current process on the relevant CPU. This mechanism must also be fast,

since references to current happen frequently. The result is an architecture-dependent mechanism

that, usually, hides a pointer to the task_struct structure on the kernel stack. The details of the

implementation remain hidden to other kernel subsystems though, and a device driver can just

include <linux/sched.h> and refer to the current process. For example, the following statement

prints the process ID and the command name of the current process by accessing certain fields in

struct task_struct:

printk(KERN_INFO "The process is %s (pid %i)\n",current­>comm,

current­>pid);

The command name stored in current->comm is the base name of the program file (trimmed to 15

characters if need be) that is being executed by the current process.

Kernel programming differs from user-space programming in many ways. We’ll point things out as

we get to them over the course of the book, but there are a few fundamental issues which, while not

warranting a section of their own, are worth a mention. So, as you dig into the kernel, the following

issues should be kept in mind.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

8/25

Applications are laid out in virtual memory with a very large stack area. The stack, of course, is

used to hold the function call history and all automatic variables created by currently active

functions. The kernel, instead, has a very small stack; it can be as small as a single, 4096-byte page.

Your functions must share that stack with the entire kernel-space call chain. Thus, it is never a good

idea to declare large automatic variables; if you need larger structures, you should allocate them

dynamically at call time.

Often, as you look at the kernel API, you will encounter function names starting with a double

underscore (__). Functions so marked are generally a low-level component of the interface and

should be used with caution. Essentially, the double underscore says to the programmer: “If you call

this function, be sure you know what you are doing.”

Kernel code cannot do floating point arithmetic. Enabling floating point would require that the

kernel save and restore the floating point processor’s state on each entry to, and exit from, kernel

space—at least, on some architectures. Given that there really is no need for floating point in kernel

code, the extra overhead is not worthwhile.

Compiling and Loading

As the first step, we need to look a bit at how modules must be built. The build process for modules

differs significantly from that used for user-space applications; the kernel is a large, standalone

program with detailed and explicit requirements on how its pieces are put together. The build

process also differs from how things were done with previous versions of the kernel; the new build

system is simpler to use and produces more correct results, but it looks very different from what

came before. The kernel build system is a complex beast, and we just look at a tiny piece of it. The

files found in the Documentation/kbuild directory in the kernel source are required reading for

anybody wanting to understand all that is really going on beneath the surface.

There are some prerequisites that you must get out of the way before you can build kernel modules.

The first is to ensure that you have sufficiently current versions of the compiler, module utilities,

and other necessary tools. The file Documentation/Changes in the kernel documentation directory

always lists the required tool versions; you should consult it before going any further. Trying to

build a kernel (and its modules) with the wrong tool versions can lead to no end of subtle, difficult

problems.

When the makefile is invoked from the command line, it notices that the KERNELRELEASE

variable has not been set. It locates the kernel source directory by taking advantage of the fact that

the symbolic link build in the installed modules directory points back at the kernel build tree. If you

are not actually running the kernel that you are building for, you can supply a KERNELDIR=

option on the command line, set the KERNELDIR environment variable, or rewrite the line that sets

KERNELDIR in the makefile. Once the kernel source tree has been found, the makefile invokes the

default: target, which runs a second make command (parameterized in the makefile as $(MAKE)) to

invoke the kernel build system as described previously. On the second reading, the makefile sets

obj-m, and the kernel makefiles take care of actually building the module.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

9/25

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

This command starts by changing its directory to the one provided with the -C option (that is, your

kernel source directory). There it finds the kernel’s top-level makefile. The M= option causes that

makefile to move back into your module source directory before trying to build the modules target.

This target, in turn, refers to the list of modules found in the obj-m variable, which we’ve set to

hello.o in our examples.

27/09/16

Publicité

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

10/25

Lab.1.2 : Writing a Linux character Device Driver

writing a Linux device driver for a hypothetical character device which reverses any string that is

given to it. i.e. If we write any string to the device file represented by the device and then read that

file, we get the string written earlier but reversed (for eg., myDev being our device, echo “hello”

>/dev/myDev ; cat /dev/ myDev would print “olleh”).

Introduction

The devices in UNIX fall in two categories- Character devices and Block devices. Character

devices can be compared to normal files in that we can read/write arbitrary bytes at a time (although

for most part, seeking is not supported).They work with a stream of bytes. Block devices, on the

other hand, operate on blocks of data, not arbitrary bytes. Usual block size is 512 bytes or larger

powers of two. However, block devices can be accessed the same was as character devices, the

driver does the block management. (Networking devices do not belong to these categories, the

interface provided by these drivers in entirely different from that of char/block devices)

The beauty of UNIX is that devices are represented as files. Both character devices and block

devices are represented by respective files in the /dev directory. This means that you can read and

write into the device by manipulating those file using standard system calls like open, read, write,

close etc.

For eg, you could directly write or read the hard disk by accessing /dev/sd* file – a dangerous act

unless you know what you are doing (for those interested, try hexdump –C /dev/sda –n 512 –

what you see then is the boot sector of your hard disk !). As another example, you could directly see

the contents of the RAM by reading /dev/mem.

Every device file represented in this manner is associated with the device driver of that device

which is actually responsible for interacting with the device on behalf of the user request. So when

you access a device file, the request is forwarded to the respective device driver which does the

processing and returns the result.

For instance, you might be knowing about the files /dev/zero (an infinite source of zeroes), /dev/null

(a data black hole), /dev/random ( a source of random numbers) etc. When you actually read these

files, what happens is that a particular function in the device driver registered for the file is invoked

which returns the respective data.

In our example, we will be developing a character device represented by the device file

/dev/myDev. The mechanisms for creating this file will be explained later.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

Under the hood

11/25

Now how does Linux know which driver is associated with which file? For that, each device and its

device file has associated with it, a unique Major number and a Minor number. No two devices

have the same major number. When a device file is opened, Linux examines its major number and

forwards the call to the driver registered for that device. Subsequent calls for read/write/close too

are processed by the same driver. As far as kernel is concerned, only major number is important.

Minor number is used to identify the specific device instance if the driver controls more than one

device of a type.

To know the major, minor number of devices, use the ls – l command as shown below

The starting ‘c’ means its a character device, 1 is the major number and 8 is the minor number.

A Linux driver is a Linux module which can be loaded and linked to the kernel at runtime. The

driver operates in kernel space and becomes part of the kernel once loaded, the kernel being

monolithic. It can then access the symbols exported by the kernel. When the device driver module is

loaded, the driver first registers itself as a driver for a particular device specifying a particular Major

number.

It uses the call register_chrdev function for registration. The call takes the Major number, Minor

number, device name and an address of a structure of the type file_operations(discussed later) as

argument. In our example, we will be using a major number of 89 . The choice of major number is

arbitrary but it has to be unique on the system.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

12/25

The syntax of register_chrdev is :

int register_chrdev(unsigned int major,const char name,struct file_operations fops)

Driver is unregistered by calling the unregister_chrdev function.

Since device driver is a kernel module, it should implement init_module and cleanup_module

functions. The register_chrdev call is done in the init_module function and unregister_chrdev call

is done in the cleanup_module function.

The register_chrdev call returns a non-negative number on success. If we specify the Major number

as 0, the kernel returns a Major number unique at that instant which can be used to create a device

file. A device file can be created either before the driver is loaded if we know the major and minor

number beforehand or it can be created later after letting the driver specify a major number for us.

implement

Apart from those, the driver must also define certain callback functions that would be invoked

when file operations are done on the device file. Ie. It must define functions that would be invoked

by the kernel when a process uses open, read, write, close system calls on the file. Every driver

must

requests.

for

When register_chrdev call is done, the fourth argument is a structure that contains the addresses of

these callback functions, callbacks for open, read, write, close system calls. The structure is of the

type file_operations and has 4 main fields that should be set – read,write,open and release. Each

field must be assigned an address of a function that would be called when open, read,write , close

system calls are called respectively. For eg:

processing

functions

these

It is important to note that all these callback functions have a predefined prototype although the

name can be any.

Creating a device file

A device file is a special file. It can’t just be created using cat or gedit or shell redirection for that

matter. The shell command mknod is usually used to create device file. The syntax of mknod is

mknod path type major minor

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

13/25

path: path where the file to be created. It’s not necessary that the device file needs to be created in

the /dev directory. It’s a mere convention. A device file can be created just about anywhere.

type: ‘c’ or ‘b’ . Whether the device being represented is a character device or a block device. In our

example, we will be simulating a character device and hence we choose ‘c’.

major, minor:- the major and minor number of the device.

Heres how

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/fs.h>

#include <asm/uaccess.h>

MODULE_LICENSE("GPL");

MODULE_DESCRIPTION("Device Driver Demo");

MODULE_AUTHOR("Amine Dhraief");

static char msg[100]= {0};

static short readPos=0;

static int times=0;

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

static int Major;

14/25

static int dev_open(struct inode , struct file );

static int dev_release(struct inode , struct file );

static ssize_t dev_read(struct file , char , size_t, loff_t *);

Publicité

static   ssize_t   dev_write(struct   file   ,   const   char   ,   size_t,

loff_t *);

static struct file_operations fops = {

.read = dev_read,

.write = dev_write,

.open = dev_open,

.release = dev_release

};

int init_module(void)

{

Major = register_chrdev(89, "MyDev", &fops);

if (Major < 0) {

printk(KERN_ALERT   "Registering   char   device   failed   with

%d\n", Major);

return Major;

}

return 0;

}

void cleanup_module(void)

{

/*

  • Unregister the device

*/

unregister_chrdev(89, "MyDev");

}

static int dev_open(struct inode inod, struct file fil)

{

times++;

printk(KERN_ALERT "Device opened %d times \n", times);

return 0;

}

27/09/16

15/25

see

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

static ssize_t dev_read(struct file *filp,

include/linux/fs.h   */

/*

char buff, / buffer to fill with data */

size_t len, / length of the buffer     /

loff_t * off)

{

}

short count;

count=0;

while (len && (msg[readPos] !=0))

{

put_user(msg[readPos],buff++);

count++;

len­­;

readPos++;

}

return count;

static   ssize_t   dev_write(struct   file   filp,   const   char   buff,

size_t len, loff_t * off)

{

short ind;

short count;

ind= len ­1;

count=0;

memset(msg,0,100);

readPos=0;

while(len>0)

{

msg[count++] = buff[ind­­];

len ­­;

}

return count;

}

static int dev_release(struct inode inod, struct file fil)

{

printk(KERN_ALERT "Device closed\n");

return 0;

}

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

16/25

Some Important Data Structures

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 get- ting into

the details of how to implement the fundamental driver operations.

File Operations

The file_operations structure is how a char driver sets up this connection. The structure, defined in

<linux/fs.h>, 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.

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 opera- tions. The exact behavior of the kernel

when a NULL pointer is specified is different for each function.

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 us.

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 documenta-tion, 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.

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

<linux/module.h>.

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

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

17/25

pointer is NULL, seek calls will modify the position counter in the file structure in potentially

unpredictable ways.

ssize_t (read) (struct file , char __user , size_t, loff_t );

Publicité

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 for- matting 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

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

18/25

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.

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. 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.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

19/25

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.

27/09/16

Systèmes d’exploitation Avancés

Master PRO MBDS

Amine DHRAIEF

20/25

The MyDeb device driver implements only the most important device methods. Its file_operations

structure is initialized as follows:

static struct file_operations fops = {

.read = dev_read,

.write = dev_write,

.open = dev_open,

.release = dev_release

};

This declaration uses the standard C tagged structure initialization syntax. This syn- tax 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...