Linux Kernel and Modules/Drivers – Basics

In this section we learn learn what really a Kernel is. Linux Kernel is a way to have a centralised interface between a User (User Application) and a Machine (Hardware like Computer etc.). So, allows a user to talk to the hardware of the computer in more direct and efficient way. So user application need NOT know all about the complexity involved in how to talk to a hardware of a computer machine.


A graphical organisation of the kernel is as follows (given in the below picture).

We have a hardware which is your machine and you may have other hardware like Pen-drive, Mouse, Keyboard attached to your computer. All these hardware are managed by Kernel. And in turn you have “shell” programs or “User Applications” that talk to kernel and make requests to handle the hardware attached to computer. The requests could be in the form of writing some files to Hard-drive, displaying something on the Monitor Screen, connecting to Internet, etc.

Now we can use another graphical representation to show how the “User Space” and “Kernel” are associated with each other.

The “User space” or “Shell” applications make requests to “Kernel”. Then the Kernel satisfies those requests by talking to the hardware.

 

1.1 What is there inside the Kernel?:

Inside the Kernel, you will find the following things,

1. Process Schedulers: These allow the Kernel to give equal time to different Processes. Processes are nothing but the programs that are actively running in memory.

2. Memory Manager: allow to manage a way to share and allocate memory space for different applications. This will ensure that processes run nicely with each other.

3. I/O Scheduler: allow for use of the Keyboard and other writing operations.

4. IPC: These are Inter-process communications. These will support multi-threading allowing for some of the concepts like semaphores. A semaphore is a value in a designated place in operating system (or Kernel) storage that each process can check and then change. Depending on the value that is found, the process can use the resource or will find that it is already in use and must wait for some period of time before trying again. For example, if a device is being used by a process, then another process will come to know that the device is busy by another process and will wait till the device is ready. This is possible only if there is synchronisation between the two processes which is made possible by IPCs.

5. Networking: the kernel has ability to interface with network services.

6. File System IO: A Linux Kernel has a virtual file system. These virtual file systems support talking to different file systems inside Linux in a uniform manner.

7. Device Driver: The above file systems talk to the Device driver. And the device driver in turn talks to the Hardware Peripherals.

We already know that the “user space” and “shell” applications talk to Linux “Kernel” making requests. Kernel always run in a very “Privileged Environment” and in that it can access any aspects of the computer and it can access the whole range of memory and can talk to all types of hardware attached to the computer. Whereas in “user space”, it has very limited access to number of resources including memory and including hardware devices like monitors, keyboard, etc. So, “user space” has to make all of its requests via the “Kernel”. The “Kernel” in privileged mode talks to the Hardware and satisfies the requests of the user or shell applications.

What you can’t do here is to make the user or shell applications directly talk to the hardware. So, user or shell applications have to rely on the Kernel to make those requests to Hardware. The requests made to Kernel are called “System Calls”. Then the Kernel will make its own form of call to the Hardware via “Interrupts”. Then the Hardware Interrupts the Kernel saying it has data for Kernel OR Kernel talks to the hardware by writing the information to the hardware.

1.2 Get the Source Code of Linux Kernel:

You can get the source code of the “kernel” from kernel.org site by downloading manually. Sometimes, the sources of kernels can be installed directly on your computer file system by using the package repositories of your Linux. It is mandatory to have source files installed for the current kernel release version of your Linux OS. To check which version of Kernel is installed in your computer, issue below command,

Command: uname -r

It will show something like “4.4.0-93-generic”. Here the first number is major number (4), second is the minor number (4) and third number is the revision number (0).

Note: If the minor number is even then it is stable version. Or else it is experimental version.

The path location for the kernel source is at “/usr/src”. If you goto the path then you can find a number of sources installed for different kernel versions.

vijay@vijay-X55U:~$ cd /usr/src

vijay@vijay-X55U:/usr/src$ ls

linux-headers-4.4.0-89 linux-headers-4.4.0-93

linux-headers-4.4.0-89-generic linux-headers-4.4.0-93-generic

linux-headers-4.4.0-92 ndiswrapper-1.60

linux-headers-4.4.0-92-generic ndiswrapper.tar.bz2

vijay@vijay-X55U:/usr/src$

If you are installing fresh source files manually, then you have to download the zip file corresponding to your kernel and extract / unzip the zip file to the path “/usr/src”.

2.0 INTRODUCTION TO LINUX MODULE / DRIVER:

A Linux Module is a piece of Binary code that is inserted into and unloaded out of the static Kernel image at runtime. The Kernel is one monolatic and static binary which runs when computer boots. It has many functionalities, but to make the Linux OS more flexible and more dynamic we are able to add more functionalities to the Linux Kernel so that you don’t have to rebuild entire Kernel from scratch and reboot the operating system after re-compiling and re-building the linux Kernel.

For example, if you insert a new hardware device into your computer, then you don’t have to re-build your Kernel to add the features to support the device. Instead, you can just write a Module to support the device and insert the module into Kernel when you are using the device and uninstall the Module when you are not using the device anymore. This is the benefit that you get from Linux Modules.

2.1 How the Module Works?

If you want to add new functionality (i.e. support for a new hardware device) into Kernel, then you write a Module. Modules are written in C (I don’t know if other programming languages are used or not). You just compile the module from source file to get a binary which has “.ko” file extension.

To Insert the Binary Module into Kernel Image, just call the command “insmod” and a method inside the Module (module_init) will be called which tells to inject the Module that we have just created into the Kernel. Then the Module registration happens inside the Kernel. Module will does the things whatever it needs to do like read / write operations from / to the device.

If you are done with the Module, you can remove (take out) the Module out of Kernel using the command “rmnod”. This will invoke the “module_exit” method inside the Module and will get unregistered from the Kernel Image.

