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),)
objm := 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...