A Simple Introduction to Device Drivers under Linux

by Valerie Henson07/05/2007

Since the misty days of yore, the first step in learning a newprogramming language has been writing a program that prints "Hello,world!" (See the Hello World Collectionfor a list of more than 300 "Hello, world!" examples.) In this article,we will use the same approach to learn how to write simple Linux kernelmodules and device drivers. We will learn how to print "Hello, world!"from a kernel module three different ways: printk(), a /proc file, and a device in /dev.

Preparation: Installing Kernel Module Compilation Requirements

For the purposes of this article, a kernel module is a piece ofkernel code that can be dynamically loaded and unloaded from therunning kernel. Because it runs as part of the kernel and needs tointeract closely with it, a kernel module cannot be compiled in avacuum. It needs, at minimum, the kernel headers and configuration forthe kernel it will be loaded into. Compiling a module also requires aset of development tools, such as a compiler. For simplicity, we willbriefly describe how to install the requirements to build a kernelmodule using Debian, Fedora, and the "vanilla" Linux kernel in tarballform. In all cases, you must compile your module against the source forthe running kernel (the kernel executing on your system when you loadthe module into your kernel).

A note on kernel source location, permissions, and privileges: the kernel source customarily used to be located in /usr/src/linuxand owned by root. Nowadays, it is recommended that the kernel sourcebe located in a home directory and owned by a non-root user. Thecommands in this article are all run as a non-root user, using sudo to temporarily gain root privileges only when necessary. To setup sudo, see the sudo(8), visudo(8), and sudoers(5)main pages. Alternatively, become root, and run all the commands asroot if desired. Either way, you will need root access to follow theinstructions in this article.

Preparation for Compiling Kernel Modules Under Debian

The module-assistant package for Debian installs packages andconfigures the system to build out-of-kernel modules. Install it with:

$ sudo apt-get install module-assistant

That’s it; you can now compile kernel modules. For further reading, the Debian Linux Kernel Handbook has an in-depth discussion on kernel-related tasks in Debian.

Fedora Kernel Source and Configuration

The kernel-devel package for Fedora has a package that includes allthe necessary kernel headers and tools to build an out-of-kernel modulefor a Fedora-shipped kernel. Install it with:

$ sudo yum install kernel-devel

Again, that’s all it takes; you can now compile kernel modules. Related documentation can be found in the Fedora release notes.

Vanilla Kernel Source and Configuration

If you choose to use the vanilla Linux kernel source, you mustconfigure, compile, install, and reboot into your new vanilla kernel.This is definitely not the easy route and this article will only coverthe very basics of working with vanilla kernel source.

The canonical Linux source code is hosted at http://kernel.org.The most recent stable release is linked to from the front page.Download the full source release, not the patch. For example, thecurrent stable release is located at http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2. For faster download, find the closest mirror from the list at http://kernel.org/mirrors/, and download from there. The easiest way to get the source is using wget in continue mode. HTTP is rarely blocked, and if your download is interrupted, it will continue where it left off.

$ wget -c "http://kernel.org/pub/linux/kernel/v2.6/linux-<version>.tar.bz2"

Unpack the kernel source:

$ tar xjvf linux-<version>.tar.bz2

Now your kernel is located in linux-<version>/. Change directory into your kernel and configure it:

$ cd linux-<version>$ make menuconfig

A number of really nice make targets exist to automatically buildand install a kernel in many forms: Debian package, RPM package,gzipped tar, etc. Ask the make system for help to list them all:

$ make help

A target that will work on almost every distro is:

$ make tar-pkg

When finished building, install your new kernel with:

$ sudo tar -C / -xvf linux-<version>.tar

Then create a symbolic link to the source tree in the standard location:

$ sudo ln -s <location of top-level source directory> /lib/modules/'uname -r'/build

Now the kernel source is ready for compiling external modules.Reboot into your new kernel before loading modules compiled againstthis source tree.

"Hello, World!" Using printk()

For our first module, we’ll start with a module that uses the kernel message facility, printk(), to print "Hello, world!". printk() is basically printf() for the kernel. The output of printk() is printed to the kernel message buffer and copied to /var/log/messages (with minor variations depending on how syslogd is configured).

Download the hello_printk module tarball and extract it:

$ tar xzvf hello_printk.tar.gz

This contains two files: Makefile, which contains instructions for building the module, and hello_printk.c, the module source file. First, we’ll briefly review the Makefile.

obj-m := hello_printk.o

obj-m is a list of what kernel modules to build. The .o and other objects will be automatically built from the corresponding .c file (no need to list the source files explicitly).

KDIR  := /lib/modules/$(shell uname -r)/build

KDIR is the location of the kernel source. The currentstandard is to link to the associated source tree from the directorycontaining the compiled modules.

PWD := $(shell pwd)

PWD is the current working directory and the location of our module source files.

default:    $(MAKE) -C $(KDIR) M=$(PWD) modules

default is the default make target; that is, make will execute the rules for this target unless it is told to build another target instead. The rule here says to run make with a working directory of the directory containing the kernel source and compile only the modules in the $(PWD) (local) directory. This allows us to use all the rules for compiling modules defined in the main kernel source tree.