Note: During registering of Kernel, the Kernel fetches the information from Module which tells the Kernel about the features of the Module and its capabilities like what are all the things it can do.

3.0 CREATING A SIMPLE KERNEL MODULE:

Now, let us directly jump into Kernel Module Programming. The things that you need for this are,

a). A Ubuntu Linux Computer with Terminal Window.

b). C compiler “gcc” installed.

The detailed steps for creating simple Linux Kernel Modules is as below,

1. Create a new folder (for example) “/home/vijay/VKS_Module”, somewhere in you Ubuntu Linux.

2. Open the “gnome-terminal” window and type the command “cd /home/vijay/VKS_Module” to change the directory to the folder.

3. Now, I create a file called “Hello.c” inside the folder using “gedit”. The contents of the file “Hello.c” is as given below,

#include <linux/init.h>

#include <linux/module.h>

static int hello_init(void)

{

printk(KERN_ALERT “TEST: Hello World. VKSALIAN\n”);

return 0;

}

static void hello_exit(void)

{

printk(KERN_ALERT “TEST: Good Bye. VKSALIAN\n”);

}

module_init(hello_init);

module_exit(hello_exit);

The above file is the source code for the module that we are going to produce now. The include file “linux/init.h” is used for initialization of the module. The include file “linux/module.h” is used to indicate that the file is to create a module. Then we are writing two “static” functions named “hello_init” and “hello_exit” to execute code during module initialization and module exit.

Note: The “static” function can have only one instance which is needed by the technology here.

Please note that you can not use “printf” function (which is limited to user-space) in kernel space so you are using “printk” instead. The “KER_ALERT” prioritizes the log message to be displayed while module is inserted into / taken out of the Kernel. The last two lines are used to link the custom function name with standard function name. Also, note that we are not sing any main() here.

At the end, we Pass the pointer to “hello_exit” and “hello_init” to the standard functions being called which are “module_init” and “module_exit”.

4. Now to compile the module let us create a “Makefile”. The filename has no extensions. The contents of the make file are as given below,

obj-m += Hello.o

KDIR = /usr/src/linux-headers-4.4.0-92-generic

all:

$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:

rm -rf *.o *.ko *.mod.* *.symvers *.order

The obj-m says to use object file “Hello.o”. The “KDIR” is used to specifiy the path to source files of the Linux Kernel being used in your computer. In my computer, I found that the present kernel is named as “linux-headers-4.4.0-92-generic” after issuing a command “uname -r”. So, there must be a folder named “linux-headers-4.4.0-92-generic” inside the path “/usr/src” which contains the source files for the Kernel.

Note: The below is the short description for the assignment types used in Makefile. This will give you a clear Idea on how to use them without any error.

1. Lazy Set (=): Using = causes the variable to be assigned a value. If the variable already had a value, it is replaced. This value will be expanded when it is used.

2. Immediate Set (:=): Using := is similar to = but the value will be expanded during the assignment.

3. Set If Absent (?=): Using ?= setting of variable will be done only if it does not have a value.

4. Append (+=): Using += we append the supplied value to the existing value (or to set the value to that variable if it does not exist already).

The Lazy set and Immediate set can be differentiated as below,

VAL = foo

VARIABLE = $(VAL)

VAL = bar

# VARIABLE and VAL will both evaluate to “bar” if displayed here

# This is because, “=” for VARIABLE make it expand with current value with lazy assign

VAL = foo

VARIABLE := $(VAL)

VAL = bar

# VAL will evaluate to “bar”, but VARIABLE will evaluate to “foo” if displayed here

# This means := sign for VARIABLE makes it expand the assigned value during assignment and hence takes the instant value

5. Now just type a command “make” inside the terminal window. After “ls” command, I found the following files in the current directory.

vijay@vijay-X55U:~/VKS_Module$ make

make -C /usr/src/linux-headers-4.4.0-92-generic SUBDIRS=/home/vijay/VKS_Module modules

make[1]: Entering directory ‘/usr/src/linux-headers-4.4.0-92-generic’

Building modules, stage 2.

MODPOST 1 modules

make[1]: Leaving directory ‘/usr/src/linux-headers-4.4.0-92-generic’

vijay@vijay-X55U:~/VKS_Module$ ls

Hello.c Hello.ko Hello.mod.c Hello.mod.o Hello.o Makefile modules.order Module.symvers

vijay@vijay-X55U:~/VKS_Module$

6. Out of all files the one we need is the file “Hello.ko” which is the Module that we just generated. To insert the module into the kernel, type command,

sudo insmod Hello.ko

To see if the module has been inserted into the kernel, just run command “dmesg | tail” and observe the output.

vijay@vijay-X55U:~/VKS_Module$ sudo insmod Hello.ko

[sudo] password for vijay:

vijay@vijay-X55U:~/VKS_Module$ dmesg | tail

[17278.318467] cfg80211: (2402000 KHz – 2472000 KHz @ 40000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318471] cfg80211: (2457000 KHz – 2482000 KHz @ 40000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318475] cfg80211: (2474000 KHz – 2494000 KHz @ 20000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318480] cfg80211: (5170000 KHz – 5250000 KHz @ 80000 KHz, 160000 KHz AUTO), (N/A, 2000 mBm), (N/A)

[17278.318485] cfg80211: (5250000 KHz – 5330000 KHz @ 80000 KHz, 160000 KHz AUTO), (N/A, 2000 mBm), (0 s)

[17278.318489] cfg80211: (5490000 KHz – 5730000 KHz @ 160000 KHz), (N/A, 2000 mBm), (0 s)

