编写你的第一个Linux内核模块(目前校对到杂项设备)

想要开始黑掉核?没有线索不知道如何开始?让我们向你展示如何做…

内核编程通常被视为黑魔法。在Arthur C Clarke的意义上说,它可能是。Linux内核与用户空间有很大的不同:抛开漫不经心的态度,你要格外小心,因为在你代码中的一个小小的bug都会影响整个系统。这里没有简单的方法来做浮点运算、堆栈既固定又小,你写的代码总是异步所以你需要考虑并发性。尽管如此,Linux内核是一个非常大而复杂的C程序,对每个人都是开放的(阅读、学习、提高),而你也可以成为其中的一部分。

“最简单的方法开始内核编程是编写一个模块—— 一段可以动态地加载到内核的代码。”

开始内核编程的最简单方法可能是编写一个模块—— 一段代码,可以动态地在内核中加载和删除。对于模块而言也是有限制的——例如,他们不能添加或删除进程描述符等常用数据结构的字段。但在其他方面他们是成熟的内核级代码,如果需要的话,他们总是可以被编译到内核(因此可以绕开了所有的限制)。完全可以开发和编译Linux源代码树之外的模块(这是意料之中的是称为一个树的构建),这是非常方便的,如果你只是想玩一下,不想提交你的更改包含到主线内核。

在本教程中,我们将开发一个简单的内核模块,创建一个/dev/reverse设备。一个字符串读取写入该设备用词序颠倒(“Hello World”变成“World Hello”)。这是个很受欢迎的程序员面试难题,你可能会得到一些加分当你通过自己的能力在内核级别实现它时。有几句警告要在开始前告诉你:模块中的错误可能导致系统崩溃和数据丢失(不太可能,但是可能)。确保你备份你所有重要的数据在你开始之前,或者,更好的是,在一个虚拟机上实验。

尽可能避免root权限

默认情况下,/dev/reverse是属于root的,所以你必须用sudo运行你的测试程序。为了解决这个问题,创建一个/lib/udev/rules.d/99-reverse.rules文件,写入:

SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"

别忘了重新插入模块。使设备节点可访问到非root用户通常不是一个好主意,但它在开发过程中非常有用。更不用说,作为根用户运行测试二进制文件不是一个好主意。

模块的结构

大多数Linux内核模块是用C编写的(除了底层特定于体系结构的部分),建议你保持你的模块在一个文件中(reverse.c)。我们把完整的源代码在GitHub(我把源代码注释上传了,免费下载查看),下面我们来看看一些片段。首先,我们包括一些常见的标题和描述该模块使用预定义的宏:

#include <linux/init.h>

#include <linux/kernel.h>

#include <linux/module.h>

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Valentine Sinitsyn <[email protected]>");

MODULE_DESCRIPTION("In-kernel phrase reverser");

在这里一切都很简单,除了MODULE_LICENSE():它不是一个纯粹的标志。内核强烈赞成GPL-compatible代码,所以如果你设置一些非GPL-compatible(比如“Proprietary”),某些内核模块功能将不可用。

什么时候不必要写内核模块

内核编程是有趣的,但是写作(特别是调试)内核代码在真实的项目需要一定的技巧。一般来说,如果没有其他方法来解决你的问题,那么你可以下调内核级别。你就可以在用户空间:

你开发一个USB驱动程序—看看libusb。

你开发一个文件系统—试试FUSE。

你扩展Netfilter——libnetfilter_queue可以帮助你。

一般情况下,原生内核代码将有更好的表现,但对于许多项目这种性能损失并不重要。

由于内核编程一直是异步的,Linux没有主要()函数执行顺序运行模块。相反,您提供各种事件的回调,像这样的:

static int __init reverse_init(void)

{

printk(KERN_INFO "reverse device has been registeredn");

return 0;

}

static void __exit reverse_exit(void)

{

printk(KERN_INFO "reverse device has been unregisteredn");

}