Now, let’s run through the code in hello_printk.c.

#include <linux/init.h>#include <linux/module.h>

This includes the header files provided by the kernel that arerequired for all modules. They include things like the definition ofthe module_init() macro, which we will see later on.

static int __inithello_init(void){        printk("Hello, world!/n");        return 0;}

This is the module initialization function, which is run when the module is first loaded. The __init keyword tells the kernel that this code will only be run once, when the module is loaded. The printk() line writes the string "Hello, world!" to the kernel message buffer. The format of printk() arguments is, in most cases, identical to that of printf(3).

module_init(hello_init);

The module_init() macro tells the kernel which functionto run when the module first starts up. Everything else that happensinside a kernel module is a consequence of what is set up in the moduleinitialization function.

static void __exithello_exit(void){        printk("Goodbye, world!/n");}module_exit(hello_exit);

Similarly, the exit function is run once, upon module unloading, and the module_exit() macro identifies the exit function. The __exit keyword tells the kernel that this code will only be executed once, on module unloading.

MODULE_LICENSE("GPL");MODULE_AUTHOR("Valerie Henson <val@nmt.edu>");MODULE_DESCRIPTION("/"Hello, world!/" minimal module");MODULE_VERSION("printk");

MODULE_LICENSE() informs the kernel what license themodule source code is under, which affects which symbols (functions,variables, etc.) it may access in the main kernel. A GPLv2 licensedmodule (like this one) can access all the symbols. Certain modulelicenses will taint the kernel, indicating that non-open or untrustedcode has been loaded. Modules without a MODULE_LICENSE()tag are assumed to be non-GPLv2 and will result in tainting the kernel.Most kernel developers will ignore bug reports from tainted kernelsbecause they do not have access to all the source code, which makesdebugging much more difficult. The rest of the MODULE_*() macros provide useful identifying information about the module in a standard format.

Now, to compile and run the code. Change into the directory and build the module:

&amp;lt;span style="font-size: medium;" mce_style="font-size: medium;"&amp;gt;&amp;lt;/span&amp;gt;

$ cd hello_printk$ make

Then, load the module, using insmod, and check that it printed its message, using dmesg, a program that prints out the kernel message buffer:

$ sudo insmod ./hello_printk.ko$ dmesg | tail

You should see "Hello, world!" in the output from dmesg. Now unload the module, using rmmod, and check for the exit message:

$ sudo rmmod hello_printk$ dmesg | tail

You have successfully compiled and installed a kernel module!

Hello, World! Using /proc

One of the easiest and most popular ways to communicate between the kernel and user programs is via a file in the /proc file system. /procis a pseudo-file system, where reads from files return datamanufactured by the kernel, and data written to files is read andhandled by the kernel. Before /proc, all user-kernelcommunication had to happen through a system call. Using a system callmeant choosing between finding a system call that already behaved theway you needed (often not possible), creating a new system call(requiring global changes to the kernel, using up a system call number,and generally frowned upon), or using the catch-all ioctl() system call, which requires the creation of a special file that the ioctl() operates on (complex and frequently buggy, and very much frowned upon). /procprovides a simple, predefined way to pass data between the kernel anduserspace with just enough framework to be useful, but still enoughfreedom that kernel modules can do what they need.

For our purposes, we want a file in /proc that will return "Hello, world!" when read. We’ll use /proc/hello_world. Download and extract the hello_proc module tarball. We’ll run through the code in hello_proc.c.

#include <linux/init.h>#include <linux/module.h>#include <linux/proc_fs.h>

This time, we add the header file for procfs, which includes support for registering with the /proc file system.

The next function will be called when a process calls read() on the /proc file we will create. It is simpler than a completely generic read() system call implementation because we only allow the "Hello, world!" string to be read all at once.

static inthello_read_proc(char *buffer, char **start, off_t offset, int size, int *eof,                void *data){

The arguments to this function deserve an explicit explanation. buffer is a pointer to a kernel buffer where we write the output of the read(). start is used for more complex /proc files; we ignore it here. offset tells us where to begin reading inside the "file"; we only allow an offset of 0 for simplicity. size is the size of the buffer in bytes; we must check that we don’t write past the end of the buffer accidentally. eof is a short cut for indicating EOF (end of file) rather than the usual method of calling read() again and getting 0 bytes back. data is again for more complex /proc files and ignored here.

Now, for the body of the function:

        char *hello_str = "Hello, world!/n";        int len = strlen(hello_str); /* Don't include the null byte. */        /*         * We only support reading the whole string at once.         */        if</ (size < len)                return< -EINVAL;        /*         * If file position is non-zero, then assume the string has         * been read and indicate there is no more data to be read.         */        if (offset != 0)                return 0;        /*         * We know the buffer is big enough to hold the string.         */        strcpy(buffer, hello_str);        /*         * Signal EOF.         */        *eof = 1;        return len;}

Next, we need to register with the /proc subsystem in our module initialization function.