[17278.318492] cfg80211: (5735000 KHz – 5835000 KHz @ 80000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318496] cfg80211: (57240000 KHz – 63720000 KHz @ 2160000 KHz), (N/A, 0 mBm), (N/A)

[17283.826088] IPv6: ADDRCONF(NETDEV_UP): wlp1s0: link is not ready

[19145.192708] TEST: Hello World. VKSALIAN

vijay@vijay-X55U:~/VKS_Module$

To remove the module from kernel, type command as,

sudo rmmod Hello

vijay@vijay-X55U:~/VKS_Module$ sudo rmmod Hello

vijay@vijay-X55U:~/VKS_Module$ dmesg | tail

[17278.318471] cfg80211: (2457000 KHz – 2482000 KHz @ 40000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318475] cfg80211: (2474000 KHz – 2494000 KHz @ 20000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318480] cfg80211: (5170000 KHz – 5250000 KHz @ 80000 KHz, 160000 KHz AUTO), (N/A, 2000 mBm), (N/A)

[17278.318485] cfg80211: (5250000 KHz – 5330000 KHz @ 80000 KHz, 160000 KHz AUTO), (N/A, 2000 mBm), (0 s)

[17278.318489] cfg80211: (5490000 KHz – 5730000 KHz @ 160000 KHz), (N/A, 2000 mBm), (0 s)

[17278.318492] cfg80211: (5735000 KHz – 5835000 KHz @ 80000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318496] cfg80211: (57240000 KHz – 63720000 KHz @ 2160000 KHz), (N/A, 0 mBm), (N/A)

[17283.826088] IPv6: ADDRCONF(NETDEV_UP): wlp1s0: link is not ready

[19145.192708] TEST: Hello World. VKSALIAN

[19272.455658] TEST: Good Bye. VKSALIAN

vijay@vijay-X55U:~/VKS_Module$

4.0 CREATING A SIMPLE KERNEL MODULE WITH PARAMETER PASSING:

1. Create or update the file as below,

#include <linux/init.h>

#include <linux/module.h>

// Include the below to able for creating variables

#include <linux/moduleparam.h>

int param_var = 0;

//module_param(name_var, data_type, permissions)

// Permissions : S_IRUSR, S_IWUSR, S_IXUSER, S_IWGRP, S_IRGRP, S_IRGRP

// USR stands for User and GRP stands for GRP. R – Read, W – Write

module_param(param_var, int, S_IRUSR | S_IWUSR);

// To display the variable value

void display(void)

{

printk(KERN_ALERT “TEST: param=%d. VKSALIAN\n”, param_var);

}

static int hello_init(void)

{

printk(KERN_ALERT “TEST: Hello World. VKSALIAN\n”);

display();

return 0;

}

static void hello_exit(void)

{

printk(KERN_ALERT “TEST: Good Bye. VKSALIAN\n”);

}

module_init(hello_init);

module_exit(hello_exit);

To enable the passing of parameters, you must include an additional header file named “linux/moduleparam.h”. To create a new variable use “int param_var = 0”. To register the variable use the line “module_param(param_var, int, S_IRUSR | S_IWUSR)”. Now, a new function “display()” is introduced which is used to display the value of the variable “param_var”. Note that you must use “void” inside the brackets of display function otherwise it will not get compiled.

2. Now, let us compile the C file to generate the module Hello.ko. We use the same “Makefile” here again.

Command : make

3. Now, insert the module into kernel by below command with the value that need to be assigned to the variable inside the module,

Command : insmod Hello.ko param_var=9999

vijay@vijay-X55U:~/VKS_Module/mod2$ sudo insmod Hello.ko param_var=9999

[sudo] password for vijay:

vijay@vijay-X55U:~/VKS_Module/mod2$ dmesg | tail

[17278.318480] cfg80211: (5170000 KHz – 5250000 KHz @ 80000 KHz, 160000 KHz AUTO), (N/A, 2000 mBm), (N/A)

[17278.318485] cfg80211: (5250000 KHz – 5330000 KHz @ 80000 KHz, 160000 KHz AUTO), (N/A, 2000 mBm), (0 s)

[17278.318489] cfg80211: (5490000 KHz – 5730000 KHz @ 160000 KHz), (N/A, 2000 mBm), (0 s)

[17278.318492] cfg80211: (5735000 KHz – 5835000 KHz @ 80000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318496] cfg80211: (57240000 KHz – 63720000 KHz @ 2160000 KHz), (N/A, 0 mBm), (N/A)

[17283.826088] IPv6: ADDRCONF(NETDEV_UP): wlp1s0: link is not ready

[22947.934327] TEST: Hello World. VKSALIAN

[22947.934337] TEST: param=9999. VKSALIAN

vijay@vijay-X55U:~/VKS_Module/mod2$

The above is the output you are going to get.

4. Now, to remove the module from kernel, use the command below,

Command : rmmod Hello.ko

vijay@vijay-X55U:~/VKS_Module/mod2$ sudo rmmod Hello

vijay@vijay-X55U:~/VKS_Module/mod2$ dmesg | tail

[17278.318485] cfg80211: (5250000 KHz – 5330000 KHz @ 80000 KHz, 160000 KHz AUTO), (N/A, 2000 mBm), (0 s)

[17278.318489] cfg80211: (5490000 KHz – 5730000 KHz @ 160000 KHz), (N/A, 2000 mBm), (0 s)

[17278.318492] cfg80211: (5735000 KHz – 5835000 KHz @ 80000 KHz), (N/A, 2000 mBm), (N/A)

[17278.318496] cfg80211: (57240000 KHz – 63720000 KHz @ 2160000 KHz), (N/A, 0 mBm), (N/A)

[17283.826088] IPv6: ADDRCONF(NETDEV_UP): wlp1s0: link is not ready