module_init(reverse_init);

module_exit(reverse_exit);

在这里,我们定义函数被称为模块的插入和删除。只有第一个是必需的。现在,他们只是打印一条消息到内核环缓冲区(从用户空间访问通过dmesg命令);KERN_INFO是一个日志级别(注意没有逗号)。__init和__exit是属性—— 一块加载到函数或者变量的元数据。这种属性在用户空间的C代码中很少见,但在内核中很常见。所有标有__init在初始化后会被回收(记得“Freeing unused kernel memory ...”消息?)。__exit表示函数被安全的优化了,当代码被静态的编译进内核。最后,module_init()和module_exit()宏设置reverse_init()和reverse_exit()函数作为我们的生命周期回调模块。实际的函数名不重要,如果你愿意可以叫他们init()和exit()或start()和stop()。它们是声明为静态的,因此在外部模块看不见。事实上,任何函数在内核中是看不见的,除非显式导出。然而,前缀你的功能模块名称是一种常见的内核程序员之间的约定。

这些都是光秃秃的骨头—让我们做得更有趣。模块可以接受参数,如下:

# modprobe foo bar=1

modinfo命令显示所有参数接受模块,而这些也可以在/sys/module//parameters中当做文件使用。我们的模块需要一个缓冲区来存储参数——我们使它的大小可由用户配置。添加以下三行于MODULE_DESCRIPTION()之下:

static unsigned long buffer_size = 8192;

module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));

MODULE_PARM_DESC(buffer_size, "Internal buffer size");

在这里,我们定义了一个变量来存储值,包装成一个参数,并通过sysfs使每个人可读。参数的描述(最后一行)出现在modinfo的输出。

用户可以直接设置buffer_size,我们需要在reverse_init()来清除无效取值。你应该经常检查来自内核外面的数据——如果你不(这样做),就是在将内核置身于异常或者安全漏洞中。

static int __init reverse_init()

{

if (!buffer_size)

return -1;

printk(KERN_INFO

"reverse device has been registered, buffer size is %lu bytesn",

buffer_size);

return 0;

}

从模块初始化函数返回非零值表示失败。

Linux内核是你开发模块时的一切来源。然而,它非常的大而且可能有接二连三的困难。幸运的是,这里有很多方法,使它更容易导航大型代码库。首先,有Cscope—一个古老的工具,运行在一个终端。简单地运行make cscope & &cscope在内核代码的顶级目录。Cscope与Vim和Emacs集成性很好,因此你可以使用它不用离开你最喜欢用的编辑器。

如果基于终端工具不是你的菜,访问http://lxr.free-electrons.com。它是一个基于web的内核的导航工具但不像Cscope尽可能多的功能(例如,你不能很容易找到函数的用法),但它仍然提供了足够的快速查找。

现在是时候编译模块。你需要内核版本的运行头文件(linux-headers或等效包)和build-essential(或类似包)。接下来,是时候创建样板Makefile:

obj-m += reverse.o

all:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

现在可以通过make来创建你的第一个模块. 如果之前的输入都正确, 你会在当前目录下找到一个reverse.ko文件。通过命令sudo insmod reverse.ko, 将它插入内核;运行:

$ dmesg | tail -1

[ 5905.042081] reverse device has been registered, buffer size is 8192 bytes

恭喜你!然而,现在这一行是在说谎——还没有设备节点。让我们解决它。

杂项设备

在Linux中,有一个特殊的字符设备类型称为“杂项”(或简单的“混杂”)。这是专为小型设备驱动程序与一个单一入口点,并且正是我们需要的。所有misc设备共享相同的主设备号(10),所以一个驱动程序(驱动程序/字符/ misc.c)可以照顾他们,和他们的小数字。在所有其他感官,他们只是正常字符设备。

