微软交流社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 111|回复: 5

跟着Linux设备驱动学软件设计 01:内核模块

[复制链接]

1

主题

3

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-11-27 17:12:43 | 显示全部楼层 |阅读模式
Hello World 模块

Linux 设备驱动通常以内核模块的形式出现,因此学习编写 Linux 内核模块是学习编写 Linux 设备驱动的先决条件。
我们从最简单的 hello world 模块开始来建立对内核模块的感性认识。下面我们来看这个示例。
hello world 模块源代码:
#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");  // 告诉内核该模块采用的许可证

static int __init hello_init(void)
{
        printk(KERN_EMERG "hello world\n");
        return 0;
}

static void __exit hello_exit(void)
{
        printk(KERN_EMERG "goodbye world\n");
}

module_init(hello_init);  // 告诉内核该模块被装载时调用 hello_init()
module_exit(hello_exit);  // 告诉内核该模块被移除时调用 hello_exit()hello world 模块 Makefile:
KVERS = $(shell uname -r)

# modules
obj-m += hello.o

# Specify flags for the module compilation
# EXTRA_CFLAG = -g -o0

build:modules

# -C选项指定内核源代码目录,M=选项指定模块源代码目录
modules:
        make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules

clean:
        make -C /lib/modules/$(KVERS)/build M=$(CURDIR) cleanhello world 模块的编译:
pi@raspberrypi:~/code/baibai_test/hello $ make
make -C /lib/modules/5.15.40-v7l/build M=/home/pi/code/baibai_test/hello modules
make[1]: Entering directory '/home/pi/code/linux-rpi-5.15.y'
  CC [M]  /home/pi/code/baibai_test/hello/hello.o
  MODPOST /home/pi/code/baibai_test/hello/Module.symvers
  CC [M]  /home/pi/code/baibai_test/hello/hello.mod.o
  LD [M]  /home/pi/code/baibai_test/hello/hello.ko
make[1]: Leaving directory '/home/pi/code/linux-rpi-5.15.y'hello world 装载和卸载:
pi@raspberrypi:~/code/baibai_test/hello $ sudo insmod hello.ko
Message from syslogd@raspberrypi at Oct 18 03:17:41 ...
kernel:[ 4340.242938] hello world

pi@raspberrypi:~/code/baibai_test/hello $ sudo rmmod hello
Message from syslogd@raspberrypi at Oct 18 03:17:49 ...
kernel:[ 4348.772362] goodbye world内核模块对 linux 内核架构的作用

我们从前面的示例已经看到,编写一个内核模块倒是挺简单的(至少当它仅仅需要显示hello world的时候),但为什么 Linux 要搞出内核模块这个东东呢?下面我们尝试从设计的角度来推演一下这个问题。
从宏内核说起