[22947.934327] TEST: Hello World. VKSALIAN

[22947.934337] TEST: param=9999. VKSALIAN

[23093.951252] TEST: Good Bye. VKSALIAN

vijay@vijay-X55U:~/VKS_Module/mod2$

The above is the output you are going to get.

5.0 CREATING A SIMPLE KERNEL MODULE WITH PARAMETER “ARRAY”PASSING:

1. I have modified the earlier “integer” as “integer array” and the below is the updated Hello.c file.

#include <linux/init.h>

#include <linux/module.h>

// Include the below to able for creating variables

#include <linux/moduleparam.h>

// Use Array here

int param_var[3] = {0,0,0};

//module_param(name_var, data_type, permissions)

// Permissions : S_IRUSR, S_IWUSR, S_IXUSER, S_IWGRP, S_IRGRP, S_IRGRP

// USR stands for User and GRP stands for GRP. R – Read, W – Write

module_param_array(param_var, int, NULL, S_IRUSR | S_IWUSR);

// To display the variable value

void display(void)

{

printk(KERN_ALERT “TEST: param1=%d. VKSALIAN\n”, param_var[0]);

printk(KERN_ALERT “TEST: param2=%d. VKSALIAN\n”, param_var[1]);

printk(KERN_ALERT “TEST: param3=%d. VKSALIAN\n”, param_var[2]);

}

static int hello_init(void)

{

printk(KERN_ALERT “TEST: Hello World. VKSALIAN\n”);

display();

return 0;

}

static void hello_exit(void)

{

printk(KERN_ALERT “TEST: Good Bye. VKSALIAN\n”);

}

module_init(hello_init);

module_exit(hello_exit);

2. Issue “make” command for compiling the source file.

3. Now, install the module by issuing command,

Command: sudo insmod Hello.ko param_var=33,66,99

NOTE: Do not forget to check if you have the current active kernel source directory path used in the “Makefile”.The following errors will encounter if you are using wrong kernel

vijay@vijay-X55U:~/VKS_Module/mod2$ make

make -C /usr/src/linux-headers-4.4.0-92-generic SUBDIRS=/home/vijay/VKS_Module/mod2 modules

make[1]: Entering directory ‘/usr/src/linux-headers-4.4.0-92-generic’

^[[A CC [M] /home/vijay/VKS_Module/mod2/Hello.o

Building modules, stage 2.

MODPOST 1 modules

WARNING: modpost: missing MODULE_LICENSE() in /home/vijay/VKS_Module/mod2/Hello.o

see include/linux/module.h for more information

CC /home/vijay/VKS_Module/mod2/Hello.mod.o

LD [M] /home/vijay/VKS_Module/mod2/Hello.ko

make[1]: Leaving directory ‘/usr/src/linux-headers-4.4.0-92-generic’

vijay@vijay-X55U:~/VKS_Module/mod2$ sudo insmod Hello.ko param_var=33,66,99

insmod: ERROR: could not insert module Hello.ko: Invalid module format

Note: ERROR!!!! In my case, the computer booted using kernel version “4.4.0-93-generic”, but I had still the path to kernel source in Makefile as “4.4.0-92-generic”. This was giving the problem to me by saying “insmod: ERROR: could not insert module Hello.ko: Invalid module format”. So, I edited the path to kernel source in “Makefile” to suite the current kernel booted. The new Makefile is,

obj-m += Hello.o

KDIR = /usr/src/linux-headers-4.4.0-93-generic

all:

$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:

rm -rf *.o *.ko *.mod.* *.symvers *.order

4. Now, recompile the module with “make” command.

5. Now, install the module by issuing command,

Command: sudo insmod Hello.ko param_var=33,66,99

Note: Since you have used the param_var as array of int with size 3, you need to pass three integer values as “param_var=33,66,99”.

6. Now, use below command to see the kernel messages,

Command: dmesg | tail

The log I got,

vijay@vijay-X55U:~/VKS_Module/mod2$ make

make -C /usr/src/linux-headers-4.4.0-93-generic SUBDIRS=/home/vijay/VKS_Module/mod2 modules

make[1]: Entering directory ‘/usr/src/linux-headers-4.4.0-93-generic’

Building modules, stage 2.

MODPOST 1 modules

WARNING: modpost: missing MODULE_LICENSE() in /home/vijay/VKS_Module/mod2/Hello.o

see include/linux/module.h for more information

CC /home/vijay/VKS_Module/mod2/Hello.mod.o

LD [M] /home/vijay/VKS_Module/mod2/Hello.ko

make[1]: Leaving directory ‘/usr/src/linux-headers-4.4.0-93-generic’

vijay@vijay-X55U:~/VKS_Module/mod2$ sudo insmod Hello.ko param_var=33,66,99

[sudo] password for vijay:

vijay@vijay-X55U:~/VKS_Module/mod2$ dmesg | tail

[ 135.387497] IPv6: ADDRCONF(NETDEV_CHANGE): enp2s0: link becomes ready

[ 1466.991754] Hello: disagrees about version of symbol module_layout

[ 1524.870478] Hello: disagrees about version of symbol module_layout

[ 1671.426282] Hello: disagrees about version of symbol module_layout

[ 1713.429411] Hello: disagrees about version of symbol module_layout

[ 2009.856176] Hello: disagrees about version of symbol module_layout

[ 2058.922181] Hello: disagrees about version of symbol module_layout

[ 2976.986149] perf interrupt took too long (2518 > 2500), lowering kernel.perf_event_max_sample_rate to 50000

[ 3114.852193] Hello: module license ‘unspecified’ taints kernel.

[ 3114.852215] Disabling lock debugging due to kernel taint

[ 3114.852316] Hello: module verification failed: signature and/or required key missing – tainting kernel

[ 3114.854523] TEST: Hello World. VKSALIAN

[ 3114.854532] TEST: param1=33. VKSALIAN

[ 3114.854535] TEST: param2=66. VKSALIAN

[ 3114.854537] TEST: param3=99. VKSALIAN

vijay@vijay-X55U:~/VKS_Module/mod2$

vijay@vijay-X55U:~/VKS_Module/mod2$

Note: You Still see some warnings in the above log. But its OK to continue as of now.

6.0 INTRODUCTION TO DEVICE DRIVERS / MODULES:

Let us introduce Device Drivers in Linux. Device drivers are used to communicate with the Hardwares attached to computers. The Device Driver is a piece of code which is a part of the kernel and is responsible for communicating with Hardwares attached to computer. When user connects his device to computer a device file will created in the hard-drive. For example “/dev/sdb1”, etc. When the user application wants to read from or write to the device attached, he will invoke the device file to write or read information first and the device file will invoke the device driver which is meant for the device. The Device driver talks with actual hardware to request user supply.

Suppose if you have attached devices of the same type to a computer, then the user uses the same device driver to talk to all these devices. All these devices will have different device file name assigned in the hard-drive. But the same device driver is attached to all the devices. To differentiate between the devices, we use something called “Major Number” and “Minor Number”. The device driver will have “Major Number” and the devices will have “Minor Number”. Minor number differentiates between each device so that device driver comes to know which device to access. Major number is used to by device driver to know which are the devices use this driver.

*******IMPROVE ANSWER*************

6.1. Different Types of Device Drivers:

The Device drivers can be subdivided into Two main types,

1. Character Device Driver:

a). The character device driver writes to or reads from device character by character.

b). It operates in Blocking Mode. That means when user writes a information to the device, the user must wait till the previous operation to finish before continuing a new one. That means user has to wait.

