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规则文件以和System V初始脚本目录命名的同种方式的目录下,/etc/udeve/rules.d这个目录,并以字母/数字的顺序。和System V的初始化脚本一样,/etc/udev/rules.d下的目录通常符号连接到真正的文件,通过使用符号连接名,将使得规则文件已正确的次序得到执行。
使用如下的命令,拷贝hello.rules文件从/hello_dev目录到/etc/udev目录下,并创建一一个最先被执行的规则文件链接在/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中找到