Kernel Module实战指南(二):内核模块和应用程序的区别

原文地址:Kernel Module实战指南(二):内核模块和应用程序的区别

Introduction

你已经写出了第一个内核模块Hello World!有没有发现内核模块和应用程序写法的不同?
下面我将从概念和原理上进行介绍,内核模块和应用程序为什么不同。

内核模块和应用程序的区别

出入口

应用程序的入口始终是main()函数,而出口是main()函数的return。
内核模块的入口是init_module(),而出口是cleanup_module()。

函数库

应用程序可以调用很多C标准库中的函数,如printf()。这些函数都编译到libc中,只有当你的代码在编译的链接阶段时,才会和libc中实际的函数地址绑定。
内核模块则不同,内核中不存在标准库,自然没有类似printf()之类的函数。不过内核中提供了一些函数/符号,你可以通过下面的命令查看:

$ cat /proc/kallsyms | grep printk
...
0000000000000000 T printk
0000000000000000 T printk_emit
...

所有内核中可以用的函数/符号都在/proc/kallsyms,换句话说,你不能使用这里之外的函数/符号。

CPU执行模式

在Intel x86架构上中,有四种模式,也叫ring 0 - ring 3,模式之间的权限不同,这里的权限指的是对硬件设备的操作,如读写内存,读写硬盘等。
Linux使用其中两种模式,即内核模式/特权模式/supervisor mode/ring 0,以及用户模式/非特权模式/user mode/ring 3。
应用程序跑的代码,包括所调用的C标准库,都是跑在用户模式中。而内核模块跑的代码,都是跑在内核模式中。用户模式想要进入内核模式,入口之一便是系统调用函数。

用户态和内核态之间的交互

用户态和内核态进行交互的方式之一,是上面我们提到过系统调用函数。什么是系统调用函数?你可以简单认为,libc调用的下层函数,就是系统调用函数,如libc中open()的实现,最终需要调用系统调用函数__NR_open(),进入内核,在内核态中访问硬盘,打开文件。
另一种方式是设备驱动程序,Linux的设备驱动都存放在/dev下面,当用户态程序和设备驱动交互时,设备驱动在内核态执行代码逻辑,然后将结果返回给用户态。

设备驱动

查看硬盘驱动:

$ls -l /dev/sda*
brw-rw---- 1 root disk 8, 0 Jan 26 23:00 /dev/sda
brw-rw---- 1 root disk 8, 1 Jan 26 23:00 /dev/sda1
brw-rw---- 1 root disk 8, 2 Jan 26 23:00 /dev/sda2
brw-rw---- 1 root disk 8, 3 Jan 26 23:00 /dev/sda3

如果你找不到sda,那么你可以查询ls -l /dev/hda*。
这里需要简单介绍一下,列的含义。第一列的b,是block的意思,代码这个设备是块设备(对应的是c, char字符设备)。

块设备:存储单位一般是固定大小的块,支持随机读写。对于读写请求有缓存,可以选择最佳的位置存储。(有没有想到硬盘的寻址算法?)
字符设备:存储大小不定,存储流式数据,一般不支持随机读写,只支持顺序读写。

8的意思是,驱动的主版本号是8,这个主版本号是给内核看的。
后面的0-3的意思是,副版本号是0-3,这个副版本号是给设备驱动程序看的,内核不关心。
这里只是抛砖迎玉,后面我们再详细介绍设备驱动。

Summary

我们进一步深入了内核模块,介绍了内核模块和应用程序的区别。虽然概念性内容偏多,但这却是理解内核模块的必经之路。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用信号量实现Linux应用程序内核模块互斥的案例: Linux应用程序: ```c #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define DEVICE "/dev/my_device" int main(int argc, char* argv[]) { int fd = open(DEVICE, O_RDWR); if (fd < 0) { perror("open"); return -1; } // 获取信号量 if (ioctl(fd, 0) < 0) { perror("ioctl"); return -1; } printf("This is a user process.\n"); // 释放信号量 if (ioctl(fd, 1) < 0) { perror("ioctl"); return -1; } close(fd); return 0; } ``` Linux内核模块: ```c #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/semaphore.h> #include <linux/ioctl.h> #define DEVICE "my_device" #define MAJOR_NUM 243 static DECLARE_MUTEX(my_mutex); static int device_open(struct inode* inode, struct file* file) { if (down_interruptible(&my_mutex)) { // 获取信号量 printk(KERN_ALERT "Semaphore acquire failed.\n"); return -1; } printk(KERN_INFO "This is a kernel module.\n"); return 0; } static int device_release(struct inode* inode, struct file* file) { up(&my_mutex); // 释放信号量 return 0; } static long device_ioctl(struct file* file, unsigned int cmd, unsigned long arg) { switch (cmd) { case 0: // 获取信号量 if (down_interruptible(&my_mutex)) { printk(KERN_ALERT "Semaphore acquire failed.\n"); return -1; } break; case 1: // 释放信号量 up(&my_mutex); break; default: return -EINVAL; } return 0; } static struct file_operations fops = { .open = device_open, .release = device_release, .unlocked_ioctl = device_ioctl, }; static int __init my_init(void) { if (register_chrdev(MAJOR_NUM, DEVICE, &fops) < 0) { printk(KERN_ALERT "Failed to register device.\n"); return -1; } printk(KERN_INFO "Module loaded.\n"); return 0; } static void __exit my_exit(void) { unregister_chrdev(MAJOR_NUM, DEVICE); printk(KERN_INFO "Module unloaded.\n"); } module_init(my_init); module_exit(my_exit); ``` 在上面的代码中,应用程序通过打开设备文件并调用ioctl函数来获取和释放信号量,内核模块通过实现设备文件的open、release和ioctl函数来管理信号量。当应用程序获取信号量时,内核模块会阻塞等待信号量释放;当内核模块获取信号量时,应用程序会阻塞等待信号量释放。通过信号量的机制,保证了Linux应用程序内核模块之间的互斥。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值