static int __inithello_init(void){        /*         * Create an entry in /proc named "hello_world" that calls         * hello_read_proc() when the file is read.         */        if (create_proc_read_entry("hello_world", 0, NULL, hello_read_proc,                                    NULL) == 0) {                printk(KERN_ERR                       "Unable to register /"Hello, world!/" proc file/n");                return -ENOMEM;        }        return 0;}module_init(hello_init);

And unregister when the module unloads (if we didn’t do this, when a process attempted to read /proc/hello_world, the /proc file system would try to execute a function that no longer existed and the kernel would panic).

static void __exithello_exit(void){        remove_proc_entry("hello_world", NULL);}module_exit(hello_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Valerie Henson <val@nmt.edu>");MODULE_DESCRIPTION("/"Hello, world!/" minimal module");MODULE_VERSION("proc");

Then, we’re ready to compile and load the module:

$ cd hello_proc$ make$ sudo insmod ./hello_proc.ko

Now, there is a file named /proc/hello_world that will produce "Hello, world!" when read:

$ cat /proc/hello_worldHello, world!

You can create many more /proc files from the same driver, add routines to allow writing to /proc files, create directories full of /proc files, and more. For anything more complicated than this driver, it is easier and safer to use the seq_file helper routines when writing /proc interface routines. For further reading, see Driver porting: The seq_file interface.

Hello, World! Using /dev/hello_world

Now we will implement "Hello, world!" using a device file in /dev, /dev/hello_world. Back in the old days, a device file was a special file created by running a crufty old shell script named MAKEDEV which called the mknod command to create every possible file in /dev, regardless of whether the associated device driver would ever run on that system. The next iteration, devfs, created /devfiles when they were first accessed, which led to many interestinglocking problems and wasteful attempts to open device files to see ifthe associated device existed. The current version of /dev support is called udev, since it creates /dev links with a userspace program. When kernel modules register devices, they appear in the sysfs file system, mounted on /sys. A userspace program, udev, notices changes in /sys and dynamically creates /dev entries according to a set of rules usually located in /etc/udev/.

Download the hello world module tarball. We’ll go through hello_dev.c.

#include <linux/fs.h>#include <linux/init.h>#include <linux/miscdevice.h><#include <linux/module.h>#include <asm/uaccess.h>

As we can see from looking at the necessary header files, creating adevice requires quite a bit more kernel support than our previousmethods. fs.h includes the definitions for a file operations structure, which we must fill out and attach to our /dev file. miscdevice.h includes support for registering a miscellaneous device file. asm/uaccess.h includes functions for testing whether we can read or write to userspace memory without violating permissions.

hello_read() is the function called when a process calls read() on /dev/hello. It writes "Hello, world!" to the buffer passed in the read() call.

static ssize_t hello_read(struct file * file, char * buf,                           size_t count, loff_t *ppos){        char *hello_str = "Hello, world!/n";        int len = strlen(hello_str); /* Don't include the null byte. */        /*         * We only support reading the whole string at once.         */        if (count < len)                return -EINVAL;        /*         * If file position is non-zero, then assume the string has         * been read and indicate there is no more data to be read.         */        if (*ppos != 0)                return 0;        /*         * Besides copying the string to the user provided buffer,         * this function also checks that the user has permission to         * write to the buffer, that it is mapped, etc.         */        if (copy_to_user(buf, hello_str, len))                return -EINVAL;        /*         * Tell the user how much data we wrote.         */        *ppos = len;        return len;}

Next, we create the file operations structdefining what actions to take when the file is accessed. The only fileoperation we care about is read.

static const struct file_operations hello_fops = {        .owner                = THIS_MODULE,        .read                = hello_read,};

Now, create the structure containing the information needed to register a miscellaneous device with the kernel.

static struct miscdevice hello_dev = {        /*         * We don't care what minor number we end up with, so tell the         * kernel to just pick one.         */        MISC_DYNAMIC_MINOR,        /*         * Name ourselves /dev/hello.         */        "hello",        /*         * What functions to call when a program performs file         * operations on the device.         */        &hello_fops};

As usual, we register the device in the module’s initialization function.

static int __inithello_init(void){        int ret;        /*         * Create the "hello" device in the /sys/class/misc directory.         * Udev will automatically create the /dev/hello device using         * the default rules.         */        ret = misc_register(&hello_dev);        if (ret)                printk(KERN_ERR                       "Unable to register /"Hello, world!/" misc device/n");        return ret;}module_init(hello_init);

And remember to unregister the device in the exit function.

static void __exithello_exit(void){        misc_deregister(&hello_dev);}module_exit(hello_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Valerie Henson <val@nmt.edu>");MODULE_DESCRIPTION("/"Hello, world!/" minimal module");MODULE_VERSION("dev");

Compile and load the module:

$ cd hello_dev$ make$ sudo insmod ./hello_dev.ko

Now there is a device named /dev/hello that will produce "Hello, world!" when read by root:

$ sudo cat /dev/helloHello, world!

But we can’t read it as a regular user:

$ cat /dev/hellocat: /dev/hello: Permission denied$ ls -l /dev/hellocrw-rw---- 1 root root 10, 61 2007-06-20 14:31 /dev/hello

This is what happens with the default udev rule, which says that when a miscellaneous device appears, create a file named /dev/<device name>and give it permissions 0660 (owner and group have read-write access,everyone else has no access). We would really like instead for thedevice be readable by regular users and have a link to it named /dev/hello_world. In order to do this, we’ll write a udev rule.

The udev rule has to do two things: create a symbolic link andchange the permissions on device to make world readable. The rule thataccomplishes this is:

KERNEL=="hello", SYMLINK+="hello_world", MODE="0444"

We’ll break the rule down into parts and explain each part.

KERNEL=="hello" says to execute the rest of the rule when a device with a name the same as this string (the == operator means "comparison") appears in /sys. The hello device appeared when we called misc_register() with a structure containing the device name "hello". See the result for yourself in /sys:

$ ls -d /sys/class/misc/hello//sys/class/misc/hello/

SYMLINK+="hello_world" says to add (the += operator means append) hello_worldto the list of symbolic links that should be created when the deviceappears. In our case, we know this is the only symbolic link in thelist, but other devices may have multiple udev rules that createmultiple different symbolic links, so it is good practice add to thelist instead of assigning to it.

MODE="0444" says to set the permissions of the originaldevice file to the 0444 mode, which allows owner, group, and world allto read the file.

In general, it is very important to use the correct operator (==, +=, or =), or unexpected things will happen.

Now that we understand what the rule does, let’s install it in the /etc/udev directory. Udev rules files are arranged in much the same manner as the System V init scripts in /etc/init.d/. Udev executes every script the udev rules directory, /etc/udev/rules.d, in alphabetical/numerical order. Like System V init scripts, the files in the /etc/udev/rules.ddirectory are usually symbolic links to the real rules files, with thesymbolic links named so that the rules will be executed in the correctorder.

Copy the hello.rules file from the hello_dev directory into the /etc/udev/ directory and create a link to it that will be executed before any other rules file:

$ sudo cp hello.rules /etc/udev/$ sudo ln -s ../hello.rules /etc/udev/rules.d/010_hello.rules

Now, reload the hello world driver and look at the new /dev entries:

$ sudo rmmod hello_dev$ sudo insmod ./hello_dev.ko$ ls -l /dev/hello*cr--r--r-- 1 root root 10, 61 2007-06-19 21:21 /dev/hellolrwxrwxrwx 1 root root      5 2007-06-19 21:21 /dev/hello_world -> hello

Now we have /dev/hello_world! Finally, check that you can read the "Hello, world!" devices as a normal user:

$ cat /dev/hello_worldHello, world!$ cat /dev/helloHello, world!

For more details on writing udev rules, see Writing udev rules, by Daniel Drake.

Valerie Henson is a Linux consultant specializing in file systems, and maintainer of the TCP/IP Drinking Game.

(译者注:本文的例子是只能在linux的2.6内核下使用的,2.6以上的内核,译者没有做过实验,2.4是要修改make文件才能运行。)

本文的出处:这里

自古以来,学习一门新编程语言的第一步就是写一个打印“hello world”的程序(可以看《hello world 集中营》这个帖子供罗列了300个“helloworld”程序例子)在本文中,我们将用同样的方式学习如何编写一个简单的linux内核模块和设备驱动程序。我将学习到如何在内核模式下以三种不同的方式来打印hello world,这三种方式分别是: printk(),/proc文件,/dev下的设备文件。

准备:安装内核模块的编译环境

一个内核模块kernelmodule是一段能被内核动态加载和卸载的内核代码,因为内核模块程序是内核的一个部分,并且和内核紧密的交互,所以内核模块不可能脱离内核编译环境,至少,它需要内核的头文件和用于加载的配置信息。编译内核模块同样需要相关的开发工具,比如说编译器。为了简化,本文只简要讨论如何在Debian、Fedora和其他以.tar.gz形式提供的原版linux内核下进行核模块的编译。在这种情况下,你必须根据你正在运行内核相对应的内核源代码来编译你的内核模块kernel module(当你的内核模块一旦被装载到你内核中时,内核就将执行该模块的代码)

必须要注意内核源代码的位置,权限:内核程序通常在/usr/src/linux目录下,并且属主是root。如今,推荐的方式是将内核程序放在一个非root用户的home目录下。本文中所有命令都运行在非root的用户下,只有在必要的时候,才使用sudo来获得临时的root权限。配置和使用sudo可以man sudo(8) visudo(8)和sudoers(5)。或者切换到root用户下执行相关的命令。不管什么方式,你都需要root权限才能执行本文中的一些命令。

在Debian下编译内核模块的准备

使用如下的命令安装和配置用于在Debian编译内核模块的module-assitant包

$ sudo apt-get install module-assistant

以此你就可以开始编译内核模块,你可以在《Debian Linux Kernel Handbook》这本书中找到对Debian内核相关任务的更深度的讨论。

Fedora的kernel-devel包包含了你编译Fedora内核模块的所有必要内核头文件和工具。你可以通过如下命令得到这个包。

$ sudo yum install kernel-devel

有了这个包,你就可以编译你的内核模块kernel modules。关于Fedora编译内核模块的相关文档你可以从Fedora release notes中找到。

一般Linux 内核源代码和配置

(译者注,下面的编译很复杂,如果你的Linux不是上面的系统,你可以使用REHL AS4系统,这个系统的内核就是2.6的内核,并且可以通过安装直接安装内核编译支持环境,从而就省下了如下的步骤。而且下面的步骤比较复杂,建议在虚拟机安装Linux进行实验。)

如果你选择使用一般的Linux内核源代吗,你必须,配置,编译,安装和重启的你编译内核。这个过程非常复杂,并且本文只会讨论使用一般内核源代码的基本概念。

linux的著名的内核源代码在http://kernel.org上都可以找到。最近新发布的稳定版本的代码在首页上。下载全版本的源代码,不要下载补丁代码。例如,当前发布稳定版本在url:http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2上。如果需要更快速的下载,从htpp://kernel.org/mirrors上找到最近的镜像进行下载。最简单获得源代码的方式是以断点续传的方式使用wget。如今的http很少发生中断,但是如果你在下载过程中发生了中断,这个命令将帮助你继续下载剩下的部分。

$ wget -c http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2

解包内核源代码

$ tar xjvf linux-&lt;version&gt;.tar.bz2

现在你的内核源代码位于linux-/目录下。转到这个目录下,并配置它:

$ cd linux-&lt;version&gt; $ make menuconfig

一些非常易用的编译目标make targets提供了多种编译安装内核的形式:Debian 包,RPM包,gzip后的tar文件 等等,使用如下命令查看所有可以编译的目标形式

$ make help

一个可以工作在任何linux的目标是:(译者注:REHL AS4上没有tar-pkg这个目标,你可以任选一个rpm编译,编译完后再上层目录可以看到有一个linux-.tar.gz可以使用)

$ make tar-pkg

当编译完成后,可以调用如下命令安装你的内核

$ sudo tar -C / -xvf linux-&lt;version&gt;.tar

在标准位置建立的到内核源代码的链接

$ sudo ln -s &lt;location of top-level source directory&gt; /lib/modules/'uname -r'/build

现在已经内核源代码已经可以用于编译内核模块了,重启你的机器以使得你根据新内核程序编译的内核可以被装载。

使用printk()函数打印”Hello World”

我们的第一个内核模块,我们将以一个在内核中使用函数printk()打印”Helloworld”的内核模块为开始。printk是内核中的printf函数。printk的输出打印在内核的消息缓存kernel messagebuffer并拷贝到/var/log/messages(关于拷贝的变化依赖于如何配置syslogd)

下载hello_printk 模块的tar包 并解包:

$ tar xzvf hello_printk.tar.gz

这个包中包含两个文件:Makefile,里面包含如何创建内核模块的指令和一个包含内核模块源代码的hello_printk.c文件。首先,我们将简要的过一下这个Makefile 文件。

obj-m := hello_printk.o

obj-m指出将要编译成的内核模块列表。.o格式文件会自动地有相应的.c文件生成(不需要显示的罗列所有源代码文件)

KDIR := /lib/modules/$(shell uname -r)/build

KDIR表示是内核源代码的位置。在当前标准情况是链接到包含着正在使用内核对应源代码的目录树位置。

PWD := $(shell pwd)

PWD指示了当前工作目录并且是我们自己内核模块的源代码位置

default: $(MAKE) -C $(KDIR) M=$(PWD) modules

default是默认的编译连接目标;即,make将默认执行本条规则编译目标,除非程序员显示的指明编译其他目标。这里的的编译规则的意思是,在包含内核源代码位置的地方进行make,然后之编译$(PWD)(当前)目录下的modules。这里允许我们使用所有定义在内核源代码树下的所有规则来编译我们的内核模块。

现在我们来看看hello_printk.c这个文件

1.#include 2.<linux/init.h> 3.#include 4.<linux/module.h>

这里包含了内核提供的所有内核模块都需要的头文件。这个文件中包含了类似module_init()宏的定义,这个宏稍后我们将用到

1.static int __init 2.hello_init(void){ 3.printk("Hello, world!n"); 4.return 0; 5.}

这是内核模块的初始化函数,这个函数在内核模块初始化被装载的时候调用。__init关键字告诉内核这个代码只会被运行一次,而且是在内核装载的时候。printk()函数这一行将打印一个”Hello,world”到内核消息缓存。printk参数的形式在大多数情况和printf(3)一模一样。

1.module_init(hello_init); 2.module_init()

宏告诉内核当内核模块第一次运行时哪一个函数将被运行。任何在内核模块中其他部分都会受到内核模块初始化函数的影响。

1.static void __exit 2.hello_exit(void){ 3.printk("Goodbye, world!n"); 4.} 5.module_exit(hello_exit);

同样地,退出函数也只在内核模块被卸载的时候会运行一次,module_exit()宏标示了退出函数。__exit关键字告诉内核这段代码只在内核模块被卸载的时候运行一次。

1.MODULE_LICENSE("GPL"); 2.MODULE_AUTHOR("Valerie Henson val@nmt.edu"); 3.MODULE_DESCRIPTION("Hello, world!" minimal module"); 4.MODULE_VERSION("printk"); 5.MODULE_LICENSE()

宏告诉内核,内核模块代码在什么样的license之下,这将影响主那些符号(函数和变量,等等)可以访问主内核。GPLv2下的模块(如同本例子中)能访问所有的符号。某些内核模块license将会损害内核开源的特性,这些license指示内核将装载一些非公开或不受信的代码。如果内核模块不使用MODULE_LICENSE()宏,就被假定为非GPLv2的,这会损害内核的开源特性,并且大部分Linux内核开发人员都会忽略来自受损内核的bug报告,因为他们无法访问所有的源代码,这使得调试变得更加困难。剩下的MODULE_*()这些宏以标准格式提供有用的标示该内核模块的信息(译者注:这里意思是,你必须使用GPLv2的license,否则你的驱动程序很有可能得不到Linux社区的开发者的支持 :))

现在,开始编译和运行代码。转到相应的目录下,编译内核模块

$ cd hello_printk $ make

接着,装载内核模块,使用insmod指令,并且通过dmesg来检查打印出的信息,dmesg是打印内核消息缓存的程序。

$ sudo insmod ./hello_printk.ko $ dmesg | tail

你将从dmesg的屏幕输出中看见”Hello world!”信息。现在卸载使用rmmod卸载内核模块,并检查退出信息。

$ sudo rmmod hello_printk $ dmesg | tail

到此,你就成功地完成了对内核模块的编译和安装!

使用/proc的Hello, World!

一种用户程序和内核通讯最简单和流行的方式是通过使用/proc下文件系统进行通讯。/proc是一个伪文件系统,从这里的文件读取的数据是由内核返回的数据,并且写入到这里面的数据将会被内核读取和处理。在使用/proc方式之前,所用用户和内核之间的通讯都不得不使用系统调用来完成。使用系统调用意味着你将在要在查找已经具有你需要的行为方式的系统调用(一般不会出现这种情况),或者创建一种新的系统调用来满足你的需求(这样就要求对内核全局做修改,并增加系统调用的数量,这是通常是非常不好的做法),或者使用ioctl这个万能系统调用,这就要求要创建一个新文件类型供ioctl操作(这也是非常复杂而且bug比较多的方式,同样是非常繁琐的)。/proc提供了一个简单的,无需定义的方式在用户空间和内核之间传递数据,这种方式不仅可以满足内核使用,同样也提供足够的自由度给内核模块做他们需要做的事情。

为了满足我们的要求,我们需要当我们读在/proc下的某一个文件时将会返回一个“Hello world!”。我们将使用/proc/hello_world这个文件。下载并解开hello proc这个gzip的tar包后,我们将首先来看一下hello_proc.c这个文件

1.#include <linux/init.h> 2.#include <linux/module.h> 3.#include <linux/proc_fs.h>

这次,我们将增加一个proc_fs头文件,这个头文件包括驱动注册到/proc文件系统的支持。当另外一个进程调用read()时,下一个函数将会被调用。这个函数的实现比一个完整的普通内核驱动的read系统调用实现要简单的多,因为我们仅做了让”Helloworld”这个字符串缓存被一次读完。

1.static int2.hello_read_proc(char *buffer, char **start,off_t offset, 3.int size, int *eof, void *data) 4.{

这个函数的参数值得明确的解释一下。buffer是指向内核缓存的指针,我们将把read输出的内容写到这个buffer中。start参数多用更复杂的/proc文件;我们在这里将忽略这个参数;并且我只明确的允许offset这个的值为0。size是指buffer中包含多字节数;我们必须检查这个参数已避免出现内存越界的情况,eof参数一个EOF的简写,用于返回文件是否已经读到结束,而不需要通过调用read返回0来判断文件是否结束。这里我们不讨论依靠更复杂的/proc文件传输数据的方法。这个函数方法体罗列如下:

01.char *hello_str = "Hello, world!/n"; 02.int len = strlen(hello_str); /* Don't include the null byte. */03./* * We only support reading the whole string at once. */04.if (size < len) 05.return< -EINVAL; 06./* * If file position is non-zero, then assume the string has 07.* been read and indicate there is no more data to be read. 08.*/09.if (offset != 0) 10.return 0; 11./* * We know the buffer is big enough to hold the string. */12.strcpy(buffer, hello_str); 13./* * Signal EOF. */14.*eof = 1; 15.return len; 16.}

下面,我们需将内核模块在初始化函数注册在/proc 子系统中。

01.static int __init 02.hello_init(void){ 03./* 04.* Create an entry in /proc named "hello_world" that calls 05.* hello_read_proc() when the file is read. 06.*/07.if (create_proc_read_entry("hello_world", 0, 08.NULL, hello_read_proc, NULL) == 0) { 09.printk(KERN_ERR 10."Unable to register "Hello, world!" proc filen"); 11.return -ENOMEM; 12.} 13.return 0; 14.} 15.module_init(hello_init);

当内核模块卸载时,需要在/proc移出注册的信息(如果我们不这样做的,当一个进程试图去访问/proc/hello_world,/proc文件系统将会试着执行一个已经不存在的功能,这样将会导致内核崩溃)

01.static void __exit 02.hello_exit(void){ 03.remove_proc_entry("hello_world", NULL); 04.} 05.module_exit(hello_exit); 06.MODULE_LICENSE("GPL"); 07.MODULE_AUTHOR("Valerie Henson val@nmt.edu"); 08.MODULE_DESCRIPTION(""Hello, world!" minimal module"); 09.MODULE_VERSION("proc");

下面我们将准备编译和装载模组

cd hello_proc $ make$ sudo insmod ./hello_proc.ko

现在,将会有一个称为/proc/hello_world的文件,并且读这个文件的,将会返回一个”Hello world”字符串。

$ cat /proc/hello_world Hello, world!

你可以为为同一个驱动程序创建多个/proc文件,并增加相应写/proc文件的函数,创建包含多个/proc文件的目录,或者更多的其他操作。如果要写比这个更复杂的驱动程序,可以使用seq_file函数集来编写是更安全和容易的。关于这些更多的信息可以看《Driver porting: The seq_file interface》

Hello, World! 使用 /dev/hello_world

现在我们将使用在/dev目录下的一个设备文件/dev/hello_world实现”Hello,world!”。追述以前的日子,设备文件是通过MAKEDEV脚本调用mknod命令在/dev目录下产生的一个特定的文件,这个文件和设备是否运行在改机器上无关。到后来设备文件使用了devfs,devfs在设备第一被访问的时候创建/dev文件,这样将会导致很多有趣的加锁问题和多次打开设备文件的检查设备是否存在的重试问题。当前的/dev版本支持被称为udev,因为他将在用户程序空间创建到/dev的符号连接。当内核模块注册设备时,他们将出现在sysfs文件系统中,并mount在/sys下。一个用户空间的程序,udev,注意到/sys下的改变将会根据在/etc/udev/下的一些规则在/dev下创建相关的文件项。

下载hello world内核模块的gzip的tar包,我们将开始先看一下hello_dev.c这个源文件。

1.#include <linux/fs.h> 2.#include <linux/init.h> 3.#include <linux/miscdevice.h> 4.#include <linux/module.h> 5.#include <asm/uaccess.h>

正如我们看到的必须的头文件外,创建一个新设备还需要更多的内核头文件支持。fs.sh包含所有文件操作的结构,这些结构将由设备驱动程序来填值,并关联到我们相关的/dev文件。miscdevice.h头文件包含了对通用miscellaneous设备文件注册的支持。asm/uaccess.h包含了测试我们是否违背访问权限读写用户内存空间的函数。hello_read将在其他进程在/dev/hello调用read()函数被调用的是一个函数。他将输出”Hello world!”到由read()传入的缓存。

01.static ssize_t hello_read(struct file * file, char * buf, size_t count, loff_t *ppos) 02.{ 03.char *hello_str = "Hello, world!n"; 04.int len = strlen(hello_str); /* Don't include the null byte. */05./* * We only support reading the whole string at once. */06.if (count < len) 07.return -EINVAL; 08./* 09.* If file position is non-zero, then assume the string has 10.* been read and indicate there is no more data to be read. 11.*/12.if (*ppos != 0) 13.return 0; 14./* 15.* Besides copying the string to the user provided buffer, 16.* this function also checks that the user has permission to 17.* write to the buffer, that it is mapped, etc. 18.*/19.if (copy_to_user(buf, hello_str, len)) 20.return -EINVAL; 21./* 22.* Tell the user how much data we wrote. 23.*/24.*ppos = len; 25.return len; 26.}

下一步,我们创建一个文件操作结构file operations struct,并用这个结构来定义当文件被访问时执行什么动作。在我们的例子中我们唯一关注的文件操作就是read。

1.static const struct file_operations hello_fops = { 2..owner = THIS_MODULE, 3..read = hello_read, 4.};

现在,我们将创建一个结构,这个结构包含有用于在内核注册一个通用miscellaneous驱动程序的信息。

01.static struct miscdevice hello_dev = { 02./* 03.* We don't care what minor number we end up with, so tell the 04.* kernel to just pick one. 05.*/06.MISC_DYNAMIC_MINOR, 07./* 08.* Name ourselves /dev/hello. 09.*/10."hello", 11./* 12.* What functions to call when a program performs file 13.* operations on the device. 14.*/15.&hello_fops 16.};

在通常情况下,我们在init中注册设备

01.static int __init 02.hello_init(void){ 03.int ret; 04./* 05.* Create the "hello" device in the /sys/class/misc directory. 06.* Udev will automatically create the /dev/hello device using 07.* the default rules. 08.*/09.ret = misc_register(&hello_dev); 10.if (ret) 11.printk(KERN_ERR 12."Unable to register "Hello, world!" misc devicen"); 13.return ret; 14.} 15.module_init(hello_init);

接下是在卸载时的退出函数

01.static void __exit 02.hello_exit(void){ 03.misc_deregister(&hello_dev); 04.} 05.module_exit(hello_exit); 06.MODULE_LICENSE("GPL"); 07.MODULE_AUTHOR("Valerie Henson val@nmt.edu>"); 08.MODULE_DESCRIPTION(""Hello, world!" minimal module"); 09.MODULE_VERSION("dev");

编译并加载模块:

$ cd hello_dev $ make$ sudo insmod ./hello_dev.ko

现在我们将有一个称为/dev/hello的设备文件,并且这个设备文件被root访问时将会产生一个”Hello, world!”

$ sudo cat /dev/hello Hello, world!

但是我们不能使用普通用户访问他:

$ cat /dev/hello cat:/dev/hello: Permission denied $ ls -l /dev/hello crw-rw---- 1 root root 10, 61 2007-06-20 14:31 /dev/hello

这是有默认的udev规则导致的,这个条规将标明当一个普通设备出现时,他的名字将会是/dev/,并且默认的访问权限是0660(用户和组读写访问,其他用户无法访问)。我们在真实情况中可能会希望创建一个被普通用户访问的设备驱动程序,并且给这个设备起一个相应的连接名。为达到这个目的,我们将编写一条udev规则。

udev规则必须做两件事情:第一创建一个符号连接,第二修改设备的访问权限。

下面这条规则可以达到这个目的:

KERNEL=="hello", SYMLINK+="hello_world", MODE="0444"

我们将详细的分解这条规则,并解释每一个部分。KERNEL==”hello”标示下面的的规则将作用于/sys中设备名字”hello”的设备(==是比较符)。hello设备是我们通过调用misc_register()并传递了一个包含设备名为”hello”的文件操作结构file_operations为参数而达到的。你可以自己通过如下的命令在/sys下查看

$ ls -d /sys/class/misc/hello//sys/class/misc/hello/

SYMLINK+=”hello_world” 的意思是在符号链接列表中增加 (+= 符号的意思着追加)一个hello_world,这个符号连接在设备出现时创建。在我们场景下,我们知道我们的列表的中的只有这个符号连接,但是其他设备驱动程序可能会存在多个不同的符号连接,因此使用将设备追加入到符号列表中,而不是覆盖列表将会是更好的实践中的做法。

MODE=”0444″的意思是原始的设备的访问权限是0444,这个权限允许用户,组,和其他用户可以访问。

通常,使用正确的操作符号(==, +=, or =)是非常重要的,否则将会出现不可预知的情况。

现在我们理解这个规则是怎么工作的,让我们将其安装在/etc/udev目录下。udev规则文件以和SystemV初始脚本目录命名的同种方式的目录下,/etc/udeve/rules.d这个目录,并以字母/数字的顺序。和SystemV的初始化脚本一样,/etc/udev/rules.d下的目录通常符号连接到真正的文件,通过使用符号连接名,将使得规则文件已正确的次序得到执行。使用如下的命令,拷贝hello.rules文件从/hello_dev目录到/etc/udev目录下,并创建一一个最先被执行的规则文件链接在/etc/udev/rules.d目录下。

$ sudo cp hello.rules /etc/udev/ $ sudo ln -s ../hello.rules /etc/udev/rules.d/010_hello.rules

现在我们重新装载驱动程序,并观察新的驱动程序项

$ sudo rmmod hello_dev $ sudo insmod ./hello_dev.ko $ ls -l /dev/hello* cr--r--r-- 1 root root 10, 61 2007-06-19 21:21 /dev/hello lrwxrwxrwx 1 root root 5 2007-06-19 21:21 /dev/hello_world -> hello

最后,检查你可以使用普通用户访问/dev/hello_world设备.

$ cat /dev/hello_world Hello, world! $ cat /dev/hello Hello, world!

更多编写udev规则的信息可以在Daniel Drake的文章Writing udev rules中找到。

obexsend.sh 源文件

#!/bin/sh# Copyright (c) 2005, Wade Alcorn# Released under the GPL license# http://www.gnu.org/copyleft/gpl.html# To find channel use the following comand:# sdptool search FTRNusage() { printf "/n$0 is a basic command line wrapper for obex_test. It /n" printf "will push a file to a bluetooth device from the command line/n" printf "usage: $0 <MAC> <Channel> <Filename>/n" exit}ot = `which obex_test`if [ ! -x $ot ]then printf "Error: obex_test not installed, in path or not executable/n" usagefiif [ $# -ne 3 ] then usagefiif [ ! -r $3 ]then printf "Error: can’t read file $3/n" usagefiprintf "c/np/n$3 $3/nq/n" | obex_test -b $1 $2

都可以…孔子的,老子的. 孙子的…都可以

A Simple Introduction to Device Drivers under Linux

相关文章:

你感兴趣的文章:

标签云: