Steps to add a new system call to Linux - similar to Minix.
Adding a device driver is different.
Linux (eventually) added kernel module utilities.
- Write module code and compile in user space.
- Use utilities to insert into the kernel
- Use utilites to remove a module from the kernel.
Modules provide a general way to insert code into a running
Linux system without having to recompile or reboot the system.
Linux systems may load some modules at boot time.
A kernel daemon process can also load existing modules into the
kernel on demand while the system is running.
The daemon uses this utility:
modprobe
Given a string that is either a module name or a generic
identifer, modprobe discovers the location of the compiled module
object file.
(Module object files have an extra section that describes the
module and the file names end with .ko instead of
.o of ordinary object files.
Files:
Modules loaded at boot time
/etc/modules
Module list and file locations for existing modules to be loaded dynamically
/lib/modules/[kernel version]/modules.dep
Modules do not have a main function.
But at least 2 functions are expected. Originally, these had to
be named init_module and cleanup_module:
int init_module(void);
void cleanup_module(void);
The init_module function executes when the module is
"inserted" into the kernel and cleanup_module, when it is
removed.
Note: Some kernel modules are not removed while the system is
running.
The modinfo utility will display the module information
stored in a kernel modules file (a .ko file).
modinfo blob.ko
The utility for listing kernel modules currently "loaded"
(dynamically linked into the kernel) is
lsmod
You can also see the list and number of users
of each currenlty loaded module in a special file in the
proc file system:
/proc/modules
A build utility makes it easy to create a Makefile to
compile and 'build' kernel modules from C files.
All that is necessary is to add the name of an ordinary object
file to a list.
The build utility then looks for the C file (or files) and
builds the .o and .ko files.
The utilities for inserting/removing modules are
insmod
rmmod
(The following examples are from The Linux Kernel Module Programming
Guide by Peter J. Saltzman, Michael Burian, and Ori
Pomerantz.)
Here is their first kernel module code.
/*
* hello-1.c - The simplest kernel module.
*/
#include <linux/module.h>/* Needed by all modules */
#include <linux/kernel.h>/* Needed for KERN_INFO */
int init_module(void)
{
printk(KERN_INFO "Hello world 1.\n");
/*
* A non 0 return means init_module failed; module can't be
loaded.
*/
return 0;
}
void cleanup_module(void)
{
printk(KERN_INFO "Goodbye world 1.\n");
}
What is KERN_INFO?
Where does printk output go?
Output goes to a log file:
/var/log/syslog
and will also go to the "console" (not to an xterm window)
provided the priority level is sufficiently high:
From /usr/src/[kernel version]/include/linux/printk.h
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
Changing KERN_INFO to KERN_ALERT will cause the output to
go to the console.
A Module can specify parameters to be passed to it when the
module is inserted in the kernel.
The init_module function takes no parameters, so the mechanism
for passing these parameters is different.
Special macros should be used.
Place the following in the .c file for a module:
#define DRIVER_AUTHOR "Glenn Lancaster <glancast@cs.depaul.edu>"
#define DRIVER_DESC "A module accepting command line arguments"
static int hidden = 0;
static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "blah";
static int myintArray[2] = {5, 10};
static int arr_argc = 0;
/*
* module_param(foo, int, 0000)
* foo - parameter name
* int - parameter's data type
* 0000 - permission bits: S_Ipq p = R | W | X, q = USR | GRP | OTH
*/
module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(myshort, "A short integer");
module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(myint, "An integer");
module_param(mylong, long, S_IRUSR);
MODULE_PARM_DESC(mylong, "A long integer");
module_param(mystring, charp, 0000);
MODULE_PARM_DESC(mystring, "A character string");
Assume the previous module is hello-5.c and the kernel module
hello-5.ko is built.
Then inserting this into the kernel and passing
parameters to the static variables mystring, myshort and myintArray:
insmod hello-5.ko mystring="bebop" myshort=255 myintArray=15,20
The module can be removed from the kernel by:
rmmod hello-5
What happens when the module is loaded? removed?
Where does the output (if any) go when the module is loaded? removed?
Note: You must be "root" to execute insmod (or rmmod).
As an aside, the strace command outputs all the system
calls made when a program runs.
The file scope variables used as module parameters in the
hello-5 module were declared static.
This is because this module is being linked into the Linux
kernel image.
Since these variables are at file scope they become accessible
to and part of the all of the kernel's global
variables. Conflicts?
Making them static means they are only accessible from code in
this file.
What are the kernel global variables? See
/proc/kallsyms
Device driver: code to control a device
Linux uses the file system interface to control devices
Devices correspond to entries in the file system.
- Major number: identifies the device driver
- Minor number: identifies a particular device
A device file specifies both the major and minor numbers.
A single device driver might control several devices: same
major number, different device numbers.
Conversely, the same device could be represented by more than
one device file and each device file specifies a different
driver.
Block devices generally have buffers and are read/written a buffer at a
time.
Character devices, e.g. keyboards, terminals read/write
variable number of bytes.
Block devices and character devices listed with b or
c
A long listing also gives the major and minor device
numbers.
Creating a device file[17] [top]
The mknod command creates a device file, specifying
block or character device and giving the major and minor device
numbers.
mknod filename [b or c] major_num minor_num
What major number should be used to create a new device
file?
The file
/proc/devices
lists the current devices and their major device numbers (not
the minor numbers)
struct file_operations {
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t,
loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t,
loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,
loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int,
unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_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 *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t,
read_actor_t,
void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t,
loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
This syntax is permitted (in the 99 C standard) to assign
device_read to the read member (and similarly for the other
members) of the file_operations struct.
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
Members not explicitly assigned are given the value 0.
A module initialization function can register a device
with:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
Note that only the major number is passed. The kernel doesn't
need to know the minor number.
Only the device driver functions need the minor number.
Here are the prototypes for functions in a char device driver
file chardev.c:
chardev.c:
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
- Build the kernel module chardev.ko from chardev.c
- Insert the module in the kernel with insmod
- Find the major device number dynamically assigned to this
device.
- Create one (or more) device files with this major device
number
- Read/write to this device.
Take a look at this file for the functions it defines:
chardev.c:
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
Insert the module
What is the major number
Create 2 device files with this major number and minor numbers 0
and 1
Demonstrating reading and writing
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp;
const int MAXLINE = 130;
char line[MAXLINE];
char *cp;
for(int i = 0; i < 5; i++) {
fp = fopen("/dev/chardev0", "r");
if (fp == NULL) {
printf("Unable to open device /dev/chardev0\n");
exit(0);
}
cp = fgets(line, MAXLINE, fp);
if ( cp == NULL ) {
printf("fgets failed to read from /dev/chardev0\n");
exit(0);
} else {
printf("%s\n", line);
}
fgets(line, MAXLINE, stdin);
fclose(fp);
}
return 0;
}
The module utility can also be used to create "files" in the
proc file system.
Two examples:
- Read (only) some kernel data.
- Read and write kernel data (but write only if root).
This existing file is of the second kind:
/proc/sys/kernel/randomize_va_space
If it is > 0 the kernel adds a random number of bytes to the
bottom of the call stack each time a program is executed as
protection against buffer overflow attacks.