c). Character Devices are the most common of all device drivers.

2. Block Device Driver:

a). The character device driver writes to or reads from device block by block with large amount of information at a time.

b). It is CPU intensive and operations take long time to complete.

c). It is asynchronous with operations.

NOTE: In this entire tutorial, we concentrate on Character Device Drivers.

6.1.1. Character Device Driver:

When an user application wants to write anything to a Physical device, it can not not enter the kernel space directly. So, it makes use of something called “device file” and user application uses the device file to write information to or get information from Physical Device. The user application can open, close, read information from or write information to the device file. So, the first step is always to create a device file. Remember that the physical devices reside in Kernel Space and the user application can not enter the Kernel space directly. So, the only way is that user application talkes to device drivers using a device file for read or write operations to actual physical device.

Now, let us see how a “device file” looks like. For this, just open a terminal window and got “/dev” folder to list all contents of it using command “ls”. The files that you se e are all the device files for the physical devices attached to your computer. The below is the list of device files that I had in my computer.

vijay@vijay-X55U:~$ cd /dev

vijay@vijay-X55U:/dev$ ls

autofs kmsg sda13 tty23 tty57 ttyS31

block kvm sda14 tty24 tty58 ttyS4

bsg lightnvm sda15 tty25 tty59 ttyS5

btrfs-control log sda2 tty26 tty6 ttyS6

bus loop0 sda3 tty27 tty60 ttyS7

cdrom loop1 sda4 tty28 tty61 ttyS8

cdrw loop2 sda5 tty29 tty62 ttyS9

char loop3 sda6 tty3 tty63 uhid

console loop4 sda7 tty30 tty7 uinput

core loop5 sda8 tty31 tty8 urandom

cpu loop6 sda9 tty32 tty9 userio

cpu_dma_latency loop7 sg0 tty33 ttyprintk v4l

cuse loop-control sg1 tty34 ttyS0 vcs

disk mapper shm tty35 ttyS1 vcs1

dri mcelog snapshot tty36 ttyS10 vcs2

dvd media0 snd tty37 ttyS11 vcs3

dvdrw mem sr0 tty38 ttyS12 vcs4

ecryptfs memory_bandwidth stderr tty39 ttyS13 vcs5

fb0 mqueue stdin tty4 ttyS14 vcs6

fd net stdout tty40 ttyS15 vcsa

full network_latency tty tty41 ttyS16 vcsa1

fuse network_throughput tty0 tty42 ttyS17 vcsa2

hidraw0 null tty1 tty43 ttyS18 vcsa3

hpet port tty10 tty44 ttyS19 vcsa4

hugepages ppp tty11 tty45 ttyS2 vcsa5

hwrng psaux tty12 tty46 ttyS20 vcsa6

i2c-0 ptmx tty13 tty47 ttyS21 vfio

i2c-1 pts tty14 tty48 ttyS22 vga_arbiter

i2c-2 random tty15 tty49 ttyS23 vhci

i2c-3 rfkill tty16 tty5 ttyS24 vhost-net

i2c-4 rtc tty17 tty50 ttyS25 video0

i2c-5 rtc0 tty18 tty51 ttyS26 zero

i2c-6 sda tty19 tty52 ttyS27

i2c-7 sda1 tty2 tty53 ttyS28

initctl sda10 tty20 tty54 ttyS29

input sda11 tty21 tty55 ttyS3

kfd sda12 tty22 tty56 ttyS30

vijay@vijay-X55U:/dev$

Now, let us consider a device file “tty0”. Type the command “ls -l tty0” to get the information of the device file “tty0”. You will get something like this.

vijay@vijay-X55U:/dev$ ls -l tty0

crw–w—- 1 root tty 4, 0 Sep 1 10:17 tty0

vijay@vijay-X55U:/dev$

The above information has following meaning.