注册一个次要版本号(和一个入口点)的设备,您声明结构体misc_device,填补它的字段(注意语法),并调用misc_register()这个结构的指针。为此,您还需要包括linux / miscdevice的h数据源文件:

static struct miscdevice reverse_misc_device = {

.minor = MISC_DYNAMIC_MINOR,

.name = "reverse",

.fops = &reverse_fops

};

static int __init reverse_init()

{

...

misc_register(&reverse_misc_device);

printk(KERN_INFO ...

}

这里,我们请求第一个可用(动态)小数量的设备命名为“反向”;th省略号表示省略了代码,我们已经看到了。别忘了注销设备模块的拆卸:

static void __exit reverse_exit(void)

{

misc_deregister(&reverse_misc_device);

...

}

The ‘fops’ field stores a pointer to a struct file_operations (declared in linux/fs.h), and this is the entry point for our module. reverse_fops is defined as:

static struct file_operations reverse_fops = {

.owner = THIS_MODULE,

.open = reverse_open,

...

.llseek = noop_llseek

};

再次,reverse_fops包含一组要执行的回调函数(也称为方法)当用户空间代码打开一个设备,读,写或关闭文件描述符。如果你忽略这些,一个明智的做法是用回退代替。这就是为什么我们显式地设置llseek方法noop_llseek(),它(顾名思义)没有。默认实现更改一个文件指针,我们不希望我们的设备现在seekable(这将是你今天的家庭作业)。

我打开关闭

让我们实现的方法。我们将为每个文件描述符分配一个新的缓冲区打开,关闭和自由。这不是真正的安全:如果一个用户空间应用程序漏洞描述符(也许故意),它可能占着内存,并使系统无法使用。你应该考虑这些可能性在现实世界中,但对于本教程,这是可以接受的。

我们需要一个结构来描述缓冲。内核提供了许多通用的数据结构:链表(双链)、哈希表、树等等。然而,缓冲区通常从头开始实现。我们会打电话给我们的“结构缓冲”:

struct buffer {

char *data, *end, *read_ptr;

unsigned long size;

};

数据是一个指向字符串的指针这个缓冲区存储,和结束后的第一个字节字符串结束。read_ptr就是read()应该开始阅读的数据。缓冲区大小存储完整性——现在,我们不使用这个字段。你不应该认为你的用户结构将正确地初始化所有这些,所以更好的封装缓冲区分配和重分配功能。他们通常叫buffer_alloc()和buffer_free()。

static struct buffer *buffer_alloc(unsigned long size)

{

struct buffer *buf;

buf = kzalloc(sizeof(*buf), GFP_KERNEL);

if (unlikely(!buf))

goto out;

...

out:

return buf;

}

内核内存分配与kmalloc()和释放与kfree();在kzalloc()味道all-zeroes设置内存。不像标准的malloc(),它的内核版本接收标志指定第二个参数的类型的内存要求。这里,GFP_KERNEL意味着我们需要一个正常的内核内存(而不是在直接存储器存取或高端内存区)和功能可以休眠(重新安排流程)。sizeof(* buf)是一种常见的办法通过指针的大小结构。

你应该经常检查kmalloc()的返回值:非关联化空指针将导致内核恐慌。还请注意可能的使用() macro。它(和相反的可能() macro)广泛用于内核表示条件几乎总是如此(或假)。它不影响控制流,但有助于现代处理器来提高性能,分支预测。

最后,注意转到。它们通常被认为是邪恶的,然而,Linux内核(和其他一些系统软件)雇佣了他们实现集中函数退出。这将导致更少的深度嵌套和更可读的代码,并就像try-ctach块中使用的高级语言。

与buffer_alloc()和buffer_free(),打开和关闭的实现方法变得非常简单。

static int reverse_open(struct inode *inode, struct file *file)

{

int err = 0;

file->private_data = buffer_alloc(buffer_size);

...

return err;

}

结构文件是一个标准的内核数据结构存储一个打开文件的信息,如当前文件位置(file->f_pos),国旗(file->f_flags)、或开放式(file->f_mode)。另一个领域,file->private_data用于将文件与一些任意数据。其类型是void *,外面是不透明的内核文件的所有者。我们存储缓冲区。

如果缓冲区分配失败,我们显示这个调用用户空间代码通过返回负值(-ENOMEM)。AC库做开放(2)系统调用(probably, glibc)将检测到并适当地设置errno。

学会读和写

“读”和“写”方法,真正的工作就完成了。当数据写入缓冲区,我们放弃之前的内容和反向就地一词,没有任何临时存储。阅读方法只是简单地将数据从内核缓冲区复制到用户空间。但应该reverse_read()方法做如果没有缓冲区中的数据吗?在用户空间,read()调用会阻塞,直到数据是可用的。在内核中,你必须等待。幸运的是,有一个机制,它被称为“等待队列”。

这个想法很简单。如果当前进程需要等待某些事件,其描述符(一个struct task_struct存储为“当前”)投入non-runnable(睡觉)状态并添加到队列中。然后安排()来选择另一个进程来运行。使用代码生成事件队列醒来服务员把他们带回TASK_RUNNING状态。调度器将选择其中一个在未来。Linux有几个non-runnable进程状态,尤其是TASK_INTERRUPTIBLE(睡眠,可以中断信号)和TASK_KILLABLE(睡眠过程可以被杀)。所有这一切应该正确处理和等待队列为你这样做。

自然的地方来储存我们读等待队列头结构缓冲,所以先添加wait_queue_head_t read_queue字段。你还应该包括linux / sched.h。等待队列可以静态地声明DECLARE_WAITQUEUE()macro。在我们的例子中,动态初始化是必要的,所以这一行添加到buffer_alloc():

init_waitqueue_head(&buf->read_queue);

我们等待可用的数据;或者read_ptr !=结束条件变为真。我们也想要等待可中断(说,通过Ctrl + C)。因此,“阅读”的方法应该是这样的:

static ssize_t reverse_read(struct file *file, char __user * out,

size_t size, loff_t * off)

{

struct buffer *buf = file->private_data;

ssize_t result;

while (buf->read_ptr == buf->end) {

if (file->f_flags & O_NONBLOCK) {

result = -EAGAIN;

goto out;

}

if (wait_event_interruptible

(buf->read_queue, buf->read_ptr != buf->end)) {

result = -ERESTARTSYS;

goto out;

}

}

...

我们一直循环直到数据和使用wait_event_interruptible()(这是一个宏,不是一个函数,这就是为什么队列是通过值)等如果不是。如果wait_event_interruptible(),中断,它返回一个非零值,我们翻译-ERESTARTSYS。是指系统调用应该重新启动。file->f_flags检查占在非阻塞模式下打开文件:如果没有数据,我们返回 -EAGAIN。

我们不能用如果()代替()然而(),因为可以有许多流程等数据。运用方法时,调度器选择以不可预知的方式运行,那么这段代码的时候有机会执行缓冲区又可以是空的。现在我们需要将数据从buf - >数据复制到用户空间。copy_to_user()核函数:

size = min(size, (size_t) (buf->end - buf->read_ptr));

if (copy_to_user(out, buf->read_ptr, size)) {

result = -EFAULT;

goto out;

}

用户空间指针调用可能会失败,如果是错误的,如果这种事情发生,我们返回-EFAULT。记住不要相信内核之外的东西!

buf->read_ptr += size;

result = size;

out:

return result;

}

简单的算术是必需的,以至于可以在任意块读取的数据。该方法返回的字节数读或错误代码。

写方法更简单和更短。首先,我们检查缓冲区有足够的空间,然后我们使用copy_from_userspace()函数获取数据。然后read_ptr和结束指针重置缓冲内容是相反的:

buf->end = buf->data + size;

buf->read_ptr = buf->data;

if (buf->end > buf->data)

reverse_phrase(buf->data, buf->end - 1);

这里,reverse_phrase()所有重担依赖于reverse_word()函数,它非常短,内联标记。这是另一种常见的优化;然而,你不应该过度使用它,因为内联使内核映像很小。

最后,我们需要运行read_queue流程等数据,如前所述。wake_up_interruptible():

wake_up_interruptible(&buf->read_queue);

唷!您现在拥有了一个内核模块,至少成功编译。现在是时候测试它了。

调试内核代码

也许最常见的内核调试方法是印刷。您可以使用普通printk()与KERN_DEBUG日志级别(大概)如果你的愿望。然而,有更好的方法。使用pr_debug()或dev_dbg(),如果您正在编写一个设备驱动程序,有自己的“结构设备”:他们支持动态调试(dyndbg)功能,可以启用或禁用请求(参见文档/ dynamic-debug-howto.txt)。纯粹的开发信息,使用pr_devel(),这成为一个空操作,除非定义调试。要启用调试模块,包括:

CFLAGS_reverse.o := -DDEBUG

Makefile。之后,使用dmesg命令来查看生成的调试消息pr_debug()或pr_devel()。或者,您可以直接发送调试消息到控制台。要做到这一点,要么console_loglevel内核变量设置为8或更高版本(回声8 > /proc/sys/kernel/printk)或临时打印调试信息在高问题像KERN_ERR日志级别。自然,您应该删除这类调试语句之前发布您的代码。

注意内核消息出现在控制台上,而不是在一个终端模拟器窗口如Xterm;这就是为什么你会发现建议不要在X内核开发环境。

惊喜,惊喜!

编译模块和加载到内核:

$ make

$ sudo insmod reverse.ko buffer_size=2048

$ lsmod

reverse 2419 0

$ ls -l /dev/reverse

crw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse

一切似乎都在它应该在的地方。现在,测试模块是如何工作的,我们将编写一个小程序,改变它的第一个命令行参数。main()函数(无错误检查)看起来像这样:

int fd = open("/dev/reverse", O_RDWR);

write(fd, argv[1], strlen(argv[1]));

read(fd, argv[1], strlen(argv[1]));

printf("Read: %sn", argv[1]);

运行该程序:

$ ./test ‘A quick brown fox jumped over the lazy dog‘

READ:dog lazy the over jumped fox brown quick A

起作用了!学习这一点:尝试通过单字原图或单核苷酸短语,空的或非英语字符串(如果你有一个键盘布局设置)和任何其他东西。

现在,让我们把事情有点棘手。我们将创建两个进程共享的文件描述符(因此内核缓冲区)。一个不断将字符串写入装置,另一个会读它们。叉子(2)系统调用中使用下面的例子,但pthreads将工作。我也省略了的代码打开和关闭设备,错误检查(again):

char *phrase = "A quick brown fox jumped over the lazy dog";

if (fork())

/* Parent is the writer */

while (1)

write(fd, phrase, len);

else

/* child is the reader */

while (1) {

read(fd, buf, len);

printf("Read: %sn", buf);

}

你希望这个程序输出什么?下面是我的笔记本电脑里有什么

阅读:dog lazy the over jumped fox brown quick A

阅读:A kcicq brown fox jumped over the lazy dog

阅读:A kciuq nworb xor jumped fox brown quick A

阅读:A kciuq nworb xor jumped fox brown quick A

这儿怎么了?这是一个竞赛。我们认为读和写是原子,或者执行一个指令从一开始直到结束。然而内核是一个并发的野兽,它可以很容易地重新安排流程运行的内核部分写操作在某处reverse_phrase()函数。如果进程,read()将作者是有机会完成之前,它会看到数据处于不一致的状态。这样的错误是真的很难调试。但是如何修复它?

基本上,我们需要确保没有阅读方法可以执行,直到写方法返回。如果你设定一个多线程应用程序中,您可能已经看到同步原语(锁)互斥锁和信号量。Linux也他们,但是有细微差别。内核代码可以运行在进程上下文(用户空间代码的“代表”工作,作为我们的方法做),在中断上下文(例如,在一个IRQ处理程序)。如果你在进程上下文和锁已经被你需要,你只是睡觉和重试,直到你成功。你不能睡在中断上下文,因此代码在一个循环中旋转,直到锁可用。相应的原始称为自旋锁,但在我们的例子中,一个简单的互斥对象,只有一个进程可以在给定时间"持有"——就足够了。一个真实的代码也可以使用读写信号量,由于性能的原因。

也可以使用读写信号量,由于性能的原因。

锁始终保护一些数据(在我们的例子中,一个“结构缓冲”实例),而且它是很常见的将其纳入结构保护。所以我们添加一个互斥锁(struct互斥锁)到“结构缓冲”。我们还必须与mutex_init初始化互斥锁();buffer_alloc()是一个好地方。使用互斥锁的代码也必须包括linux / mutex.h。

互斥是更像一个红绿灯是没有用的,除非司机看,信号。所以我们需要更新reverse_read()和reverse_write()来获得互斥锁之前做任何缓冲和释放它当他们完成。让我们看看阅读方法——写的作品只是相同的方式:

static ssize_t reverse_read(struct file *file, char __user * out,

size_t size, loff_t * off)

{

struct buffer *buf = file->private_data;

ssize_t result;

if (mutex_lock_interruptible(&buf->lock)) {

result = -ERESTARTSYS;

goto out;

}

我们获得锁的函数在刚开始的时候。mutex_lock_interruptible()获取互斥锁并返回或把过程睡觉,直到互斥。和之前一样,_interruptible后缀意味着睡眠可以中断信号。

while (buf->read_ptr == buf->end) {

mutex_unlock(&buf->lock);

/* ... wait_event_interruptible() here ... */

if (mutex_lock_interruptible(&buf->lock)) {

result = -ERESTARTSYS;

goto out;

}

}

下面是我们的”等数据”循环。你不应该睡觉当持有互斥锁,或可能发生的情况称为“死锁”。所以,如果没有数据,我们释放互斥锁并调用wait_event_interruptible()。当它返回,我们重新获取互斥锁并继续像往常一样:

if (copy_to_user(out, buf->read_ptr, size)) {

result = -EFAULT;

goto out_unlock;

}

...

out_unlock:

mutex_unlock(&buf->lock);

out:

return result;

最后,函数结束时互斥对象已被解锁或者互斥锁被持有时发生错误。重新编译模块(别忘了重新加载)并再次运行第二个测试。现在您应该看到没有损坏的数据。

接下来是什么?

现在你是对内核黑客有点了解。我们刚刚触及只是表面的问题,其实还有更多问题。我们的第一个模块是故意简单的,然而你学到的概念将在更复杂的场景中也是保持不变的。并发性、方法表、注册回调,将流程睡眠和唤醒他们每个内核黑客应该做的事情,现在你已经看到他们在行动。也许你的内核代码最终会在主线Linux源代码树,如果发生这种情况写信给我们!

时间: 2024-10-07 23:25:37

编写你的第一个Linux内核模块(目前校对到杂项设备)的相关文章

一个linux内核模块移植到低版本时发生的异常

在3.10的内核版本下,有一个运行稳定的内核模块,移植到suse11的时候,编译正常,运行则直接出现crash: <4>[ 503.347297] CPU 0 <4>[ 503.347300] Modules linked in: caq_sendmsg(EN) mysendmsg(EN) witdriver(EN) datalink(EN) w83627dhg(EN) tipc(EX) ossmod(EN) pagecachelimit(EN) xfs ip6table_filt

Linux内核模块编程与内核模块LICENSE -《详解(第3版)》预读

Linux内核模块简介 Linux内核的整体结构已经非常庞大,而其包含的组件也非常多.我们怎样把需要的部分都包含在内核中呢?一种方法是把所有需要的功能都编译到Linux内核.这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核. 有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?Linux提供了这样的一种机制,这种机制被称为模块(Module).模块具有这样的特点. 模块本

Linux内核模块编程与内核模块LICENSE -《具体解释(第3版)》预读

Linux内核模块简单介绍 Linux内核的总体结构已经很庞大,而其包括的组件或许多.我们如何把须要的部分都包括在内核中呢?一种方法是把全部须要的功能都编译到Linux内核.这会导致两个问题.一是生成的内核会很大,二是假设我们要在现有的内核中新增或删除功能,将不得不又一次编译内核. 有没有一种机制使得编译出的内核本身并不须要包括全部功能,而在这些功能须要被使用的时候,其相应的代码被动态地载入到内核中呢?Linux提供了这样的一种机制,这样的机制被称为模块(Module).模块具有这样的特点. 模

Smart210学习记录-------linux内核模块

Linux 驱动工程师需要牢固地掌握 Linux 内核的编译方法以为嵌入式系统构建可运行的Linux 操作系统映像.在编译 LDD6410 的内核时,需要配置内核,可以使用下面命令中的 一个: #make config(基于文本的最为传统的配置界面,不推荐使用) #make menuconfig(基于文本菜单的配置界面) #make xconfig(要求 QT 被安装) #make gconfig(要求 GTK+被安装) 在配置Linux 2.6内核所使用的make config.make me

Linux内核模块编写详解

内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了.Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统,本文给大家介绍linux内核模块编写,需要的朋友可以参考下 内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了.Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统.浮点运算做起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,

linux 内核模块的编写,插入,显示及卸载

环境:ubuntu 8.04 内核版本:2.6.32.59-debug 1.编写文件hello.c #include <linux/init.h> #include <linux/kernel.h> //printk /*写内核驱动的时候 必须加载这个头文件,作用是动态的将模块加载到内核中去,常用的宏定义如 MODULE_LICESENCE(),MODULE_AUTHOR(),等在此文件中*/ #include <linux/module.h> MODULE_LICEN

Linux内核模块编写模板

本文讲述如何编写Linux内核模块,需要两个文件 mytest.c,Makefile. 存放到同一个文件夹目录,然后make.make install,就可以完成内核模块的编译生成和安装. 然后通过dmesg命令就可以看到从模块初始化函数输出的信息. mytest.c: #include <linux/module.h> #include <linux/crypto.h> #include <linux/kernel.h> #include <linux/init

Linux内核模块简介

1. 宏内核与微内核 内核(Kernel)在计算机科学中是操作系统最基本的部分,主要负责管理系统资源.中文版维基百科上将内核分为四大类:单内核(宏内核):微内核:混合内核:外内核. 混合内核实质上也是微内核,而外内核是一种比较极端的设计方法,目前还处于研究阶段,所以我们就着重讨论宏内核与微内核两种内核. 简单的介绍,宏内核(Monolithickernel)是将内核从整体上作为一个大过程来实现,所有的内核服务都在一个地址空间运行,相互之间直接调用函数,简单高效.微内核(Microkernel)功

第六章 第一个Linux驱动程序:统计单词个数 心得笔记

一.Linux系统将每个驱动都映射成一个文件.这些文件称为设备文件或驱动文件,都保存在/dev目录中.这使得与Linux驱动进行交互就向与普通文件进行交互一样容易.大多数Linux驱动都有与其对应的设备文件,因此与Linux驱动交换数据变成与驱动设备交换数据. 二.编写Linux驱动程序 1.建立Linux驱动骨架           Linux内核在使用驱动时需要装载与卸载驱动        装载驱动:建立设备文件.分配内存地址空间等:module_init 函数处理驱动初始化