众所周知 Linux Kernel 使用的是宏内核。宏内核相对应的是微内核。
如果仅讨论架构的优劣,宏内核肯定是不如微内核的。关于宏内核和微内核, Linus 和 微内核操作系统 Minix 的作者 Tanenbaum 有一个著名辩论,其中 Linus 自己也说:“True, linux is monolithic, and I agree that microkernels are nicer.”
那么为什么 Linus 还是为 Linux 选择了宏内核架构呢?我们还是从刚才那个著名辩论中来找答案,Linus 说道:
“Linux has very much been a hobby (but a serious one: the best type) for me: I get no money for it, and it's not even part of any of my studies in the university. I've done it all on my own time, and on my own machine.”
“What you don't mention is that minix doesn't do the micro-kernel thing very well, and has problems with real multitasking (in the kernel).”
“I got my 386 last January, and linux was partly a project to teach me about it. Many things should have been done more portably if it would have been a real project. I'm not making overly many excuses about it though: it was a design decision, and last april when I started the thing, I didn't think anybody would actually want to use it. ”
。。。。。。
(附:Linus vs Tanenbaum 辩论原文链接:http://choices.cs.illinois.edu/cache/Linus_vs_Tanenbaum.html )
内容比较多,我这里只摘了几句有代表性的话。总结来说,这里面并没有非常直白地说为什么选择宏内核架构,但我们能够看到几个关键信息,一是 Linus 最初开发 Linux 时没想投入太多时间,也没想着赚钱,也没想过会被其他人使用,仅仅就是因为自己有了台PC,想在上面跑个操作系统来玩;二是微内核架构虽好,但实现起来比宏内核复杂,Tanenbaum 的 Minix 就有不少实现上的问题,导致 Minix 最终体现出来的效果并不好。
所以,我们可以推断 Linus 为 Linux 选择宏内核架构很大程度是因为宏内核简单、能用、好实现
宏内核的缺点

Linux 的发展显然是远远超出了最初的预期,我们来分析下随着规模的增大 Linux 的宏内核会遇到什么样的问题:

  • 所有内核功能整体编译在一起,即使一开始不用的功能也要先编译进内核,即使过程中不会再使用的功能也必须一直保持在内核中,非常不灵活;如果面对提前难以预知的热插拔设备,几乎就没法实现,难不成每次新插一个未知设备就要重装系统?
  • 每次增减、删除、修改某个功能都要重新编译、重新加载整个内核,而且内核越大,编译、加载也会越慢,对调试非常不友好。我只想调试1K代码,结果每次都要背上整个内核的包袱,这严重降低了调试效率。
  • 内核镜像文件会比较大,而且整个内核镜像会被加载到内存中运行,会占用较多内存空间。
  • C语言的函数默认是全局的,所以内核的函数默认可以在整个内核范围内被调用,调用关系难以控制,整个内核很容易演变成糟糕的大泥球(大泥球是一种著名的反架构模式,可参考:https://wenku.baidu.com/view/3d8d6702b7daa58da0116c175f0e7cd18425188a.html)。
    。。。。。。
如何改善?

前面把缺点列出来了,其实这些都是宏内核天然的问题,而微内核天然就没有这些问题(当然微内核会有其它问题,但跟本文的内核模块没什么关系,所以不讨论)。那很自然就能想到,那就跟微内核学习嘛。
像微内核一样,把一些功能划分出来。但因为是宏内核,这些功能还是在内核态运行,但是逻辑上是独立的,可以单独编译、单独装载和卸载。这就是 Linux 的内核模块啦。
这样一来,上面4个缺点中的前面3个基本就解决了。第4个缺点也得到一部分消减,毕竟分了模块,模块和模块之间是有逻辑边界的。如果想进一步消减第4个缺点,那就要把函数的范围默认限定在模块内部,模块对外的函数需要显示声明。于是 Linux 又设计了一个机制:只有显示用 EXPORT_SYMBOL() 或 EXPORT_SYMBOL_GPL() 声明的函数才能被其它模块调用。这样第4个缺点也基本得到了解决。
回复

使用道具 举报

1

主题

3

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-11-27 17:13:31 | 显示全部楼层
大佬,问个问题,函数名前那个_init是干嘛用的,c语言有这种写法嘛
回复

使用道具 举报

2

主题

7

帖子

10

积分

新手上路

Rank: 1

积分
10
发表于 2022-11-27 17:14:08 | 显示全部楼层
现在用手机输入不太方便,先简单回答下:1._init是干嘛用: _init告诉编译器,将函数放在.init.text这个代码区域2._init是linux kernel中定义的一个宏,这个宏展开后是__attribute__(xxxx)。这个__attribute__它是GNU C的语法,它不是C89 C99中的语法
回复

使用道具 举报

0

主题

4

帖子

0

积分

新手上路

Rank: 1

积分
0
发表于 2022-11-27 17:14:42 | 显示全部楼层
1._init是干嘛用: _init告诉编译器,将函数放在.init.text这个代码区域2._init是linux kernel中定义的一个宏,这个宏展开后是__attribute__(xxxx)。这个__attribute__它是GNU C的语法,它不是C89 C99中的语法
回复

使用道具 举报

0

主题

1

帖子

0

积分

新手上路

Rank: 1

积分
0
发表于 2022-11-27 17:15:31 | 显示全部楼层
这种语法有什么官方文档嘛,我man出来的都是一些编译器的用法
回复

使用道具 举报

1

主题

2

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2022-11-27 17:15:57 | 显示全部楼层
可以在gnu官网上找gcc的文档。这是其中一个版本的链接 http://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc.pdf      第6.33节 declaring attributes of functions
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|微软交流社区

GMT+8, 2025-1-22 09:45 , Processed in 0.075151 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表