crw – c indicates that the device file is a character device file.

crwrw indicates that the device file is in both readable and writable.

tty 4, 0 – The number 4 is the major number. That means there is a device driver in the kernel which has a major number 4 which is for this device.

tty 4, 0 – The number 0 is the minor number. There may may be other devices like tty1, tty2, etc. which too have the same major number 4. But each device file has different minor numbers like tty1 has minor number 1 and tty2 has minor number 2, etc.

So, even though the devices tty0, tty1, etc have the same major number 4 indicating that they use common device driver with major number 4, the device driver differentiates between these devices by looking at the minor number of each device.

6.1.2. Creating a Custom Device File (DUMMY):

To create a device file, you need to issue a command something like this,

sudo mknod /dev/MyTestDeviceFile c 900 1

Note: Never forget to be a super user for this command. The “mknod” is the command to create a “device file” and the filename for the device file is “MyTestDeviceFile”. The “c” stands for character device. The “900” is the major number. The “1” is the minor number.

Note: The Major Number has to be unique. Otherwise there will be some conflicts.

To check if the command has been created, issue command “ls -l My*” after executing command “cd /dev”. The sample output is as below,

crw-r–r– 1 root root 900, 1 Sep 1 11:37 MyTestDeviceFile

The significant of the this device file “MyTestDeviceFile” is that it is going to be “tied” directly to the device driver that we are going to write. With this, we as user open the device file using a user application and which in turn invokes the device driver to which the device file is tied to. Then the device driver will talk to the Physical Device.

7.0 WRITING A (FAKE or DUMMY) DEVICE DRIVER:

Here with the “fake” device driver written, we will will now going to write a device driver for the device. The below is the driver file written by referring the video,

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h> // file operations structure- which allows use to open/close, read/write to device

#include <linux/cdev.h> // this is a char driver; makes cdev available. Helps to register the driver to kernel

#include <linux/semaphore.h> // used to access semaphores;synchronous behaviour.

#include <asm/uaccess.h> // copy_to_user and copy_from_user. To map data from user space to kernel space and vice versa

//(1) Create a stucture of our fake device

// This device is capable of handling 100 characters

// The semaphore is to prevent the data corruption when accessed by multiple processes occupy this fake devices at the same time

struct fake_device {

char data[100]; // This is for storing the data

struct semaphore sem; // used for synchronise between various processes trying to access the device at a time without allowing data curruption

} virtual_device;

//(2) Declare a couple of variables.

// Note: All the variables are created in Global space (here in this place) because the stack of Kernel is very small and if we declare the variables inside function might cause crashing of the Kernel stack.

//To later register our device we need a cdev object and some other variables

struct cdev *mcdev; // “m” stands for “my”. cdev refers to character device driver

int major_number; // Used to Store Major Number. This number will extracted from the structure dev_t below using macro – mknod /dev/file_name c major minor

int ret; // This is used to hold the return values of functions;

dev_t dev_num; // Holds the major number that kernel gives us

// Name of our Device Driver

#define DEVICE_NAME “fake_device”

//(7) Called on device_file open

// a). It has inode refrence to the file on disk

// and contains information about that file

// b). struct file represents an abstract open file

int device_open (struct inode *inode, struct file *filp) {

// only allow one process to open this device by using a semaphore as mutual exclusive lock. mutex

if (down_interruptible(&virtual_device.sem) != 0) {

printk(KERN_ALERT “fake_device: could not lock device during open”);

return -1;

}

printk(KERN_INFO “fake_device: opened device”);

return 0;

}

//(8) Called when user wants to get information from device

ssize_t device_read(struct file* filp, char* bufStoreData, size_t bufCount, loff_t* curOffset) {

//take data from kernel space(device) to user_space(process or user application)

// copy_to_user (destination, source, sizeToTransfer)

printk(KERN_INFO “fake_device: Reading from device”);

ret = copy_to_user(bufStoreData, virtual_device.data, bufCount);

return ret;

}

//(9) Called when user wants to send information to the device

ssize_t device_write (struct file* filp, const char* bufSourceData, size_t bufCount, loff_t* curOffset) {

//send data from user to kernel

//copy_from_user (dest, source, count)

printk(KERN_INFO “fake_device: writing to device”);

ret = copy_from_user(virtual_device.data, bufSourceData, bufCount);

return ret;

}

//(10) Called upon user close

int device_close (struct inode *inode, struct file *filp) {

//by calling up, which is opposite of down for semaphore, we release the mutex that we obtained at device open

// this has the effect of allowing other process to use the device now

up(&virtual_device.sem);

printk(KERN_INFO “fake_device: closed device”);

return 0;

}

//(6) Tell the kernel which functions to call when user operates on our device file. We call this as “file operation structure”.

// Note: Here we declare the callback functions that will be executed as requested by user

struct file_operations fops = {

.owner = THIS_MODULE, // prevent unloading of this module when operations are in use

.open = device_open, // points to the method to call when opening the device

.release = device_close,// points to the method to call when closing the device

.write = device_write, // points to the method to call when writing to the device

.read = device_read // points to the method to call when reading from the device

};

// Note: that all the methods are implemented in the subsequent steps

// Device Driver Entry Point

static int driver_entry (void)

{

//(3) Register our Character Device driver with the system; a two step process

//Step 1. Use dynamic allocation to assign our device

// a major number– alloc chrdev_region(dev_t*, uint fminor, uint count, char* name)

ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);

if(ret<0) { //at time kernel functions return negatives, there is an error

printk(KERN_ALERT “fake_device: failed to allocate a major number”);

return ret; // Propogate error

}

major_number = MAJOR(dev_num); // The dev_num contains the major number and hence we extract major_number from it and store in our variable “major_number”

