CSC373 Nov02

slide version

single file version

Contents

  1. Modifying Linux
  2. Existing current kernel modules
  3. Modules
  4. Module Information Section of .ko File
  5. Listing Modules in the Kernel
  6. Compiling modules
  7. Inserting/Removing Kernel Modules
  8. A kernel module source code
  9. Module Parameters
  10. Example
  11. Passing Parameters
  12. The strace command
  13. Why static
  14. Device Drivers
  15. Major/Minor Device Numbers
  16. The /dev Directory
  17. Creating a device file
  18. File Interface (also used by devices)
  19. Assigning Members of the file_operations struct
  20. Registering a Device
  21. A char device module
  22. Installing the Device driver
  23. chardev.c
  24. Create device files
  25. Using cat and echo
  26. Program to read the char device
  27. The proc file system and modules

Modifying Linux[1] [top]

Steps to add a new system call to Linux - similar to Minix.

Adding a device driver is different.

Linux (eventually) added kernel module utilities.

Modules provide a general way to insert code into a running Linux system without having to recompile or reboot the system.

Existing current kernel modules[2] [top]

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[3] [top]

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.

Module Information Section of .ko File[4] [top]

The modinfo utility will display the module information stored in a kernel modules file (a .ko file).

 modinfo blob.ko
    

Listing Modules in the Kernel[5] [top]

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
    

Compiling modules[6] [top]

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.

Inserting/Removing Kernel Modules[7] [top]

The utilities for inserting/removing modules are

 insmod
 rmmod      
    

A kernel module source code[8] [top]

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

Module Parameters[9] [top]

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.

Example[10] [top]

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");

Passing Parameters[11] [top]

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

The strace command[12] [top]

As an aside, the strace command outputs all the system calls made when a program runs.

Why static[13] [top]

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 Drivers[14] [top]

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/Minor Device Numbers[15] [top]

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.

The /dev Directory[16] [top]

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)

File Interface (also used by devices)[18] [top]


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);
};

Assigning Members of the file_operations struct[19] [top]

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.

Registering a Device[20] [top]

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.

A char device module[21] [top]

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 *);

Installing the Device driver[22] [top]

chardev.c[23] [top]

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 *);

Create device files[24] [top]

Insert the module

What is the major number

Create 2 device files with this major number and minor numbers 0 and 1

Using cat and echo [25] [top]

Demonstrating reading and writing

Program to read the char device[26] [top]


#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 proc file system and modules[27] [top]

The module utility can also be used to create "files" in the proc file system.

Two examples:

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.