printk(KERN_INFO “fake_device: major number is %d”, major_number);

printk(KERN_INFO “\tuse \”mknod /dev/%s c %d 0\” for device file”,DEVICE_NAME, major_number);

// Above, we are asking user to create a character device

//Step 2. Allocate the character device Structure

mcdev = cdev_alloc(); //create our cdev structure, initialized our cdev

mcdev->ops = &fops; // Passing pointer for stcruct file operations.

//Note: fops stands for file operations.

mcdev->owner = THIS_MODULE;

// Now that we created cdev, we have to add it to the Kernel

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

ret = cdev_add(mcdev, dev_num, 1); // add to character device to the system

if(ret <0) { // always check for errors

printk(KERN_ALERT “fake_device: unable to add cdev to kernel”);

return ret;

}

//(4) Initialize our semaphore

sema_init(&virtual_device.sem, 1); // initial value of one

return 0;

}

// De-Initialization function

// It is responsible for undoing everything that we did in reverse order

static void driver_exit (void) {

// (5) Unregister everything in reverse order

// (a)

cdev_del(mcdev);

//(b)

unregister_chrdev_region(dev_num, 1);

printk(KERN_ALERT “fake_device: unloaded module”);

}

// Inform the kernel where to start and stop with our module/driver

module_init(driver_entry);

module_exit(driver_exit );

Now, let us compile the Device Driver suing the below make file.

File : Makefile

obj-m := fake_driver.o

K_DIR = /usr/src/linux-headers-$(shell uname -r)

all:

$(MAKE) -C $(K_DIR) SUBDIRS=$(PWD) modules

clean:

rm -rf *.o *.ko *.mod.* *.symvers *.order *~

Now, let us compile the fake driver (fake_driver.c) using the following command,

Command: make

vijay@vijay-X55U:~/VKS_Module/fake_driver$ make

make -C /usr/src/linux-headers-4.4.0-93-generic SUBDIRS=/home/vijay/VKS_Module/fake_driver modules

make[1]: Entering directory ‘/usr/src/linux-headers-4.4.0-93-generic’

CC [M] /home/vijay/VKS_Module/fake_driver/fake_driver.o

Building modules, stage 2.

MODPOST 1 modules

CC /home/vijay/VKS_Module/fake_driver/fake_driver.mod.o

LD [M] /home/vijay/VKS_Module/fake_driver/fake_driver.ko

make[1]: Leaving directory ‘/usr/src/linux-headers-4.4.0-93-generic’

vijay@vijay-X55U:~/VKS_Module/fake_driver$

Now, you will have a new device driver called “fake_driver.ko”. To install it use the “insmod” command and to verify the insertion of driver into kernel use “dmesg” command.

vijay@vijay-X55U:~/VKS_Module/fake_driver$ sudo insmod fake_driver.ko

[sudo] password for vijay:

vijay@vijay-X55U:~/VKS_Module/fake_driver$ dmesg | tail

[ 834.356820] cfg80211: (5490000 KHz – 5730000 KHz @ 160000 KHz), (N/A, 2000 mBm), (0 s)

[ 834.356824] cfg80211: (5735000 KHz – 5835000 KHz @ 80000 KHz), (N/A, 2000 mBm), (N/A)

[ 834.356827] cfg80211: (57240000 KHz – 63720000 KHz @ 2160000 KHz), (N/A, 0 mBm), (N/A)

[ 839.125135] IPv6: ADDRCONF(NETDEV_UP): wlp1s0: link is not ready

[ 2685.669333] perf interrupt took too long (2511 > 2500), lowering kernel.perf_event_max_sample_rate to 50000

[ 8722.508986] fake_driver: module license ‘unspecified’ taints kernel.

[ 8722.508998] Disabling lock debugging due to kernel taint

[ 8722.509070] fake_driver: module verification failed: signature and/or required key missing – tainting kernel

[ 8722.509530] fake_device: major number is 244

[ 8722.509535] use “sudo mknod /dev/fake_device c 244 0” for device file

vijay@vijay-X55U:~/VKS_Module/fake_driver$

Since this is a dummy Linux Driver, You need to create a new device file. Use the below command,

Command: sudo mknod /dev/fake_device c 244 0

Sometimes, you can not use the device file for reading / writing. In such cases, you need to set the permission for the device file using below command,

Command: sudo chmod 777 /dev/fake_device

vijay@vijay-X55U:~/VKS_Module/fake_driver$ sudo mknod /dev/fake_device c 244 0

[sudo] password for vijay:

vijay@vijay-X55U:~/VKS_Module/fake_driver$ ls -l /dev/fake*

crw-r–r– 1 root root 244, 0 Sep 5 12:44 /dev/fake_device

vijay@vijay-X55U:~/VKS_Module/fake_driver$

8.0. WRITE A USER APPLICATION TO VERIFY THE DRIVER:

To test if our application is working, we need to write a “User Application” which will access the “device file” and make requests for read/write operation. During this process, the driver/module will be invoked based on the “Major Number” and the correct driver will be used to do access/read/write operations.

The below is the User Application written for testing the device driver,

File : userApp.c

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h> // Used for File Control Operations

#include <unistd.h> // Used for accessing standard symbolic constants and types

// Define the device_file that we use in this User Application

#define DEVICE “/dev/fake_device”

// Note: the above device need to be creaed as,

// “sudo mknod /dev/fake_device c 244 0”

// Note: If you don’t have permissions to read/write

// to the device, then change the mode of file itself,

// “sudo chmod 777 /dev/fake_device”

int main (void) {

// Variables declaraion

int i; // number of files written

int fd; // File Descriptor

char ch; // represens command choice

char write_buf[100]; // write buffer (to device)

char read_buf[100]; // read buffer (from device)

// Open device for Read/Write Operation

fd = open (DEVICE, O_RDWR);

// Check for any error in opening device

if (fd == -1) {

printf(“Error Opening Device file \”%s\”, reason could be,\n (a). Device does not exist\n (b). Device has been locked by another process\n (c). Your application has no permission to open the Device\n …..try \”sudo chmod 777 /dev/your_device_file\”\n”, DEVICE);

exit(-1);

}

printf(“r = read from device\nw = write to device\nenter command:”);

scanf(“%c”, &ch);

switch (ch) {

case ‘w’:

printf(“enter the data:”);

scanf(” %[^\n]”,write_buf);

write(fd, write_buf, sizeof(write_buf));

break;

case ‘r’:

read(fd, read_buf, sizeof(read_buf));

printf(“device: %s\n”, read_buf);

break;

default :

printf(“invalid command\n”);

break;

}

close(fd); // Close the file

return 0;

}

Compile the User Application,

Command : gcc -o UserApp userApp.c

Run the user application,

Command : ./UserApp

Error!!!: I got the below message while running the User Application “UserApp”,

vijay@vijay-X55U:~/VKS_Module/fake_driver$ ./userApp

Error Opening Device file “/dev/fake_device”, reason could be,

(a). Device does not exist

(b). Device has been locked by another process

(c). Your application has no permission to open the Device

…..try “sudo chmod 777 /dev/your_device_file

In my case, the problem was (c). So I issue command below to solve the problem,

Command: sudo chmod 777 /dev/fake_device

vijay@vijay-X55U:~/VKS_Module/fake_driver$ sudo chmod 777 /dev/fake_device

[sudo] password for vijay:

vijay@vijay-X55U:~/VKS_Module/fake_driver$

If everything is OK with your device driver and the Device File, you should see output of the User Application as below,

vijay@vijay-X55U:~/VKS_Module/fake_driver$ ./userApp r = read from device

w = wrte to device

enter command:

The sample output is as given below,

vijay@vijay-X55U:~/VKS_Module/fake_driver$ ./userApp

r = read from device

w = write to device

enter command:w

enter the data:Hi, This is VKSALIAN, writing Data.

vijay@vijay-X55U:~/VKS_Module/fake_driver$ ./userApp

r = read from device

w = write to device

enter command:r

device: Hi, This is VKSALIAN, writing Data.

vijay@vijay-X55U:~/VKS_Module/fake_driver$

The dmesg output you will get something as below,

[22797.368957] fake_device: opened device

[22821.954621] fake_device: writing to device

[22821.954643] fake_device: closed device

[23021.120100] fake_device: opened device

[23024.129461] fake_device: Reading from device

[23024.129498] fake_device: closed device

9.0 DEBUGGING THE PROGRAM:

9.1 Failed to compile the Driver Source File:

The userApp.c compilation was giving error during compilation. To fix this, I just added one more header file which is #include <unistd.h>.

The error was ,

vijay@vijay-X55U:~/VKS_Module/mod3$ gcc -o UserApp userapp.c

userapp.c: In function ‘main’:

userapp.c:26:4: warning: implicit declaration of function ‘write’ [-Wimplicit-function-declaration]

write(fd, write_buf, sizeof(write_buf));

^

userapp.c:29:4: warning: implicit declaration of function ‘read’ [-Wimplicit-function-declaration]

read(fd, read_buf, sizeof(read_buf));

^

userapp.c:36:2: warning: implicit declaration of function ‘close’ [-Wimplicit-function-declaration]

close(fd);

^

vijay@vijay-X55U:~/VKS_Module/mod3$

9.2 Failed attempt to Write to Device:

ERRROR!!!!: I noticed that if you use “scanf(“%[^\n]”,write_buf);” you will not be able to write any data to the device because the User Application exits without allowing you to enter any data. So, you must use “scanf(” %[^\n]”,write_buf);”. Notice the difference, there is a space before % sign.

10.0. Add Module License Information:

Soon after the Header file declaration in source file of Linux Kernel Module, just place a line as below,

MODULE_LICENSE (“Dual BSD/GPL”);

—*****WRITE MORE********

11.0. How different is the Kernel Module from User Apps?

1). Kernel module needs to do init and exit very carefully. When you init you need to allocate some memory for buffer, etc. So, while exiting you must deallocate them. Or else it will result in memory leak. One more thing when you try to access a NULL values which will result in Kernel Panic which is more dangerous than the Segmentation fault that you get in user applications for the same case.

2). No printf and other functions can be used because no libc or other libraries. Instead we use printk.

3). Linux Kernel Header files (e.g. <include/linux>)

4). Bugs in device driver may crash kernel.

5). Kernel Driver Runs in kernel space.

6). Much greater concurrency in kernel modules

– interrupt and interrupt handler

– Timers

– SMP support (modern OS)

10. REFERENCES:

1. https://www.youtube.com/watch?v=-O6GsrmOUgY

2. https://www.youtube.com/watch?v=o768iZKtzBA

3. https://www.youtube.com/watch?v=S8hifIrDh-g

4. https://www.youtube.com/watch?v=4z-nSTxUAIA

5. https://www.youtube.com/watch?v=_EcDreXNpO8

6. https://www.youtube.com/watch?v=hr-3rKA2Oxs

7. https://www.youtube.com/watch?v=_4H-F_5yDvo

8. https://www.youtube.com/watch?v=UdeGTRvPjK4

9. http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/

10. https://www.youtube.com/watch?v=NYRhkGrt4Q4

11. https://www.youtube.com/watch?v=5IDL070RtoQ

Leave a Reply

Your email address will not be published. Required fields are marked *