使用md格式重写翻译

This commit is contained in:
寻觅(樊旭东) 2024-09-03 14:48:19 +08:00
parent b8430a39b5
commit 88db7fbc28
96 changed files with 3049 additions and 4924 deletions

View File

@ -1,30 +0,0 @@
Jian-Xing Wu <fdgkhdkgh@gmail.com> 吳建興
Meng-Zong Tsai <hwahwa649@gmail.com> fennecJ <58484289+fennecJ@users.noreply.github.com>
Meng-Zong Tsai <hwahwa649@gmail.com> fennecJ <hwahwa649@gmail.com>
Jim Huang <jserv.tw@gmail.com> Jim Huang <jserv@ccns.ncku.edu.tw>
Jim Huang <jserv.tw@gmail.com> Jim Huang <jserv@biilabs.io>
Chih-En Lin <shiyn.lin@gmail.com> linD026 <shiyn.lin@gmail.com>
Chih-En Lin <shiyn.lin@gmail.com> linD026 <66012716+linD026@users.noreply.github.com>
Chih-En Lin <shiyn.lin@gmail.com> linD026 <0086d026@email.ntou.edu.tw>
Chih-En Lin <shiyn.lin@gmail.com> linzhien <0086d026@email.ntou.edu.tw>
mengxinayan <31788564+mengxinayan@users.noreply.github.com> 萌新阿岩
Ethan Chan <F04066028@gs.ncku.edu.tw> tzuyichan
Peter Lin <peterlin@qilai.dev> lyctw <lyctw.ee@gmail.com>
Peter Lin <peterlin@qilai.dev> Peter Lin <peterlin.tw@protonmail.com>
Che-Chia Chang <vivahavey@gmail.com> gagachang
Shao-Tse Hung <ccs100203@gmail.com> ccs100203
Yi-Wei Lin <s921975628@gmail.com> RinHizakura
Chih-Hsuan Yang <zxc25077667@gmail.com> 25077667
Yin-Chiuan Chen <leovincentseles@gmail.com> leovincentseles
Xatierlike Lee <xatierlike@gmail.com> xatier
Chin Yik Ming <yikming2222@gmail.com> ChinYikMing
Tse-Wei Lin <20110901eric@outlook.com> 2011eric
Yu-Hsiang Tseng <asas1asas200@gmail.com> asas1asas200
Kuan-Wei Chiu <visitorckw@gmail.com> visitorckw
I-Hsin Cheng <richard120310@gmail.com> vax-r
Wei-Hsin Yeh <weihsinyeh168@gmail.com> weihsinyeh <weihsinyeh168@gmail.com>
Wei-Hsin Yeh <weihsinyeh168@gmail.com> weihsinyeh <90430653+weihsinyeh@users.noreply.github.com>
Cheng-Shian Yeh <yehchanshen@gmail.com> yeh-sudo
Yo-Jung Lin <0xff07@gmail.com> <leo.lin@canonical.com> 0xff07
YYGO <srayuws@users.noreply.github.com> srayuws
Yen-Yu Chen <matt162162162@gmail.com> <69316865+YLowy@users.noreply.github.com> Ylowy

View File

@ -1,24 +0,0 @@
PROJ = lkmpg
all: $(PROJ).pdf
$(PROJ).pdf: lkmpg.tex
@if ! hash latexmk; then echo "No Latexmk found. See https://mg.readthedocs.io/latexmk.html for installation."; exit 1; fi
rm -rf _minted-$(PROJ)
latexmk -shell-escape lkmpg.tex -pdf
html: lkmpg.tex html.cfg assets/Manrope_variable.ttf
sed $ 's/\t/ /g' lkmpg.tex > lkmpg-for-ht.tex
make4ht --shell-escape --utf8 --format html5 --config html.cfg --output-dir html lkmpg-for-ht.tex "fn-in"
ln -sf lkmpg-for-ht.html html/index.html
cp assets/Manrope_variable.ttf html/Manrope_variable.ttf
rm -f lkmpg-for-ht.tex lkmpg-for-ht.xref lkmpg-for-ht.tmp lkmpg-for-ht.html lkmpg-for-ht.css lkmpg-for-ht.4ct lkmpg-for-ht.4tc lkmpg-for-ht.dvi lkmpg-for-ht.lg lkmpg-for-ht.idv lkmpg*.svg lkmpg-for-ht.log lkmpg-for-ht.aux
rm -rf _minted-$(PROJ) _minted-lkmpg-for-ht
indent:
(cd examples; find . -name '*.[ch]' | xargs clang-format -i)
clean:
rm -f *.dvi *.aux *.log *.ps *.pdf *.out lkmpg.bbl lkmpg.blg lkmpg.lof lkmpg.toc lkmpg.fdb_latexmk lkmpg.fls lkmpg-*
rm -rf html
.PHONY: html

View File

@ -2,25 +2,25 @@
# Linux 内核模块编程指南 # Linux 内核模块编程指南
该项目不断更新《Linux 内核模块编程指南》,并提供最新 5.x 和 6.x 内核版本的[示例](examples/)。该指南自 2001 年开始发布,网络上的大多数副本只介绍了旧的 2.6.x 内核 该项目是不断更新《Linux 内核模块编程指南》的中文译本, 但需要注意的是此项目和原版在使用的中央处理器架构上有所不同, 原版是使用的主流X86架构, 本版本会将部分涉及到架构相关的内容修改为龙架构,目前本译本已支持到 6.9.x 版本的内核, 相关[示例](示例/)
中文翻译会根据受限于译者的理解能力可能会出现一些误差, 如有能力可以查看[英文原版](https://sysprog21.github.io/lkmpg/#kernel-module-package) 本项目原版是使用TeXLie格式编写, 在译者对此格式较为陌生, 已改为更为主流的 `markdown` 格式重写, 并且在翻译途中为了方便读者理解一些不常见的英文缩写, 添加了部分注释
## 开始使用 ## 开始使用
### 概述 在线文档: http://223.76.216.188:50201/books/linux/chapter/6c4b7
1. 获取最新源代码(相信你能看到这个说明就已经有源码了) > 目录:
2. 安装先决条件 > * [0 准备工作](文档/0-准备工作)
> 要从源代码生成图书,需要使用 make 和 [TeXLive](https://www.tug.org/texlive/) ([MacTeX](https://www.tug.org/mactex/))。 > * [1 编写驱动](文档/1-编写驱动)
> 对于不同的发行版会有不同的包名其中主要包名有texlive(安同). texlive-full(debian系), texlive-binextra texlive-bin(arch系) mactex(mac) > * [2 驱动相关文件](文档/2-驱动相关文件)
3. 生成 PDF 和/或 HTML 文档 > * [3 ioctl](文档/3-ioctl)
> 可以用以下命令生成文档: > * [4 系统调用](文档/4-系统调用)
```bash > * [5 阻塞进程和线程](文档/5-阻塞进程和线程)
make all # 生成PDF文档 > * [6 内核模块的锁](文档/6-内核模块的锁)
make html # 将 TeX 转换为 HTML > * [7 驱动与用户交互](文档/7-驱动与用户交互)
make clean # 删除生成的文件 > * [8 调度与中断](文档/8-调度与中断)
``` > * [](文档/)
## 版权说明 ## 版权说明

Binary file not shown.

View File

@ -1,59 +0,0 @@
Amit Dhingra, % <mechanicalamit@gmail.com>
Andy Shevchenko, % <andriy.shevchenko@linux.intel.com>
Arush Sharma, % <46960231+arushsharma24@users.noreply.github.com>
Benno Bielmeier, % <32938211+bbenno@users.noreply.github.com>
Bob Lee, % <defru04002@gmail.com>
Brad Baker, % <brad@brdbkr.com>
Che-Chia Chang, % <vivahavey@gmail.com>
Cheng-Shian Yeh, % <yehchanshen@gmail.com>
Chih-En Lin, % <shiyn.lin@gmail.com>
Chih-Hsuan Yang, % <zxc25077667@gmail.com>
Chih-Yu Chen, % <34228283+chihyu1206@users.noreply.github.com>
Ching-Hua (Vivian) Lin, % <jkrvivian@gmail.com>
Chin Yik Ming, % <yikming2222@gmail.com>
cvvletter, % <cvvletter@pm.me>
Cyril Brulebois, % <cyril@debamax.com>
Daniele Paolo Scarpazza, % <>
David Porter, % <>
demonsome, % <horseradish1208@gmail.com>
Dimo Velev, % <>
Ekang Monyet, % <ekangmonyet@posteo.net>
Ethan Chan, % <F04066028@gs.ncku.edu.tw>
Francois Audeon, % <>
Gilad Reti, % <gilad.reti@gmail.com>
heartofrain, % <heartofrain@outlook.com>
Horst Schirmeier, % <>
Hsin-Hsiang Peng, % <hsinspeng@gmail.com>
Ignacio Martin, % <>
I-Hsin Cheng, % <richard120310@gmail.com>
Iûnn Kiàn-îng, % <black.yangcr@gmail.com>
Jian-Xing Wu, % <fdgkhdkgh@gmail.com>
Johan Calle, % <43998967+jcallemc@users.noreply.github.com>
keytouch, % <qsytech@126.com>
Kohei Otsuka, % <13173186+rjhcnf@users.noreply.github.com>
Kuan-Wei Chiu, % <visitorckw@gmail.com>
manbing, % <manbing3@gmail.com>
Marconi Jiang, % <marconi1964@yahoo.com>
mengxinayan, % <31788564+mengxinayan@users.noreply.github.com>
Meng-Zong Tsai, % <hwahwa649@gmail.com>
Peter Lin, % <peterlin@qilai.dev>
Roman Lakeev, % <>
Sam Erickson, % <samuelerickson977@gmail.com>
Shao-Tse Hung, % <ccs100203@gmail.com>
Shih-Sheng Yang, % <james1qaz2wsx12qw@gmail.com>
Stacy Prowell, % <sprowell@gmail.com>
Steven Lung, % <1030steven@gmail.com>
Tristan Lelong, % <tristan.lelong@blunderer.org>
Tse-Wei Lin, % <20110901eric@outlook.com>
Tucker Polomik, % <tucker.polomik@inficon.com>
Tyler Fanelli, % <tfanelli@redhat.com>
VxTeemo, % <tcccvvv123@gmail.com>
Wei-Hsin Yeh, % <weihsinyeh168@gmail.com>
Wei-Lun Tsai, % <alan23273850@gmail.com>
Xatierlike Lee, % <xatierlike@gmail.com>
Yen-Yu Chen, % <matt162162162@gmail.com>
Yin-Chiuan Chen, % <leovincentseles@gmail.com>
Yi-Wei Lin, % <s921975628@gmail.com>
Yo-Jung Lin, % <0xff07@gmail.com>
Yu-Hsiang Tseng, % <asas1asas200@gmail.com>
YYGO. % <srayuws@users.noreply.github.com>

View File

@ -1,91 +0,0 @@
\Preamble{xhtml}
\Configure{tableofcontents*}{chapter,section,subsection}
\Css{html {
width: 100vw;
overflow-x: hidden;
}}
\Css{@font-face {
font-family: Manrope;
src: url(Manrope_variable.ttf);
}}
\Css{body {
max-width: 55rem;
box-sizing: border-box;
padding: 1rem;
margin: 0 auto;
overflow-x: hidden;
background-color: \#F4ECD8;
color: \#5B464B;
line-height: 1.5;
}}
\Css{a {
color: \#0060DF;
}}
\Css{p, a {
font-size: 1.2rem;
font-family: Manrope;
}}
\Css{p + pre {
font-size: 1.1em;
}}
\Css{div.author {
white-space: normal;
}}
\Css{img.math {
height: 1rem;
vertical-align: top;
}}
\Css{pre.fancyvrb, {
white-space: pre;
}}
\Css{figure, .fancyvrb, .verbatim {
margin-inline: 0;
overflow-x: auto;
}}
\Css{.ecrm-0500 {
font-size: 70\%;
font-style: italic;
color: gray;
width: 1.5rem;
display: inline-block;
-webkit-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}}
\Css{.flushright:first-child {
position:absolute;
top: 10px;
right: 50px;
}}
\Css{.right {
text-align: right;
}}
\AtBeginDocument{%
\Configure{@HEAD}{\HCode{
<script async defer src="https://buttons.github.io/buttons.js"></script>
<div class="right">
<a class="github-button" href="https://github.com/sysprog21/lkmpg" data-size="large" aria-label="View on GitHub">View on GitHub</a>
<a class="github-button" href="https://github.com/sysprog21/lkmpg/releases/download/latest/lkmpg.pdf" data-icon="octicon-download" data-size="large" aria-label="Download PDF document">Download PDF document</a>
<br><br>
</div>
\Hnewline}}
}
\begin{document}
\EndPreamble

View File

@ -1,37 +0,0 @@
\newminted[code]{c}{frame=single,
framesep=2mm,
baselinestretch=1,
fontsize=\footnotesize,
breaklines,
breakafter=d,
linenos
}
\usemintedstyle{vs}
\NewDocumentCommand{\samplec}{oom}{%
\IfNoValueTF{#1}%
{%
\inputminted[frame=single, framesep=2mm, baselinestretch=1, fontsize=\footnotesize, breaklines, breakafter=d, linenos]{c}{#3}%
}%
{%
\IfNoValueTF{#2}%
{%
\inputminted[frame=single, framesep=2mm, baselinestretch=1, fontsize=\footnotesize, breaklines, breakafter=d, firstline=#1, linenos]{c}{#3}%
}%
{%
\inputminted[frame=single, framesep=2mm, baselinestretch=1, fontsize=\footnotesize, breaklines, breakafter=d, firstline=#1, lastline=#2, linenos]{c}{#3}%
}%
}%
}
\newminted[codebash]{bash}{frame=single,
framesep=2mm,
baselinestretch=1.2,
breaklines,
breakafter=d,
linenos
}
\newmintinline[sh]{bash}{}
\newmintinline[cpp]{c}{}

View File

@ -1,2 +0,0 @@
\newcommand*{\src}[2][]{\href{https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/#2}%
{\ifthenelse{\equal{#1}{}}{#2}{#1}}}

1894
lkmpg.tex

File diff suppressed because it is too large Load Diff

2645
nhmk.md

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +0,0 @@
Amit Dhingra,<mechanicalamit@gmail.com>
Andy Shevchenko,<andriy.shevchenko@linux.intel.com>
Arush Sharma,<46960231+arushsharma24@users.noreply.github.com>
Benno Bielmeier,<32938211+bbenno@users.noreply.github.com>
Bob Lee,<defru04002@gmail.com>
Brad Baker,<brad@brdbkr.com>
Che-Chia Chang,<vivahavey@gmail.com>
Cheng-Shian Yeh,<yehchanshen@gmail.com>
Chih-En Lin,<shiyn.lin@gmail.com>,<0086d026@email.ntou.edu.tw>,<66012716+linD026@users.noreply.github.com>
Chih-Hsuan Yang,<zxc25077667@gmail.com>
Chih-Yu Chen,<34228283+chihyu1206@users.noreply.github.com>
Ching-Hua (Vivian) Lin,<jkrvivian@gmail.com>
Chin Yik Ming,<yikming2222@gmail.com>
cvvletter,<cvvletter@pm.me>
Cyril Brulebois,<cyril@debamax.com>
Daniele Paolo Scarpazza,<>
David Porter,<>
demonsome,<horseradish1208@gmail.com>
Dimo Velev,<>
Ekang Monyet,<ekangmonyet@posteo.net>
Ethan Chan,<F04066028@gs.ncku.edu.tw>
Francois Audeon,<>
Gilad Reti,<gilad.reti@gmail.com>
heartofrain,<heartofrain@outlook.com>
Horst Schirmeier,<>
Hsin-Hsiang Peng,<hsinspeng@gmail.com>
Ignacio Martin,<>
I-Hsin Cheng,<richard120310@gmail.com>
Iûnn Kiàn-îng,<black.yangcr@gmail.com>
Jian-Xing Wu,<fdgkhdkgh@gmail.com>
Johan Calle,<43998967+jcallemc@users.noreply.github.com>
keytouch,<qsytech@126.com>
Kohei Otsuka,<13173186+rjhcnf@users.noreply.github.com>
Kuan-Wei Chiu,<visitorckw@gmail.com>
manbing,<manbing3@gmail.com>
Marconi Jiang,<marconi1964@yahoo.com>
mengxinayan,<31788564+mengxinayan@users.noreply.github.com>
Meng-Zong Tsai,<hwahwa649@gmail.com>,<58484289+fennecJ@users.noreply.github.com>
Peter Lin,<peterlin@qilai.dev>,<lyctw.ee@gmail.com>,<peterlin.tw@protonmail.com>
Roman Lakeev,<>
Sam Erickson,<samuelerickson977@gmail.com>
Shao-Tse Hung,<ccs100203@gmail.com>
Shih-Sheng Yang,<james1qaz2wsx12qw@gmail.com>
Stacy Prowell,<sprowell@gmail.com>
Steven Lung,<1030steven@gmail.com>
Tristan Lelong,<tristan.lelong@blunderer.org>
Tse-Wei Lin,<20110901eric@outlook.com>
Tucker Polomik,<tucker.polomik@inficon.com>
Tyler Fanelli,<tfanelli@redhat.com>
VxTeemo,<tcccvvv123@gmail.com>
Wei-Hsin Yeh,<weihsinyeh168@gmail.com>,<90430653+weihsinyeh@users.noreply.github.com>
Wei-Lun Tsai,<alan23273850@gmail.com>
Xatierlike Lee,<xatierlike@gmail.com>
Yen-Yu Chen,<matt162162162@gmail.com>,<69316865+YLowy@users.noreply.github.com>
Yin-Chiuan Chen,<leovincentseles@gmail.com>
Yi-Wei Lin,<s921975628@gmail.com>
Yo-Jung Lin,<0xff07@gmail.com>,<leo.lin@canonical.com>
Yu-Hsiang Tseng,<asas1asas200@gmail.com>
YYGO,<srayuws@users.noreply.github.com>

View File

@ -1 +0,0 @@
Jim Huang, One of main author

View File

@ -1,8 +0,0 @@
Daniele Paolo Scarpazza,<>
David Porter,<>
Dimo Velev,<>
Francois Audeon,<>
Horst Schirmeier,<>
Ignacio Martin,<>
Roman Lakeev,<>
Tristan Lelong,<tristan.lelong@blunderer.org>

View File

@ -1,60 +0,0 @@
#!/usr/bin/env bash
FORMAT="%aN,<%aE>" #Set git output format in "Name,<Email>" //Capital in aN and aE means replace str based on .mailmap
TARGET=(examples lkmpg.tex) #Target files we want to trace
DIR=`git rev-parse --show-toplevel` #Get root dir of the repo
TARGET=("${TARGET[@]/#/$DIR/}") #Concat $DIR BEFORE ALL elements in array TARGET
#The str in each line should be Username,<useremail>
function gen-raw-list()
{
git log --pretty="$FORMAT" ${TARGET[@]} | sort -u
}
function parse-list()
{
> Contributors # Clear contributors' list (Overwrite with null)
while read -r line; do
User=`echo "$line" | awk -F "," '{print $1}'`
if [[ `grep -w "$User" Exclude` ]]; then
echo "[skip] $User"
continue;
fi
echo "[Add] $User"
MainMail=`echo "$line" | awk -F "[<*>]" '{print $2}'`
Emails=(`cat $DIR/.mailmap | grep -w "$User" | awk -F "[<*>]" '{print $4}' | sort -u`)
for Email in ${Emails[@]}; do
if [[ "$Email" != "$MainMail" ]]; then
line="$line,<$Email>";
fi
done
echo "$line" >> Contributors
done <<< $(gen-raw-list)
cat Include >> Contributors
}
function sort-list()
{
if [[ `git diff Contributors` ]]; then
sort -f -o Contributors{,}
git add $DIR/scripts/Contributors
fi
}
#For all lines before endline, print "name, % <email>"
#For endline print "name. % <email>"
function gen-tex-file()
{
cat Contributors | awk -F "," \
' BEGIN{k=0}{name[k]=$1;email[k++]=$2}
END{
for(i=0;i<k;i++){
name[i]=(i<k-1)?name[i]",":name[i]".";
printf("%-30s %% %s\n",name[i],email[i]);
}
}
'
}
parse-list
sort-list
gen-tex-file > $DIR/contrib.tex

17
示例/0-hello/Makefile Normal file
View File

@ -0,0 +1,17 @@
obj-m += hello-1.o
obj-m += hello-2.o
obj-m += hello-3.o
obj-m += hello-4.o
obj-m += hello-5.o
obj-m += startstop.o
startstop-objs := start.o stop.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

21
示例/0-hello/hello-1.c Normal file
View File

@ -0,0 +1,21 @@
/*
* hello-1.c -
*/
#include <linux/module.h> /* 内核模块必要的头 */
#include <linux/printk.h> /* 引入 pr_info() */
int init_module(void)
{
pr_info("你好,世界 1(报错返回).\n");
/* 非0返回意味着init_module失败; 无法加载模块 */
return 1;
}
void cleanup_module(void)
{
pr_info("再见,世界 1.\n");
}
MODULE_LICENSE("GPL");

24
示例/0-hello/hello-2.c Normal file
View File

@ -0,0 +1,24 @@
/*
* hello-2.c - module_init()module_exit()
* 使init_module()cleanup_module()
*/
#include <linux/init.h> /* 初始化需要的宏 */
#include <linux/module.h> /* 内核模块必要的头 */
#include <linux/printk.h> /* 引入 pr_info() */
static int __init hello_2_init(void)
{
pr_info("你好 2\n");
return 0;
}
static void __exit hello_2_exit(void)
{
pr_info("再见 2\n");
}
module_init(hello_2_init);
module_exit(hello_2_exit);
MODULE_LICENSE("GPL");

25
示例/0-hello/hello-3.c Normal file
View File

@ -0,0 +1,25 @@
/*
* hello-3.c - __init, __initdata __exit 使
*/
#include <linux/init.h> /* 初始化需要的宏 */
#include <linux/module.h> /* 内核模块必要的头 */
#include <linux/printk.h> /* 引入 pr_info() */
static int hello3_data __initdata = 3;
static int __init hello_3_init(void)
{
pr_info("Hello, world %d\n", hello3_data);
return 0;
}
static void __exit hello_3_exit(void)
{
pr_info("Goodbye, world 3\n");
}
module_init(hello_3_init);
module_exit(hello_3_exit);
MODULE_LICENSE("GPL");

25
示例/0-hello/hello-4.c Normal file
View File

@ -0,0 +1,25 @@
/*
* hello-4.c -
*/
#include <linux/init.h> /* 初始化需要的宏 */
#include <linux/module.h> /* 内核模块必要的头 */
#include <linux/printk.h> /* 引入 pr_info() */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("寻觅");
MODULE_DESCRIPTION("一个练习驱动");
static int __init init_hello_4(void)
{
pr_info("Hello, world 4\n");
return 0;
}
static void __exit cleanup_hello_4(void)
{
pr_info("Goodbye, world 4\n");
}
module_init(init_hello_4);
module_exit(cleanup_hello_4);

67
示例/0-hello/hello-5.c Normal file
View File

@ -0,0 +1,67 @@
/*
* hello-5.c -
*/
#include <linux/init.h>
#include <linux/kernel.h> /* 引入 ARRAY_SIZE() */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/printk.h>
#include <linux/stat.h>
MODULE_LICENSE("GPL");
static short int myshort = 1;
static int myint = 420;
static long int mylong = 9999;
static char *mystring = "寻觅";
static int myintarray[2] = { 420, 420 };
static int arr_argc = 0;
/* module_param(foo, int, 0000)
* foo:
* int:
* 0000: , sysfs中的参数()
*/
module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
MODULE_PARM_DESC(myshort, "一个短整数");
module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(myint, "一个整数");
module_param(mylong, long, S_IRUSR);
MODULE_PARM_DESC(mylong, "一个长整数");
module_param(mystring, charp, 0000);
MODULE_PARM_DESC(mystring, "一个字符");
/* module_param_array(name, type, num, perm);
* name:
* type:
* num: ,
* perm:
*/
module_param_array(myintarray, int, &arr_argc, 0000);
MODULE_PARM_DESC(myintarray, "一组整数数组");
static int __init hello_5_init(void)
{
int i;
pr_info("你好 5\n=============\n");
pr_info("myshort是一个短整数: %hd\n", myshort);
pr_info("myint是一个整数: %d\n", myint);
pr_info("mylong是一个长整数: %ld\n", mylong);
pr_info("mystring是一个字符: %s\n", mystring);
for (i = 0; i < ARRAY_SIZE(myintarray); i++)
pr_info("myintarray[%d] = %d\n", i, myintarray[i]);
pr_info("从 myintarray 中获得 %d 个参数\n", arr_argc);
return 0;
}
static void __exit hello_5_exit(void)
{
pr_info("再见 5\n");
}
module_init(hello_5_init);
module_exit(hello_5_exit);

7
示例/0-hello/hello.c Normal file
View File

@ -0,0 +1,7 @@
#include <stdio.h>
int main(void)
{
printf("你好");
return 0;
}

15
示例/0-hello/start.c Normal file
View File

@ -0,0 +1,15 @@
/*
* start.c - -
*/
#include <linux/kernel.h> /* 进行内核工作时引入的头 */
#include <linux/module.h> /* 内核模块必要的头 */
int init_module(void)
{
pr_info("你好, 内核初始化模块启动\n");
return 0;
}
MODULE_LICENSE("GPL");

14
示例/0-hello/stop.c Normal file
View File

@ -0,0 +1,14 @@
/*
* stop.c - -
*/
#include <linux/kernel.h> /* 进行内核工作时引入的头 */
#include <linux/module.h> /* 内核模块必要的头 */
void cleanup_module(void)
{
pr_info("一个很短的内核结束模块\n");
}
MODULE_LICENSE("GPL");

11
示例/1-chardev/Makefile Normal file
View File

@ -0,0 +1,11 @@
obj-m += chardev.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

161
示例/1-chardev/chardev.c Normal file
View File

@ -0,0 +1,161 @@
/*
* chardev.c: char dev
*/
#include <linux/atomic.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h> /* 引入 sprintf() */
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h> /* 引入 get_user 和 put_user */
#include <linux/version.h>
#include <asm/errno.h>
/* 原型(Prototypes) - 这通常会放在一个 .h 文件中 */
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char __user *, size_t,
loff_t *);
#define SUCCESS 0
#define DEVICE_NAME "chardev" /* 在/proc/devices中显示的Dev名称 */
#define BUF_LEN 80 /* 来自驱动消息的最大长度 */
/* 全局变量声明为静态的, 文件中的全局变量也是如此 */
static int major; /* 分配给设备驱动程序的主编号 */
enum {
CDEV_NOT_USED = 0,
CDEV_EXCLUSIVE_OPEN = 1,
};
/* 设备是否打开?用于防止多次访问设备 */
static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
static char msg[BUF_LEN + 1]; /* 当被请求时,设备将给出的信息 */
static struct class *cls;
static struct file_operations chardev_fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release,
};
static int __init chardev_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &chardev_fops);
if (major < 0) {
pr_alert("注册字符设备失败 %d\n", major);
return major;
}
pr_info("分配的主编号 %d.\n", major);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
cls = class_create(DEVICE_NAME);
#else
cls = class_create(THIS_MODULE, DEVICE_NAME);
#endif
device_create(cls, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
pr_info("设备创建于 /dev/%s\n", DEVICE_NAME);
return SUCCESS;
}
static void __exit chardev_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
/* 注销设备 */
unregister_chrdev(major, DEVICE_NAME);
}
/* 方法 */
/* 当进程尝试打开设备文件时调用,如
* "sudo cat /dev/chardev"
*/
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN))
return -EBUSY;
sprintf(msg, "我已经跟你说了 %d 次 Hello world!\n", counter++);
try_module_get(THIS_MODULE);
return SUCCESS;
}
/* 当进程关闭设备文件时调用 */
static int device_release(struct inode *inode, struct file *file)
{
/* 准备好迎接下一个触发 */
atomic_set(&already_open, CDEV_NOT_USED);
/* 减少使用次数,否则一旦打开文件,就永远无法删除模块 */
module_put(THIS_MODULE);
return SUCCESS;
}
/* 当一个已打开 dev 文件的进程试图读取该文件时调用 */
static ssize_t device_read(struct file *filp, /* 请参见 include/linux/fs.h */
char __user *buffer, /* 待填充数据的缓冲区 */
size_t length, /* 缓冲区长度 */
loff_t *offset)
{
/* 实际写入缓冲区的字节数 */
int bytes_read = 0;
const char *msg_ptr = msg;
if (!*(msg_ptr + *offset)) { /* 已到达消息的末尾 */
*offset = 0; /* 重置偏移量 */
return 0; /* 表示文件结束 */
}
msg_ptr += *offset;
/* 实际将数据放入缓冲区 */
while (length && *msg_ptr) {
/* 缓冲区在用户数据段,而不是内核段所以直接赋值 "*" 是无效的
* 使 put_user
*/
put_user(*(msg_ptr++), buffer++);
length--;
bytes_read++;
}
*offset += bytes_read;
/* 大多数读取函数返回放入缓冲区的字节数 */
return bytes_read;
}
/* 当进程写入dev文件时调用: echo "hi" > /dev/hello */
static ssize_t device_write(struct file *filp, const char __user *buff,
size_t len, loff_t *off)
{
pr_alert("对不起,不支持此操作.\n");
return -EINVAL;
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,15 @@
obj-m += procfs1.o
obj-m += procfs2.o
obj-m += procfs3.o
obj-m += procfs4.o
obj-m += hello-sysfs.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
gcc procfs.c -o 占位
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm 占位

View File

@ -0,0 +1,61 @@
/*
* hello-sysfs.c sysfs
*/
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/sysfs.h>
static struct kobject *mymodule;
/* 你希望能够更改的变量 */
static int myvariable = 0;
static ssize_t myvariable_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", myvariable);
}
static ssize_t myvariable_store(struct kobject *kobj,
struct kobj_attribute *attr, char *buf,
size_t count)
{
sscanf(buf, "%du", &myvariable);
return count;
}
static struct kobj_attribute myvariable_attribute =
__ATTR(myvariable, 0660, myvariable_show, (void *)myvariable_store);
static int __init mymodule_init(void)
{
int error = 0;
pr_info("我的模块: 初始化完成\n");
mymodule = kobject_create_and_add("我的模块", kernel_kobj);
if (!mymodule)
return -ENOMEM;
error = sysfs_create_file(mymodule, &myvariable_attribute.attr);
if (error) {
pr_info("在 /sys/kernel/我的模块 创建 myvariable 文件失败\n");
}
return error;
}
static void __exit mymodule_exit(void)
{
pr_info("我的模块: 退出成功\n");
kobject_put(mymodule);
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,37 @@
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
char procName[256];
strcpy(procName, "/proc/buffer2k");
int fd = open(procName, O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
char buffer[256];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
if (bytesRead < 0) {
perror("read");
close(fd);
return 1;
}
int seconds = 30;
buffer[bytesRead] = '\0';
printf("%s 内容: %s \n", procName, buffer, seconds);
while (seconds > 0) {
printf("\r文件将在: %d 秒后关闭", seconds);
fflush(stdout);
sleep(1);
seconds--;
}
close(fd);
printf("\r文件已关闭");
return 0;
}

View File

@ -0,0 +1,69 @@
/*
* procfs1.c
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#define HAVE_PROC_OPS
#endif
#define procfs_name "helloworld"
static struct proc_dir_entry *our_proc_file;
static ssize_t procfile_read(struct file *file_pointer, char __user *buffer,
size_t buffer_length, loff_t *offset)
{
char s[13] = "HelloWorld!\n";
int len = sizeof(s);
ssize_t ret = len;
/* 如果偏移量大于或等于字符串长度,或者 copy_to_user 失败,则读取操作失败,返回 0。 */
if (*offset >= len || copy_to_user(buffer, s, len)) {
pr_info("copy_to_user 执行结束 \n");
ret = 0;
} else {
pr_info("读取项目文件: %s\n", file_pointer->f_path.dentry->d_name.name);
*offset += len;
}
return ret;
}
#ifdef HAVE_PROC_OPS
static const struct proc_ops proc_file_fops = {
.proc_read = procfile_read,
};
#else
static const struct file_operations proc_file_fops = {
.read = procfile_read,
};
#endif
static int __init procfs1_init(void)
{
our_proc_file = proc_create(procfs_name, 0644, NULL, &proc_file_fops);
if (NULL == our_proc_file) {
proc_remove(our_proc_file);
pr_alert("错误: 无法初始化 /proc/%s\n", procfs_name);
return -ENOMEM;
}
pr_info("/proc/%s 已创建\n", procfs_name);
return 0;
}
static void __exit procfs1_exit(void)
{
proc_remove(our_proc_file);
pr_info("/proc/%s 已卸载\n", procfs_name);
}
module_init(procfs1_init);
module_exit(procfs1_exit);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,98 @@
/*
* procfs2.c - /proc中创建一个 "文件"
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h> /* 使用 procfs 时的必要模块 */
#include <linux/uaccess.h> /* 引入 copy_from_user */
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#define HAVE_PROC_OPS
#endif
#define PROCFS_MAX_SIZE 1024
#define PROCFS_NAME "buffer1k"
/* 此结构保存有关 /proc 文件的信息 */
static struct proc_dir_entry *our_proc_file;
/* 用于存储此模块字符的缓冲区 */
static char procfs_buffer[PROCFS_MAX_SIZE];
/* 缓冲区的大小 */
static unsigned long procfs_buffer_size = 0;
/* 在读取 /proc 文件时调用该函数 */
static ssize_t procfile_read(struct file *file_pointer, char __user *buffer,
size_t buffer_length, loff_t *offset)
{
char s[13] = "HelloWorld!\n";
int len = sizeof(s);
ssize_t ret = len;
if (*offset >= len || copy_to_user(buffer, s, len)) {
pr_info("copy_to_user 执行结束 \n");
ret = 0;
} else {
pr_info("读取项目文件: %s\n", file_pointer->f_path.dentry->d_name.name);
*offset += len;
}
return ret;
}
/* 在写入 /proc 文件时调用该函数 */
static ssize_t procfile_write(struct file *file, const char __user *buff,
size_t len, loff_t *off)
{
procfs_buffer_size = len;
if (procfs_buffer_size > PROCFS_MAX_SIZE)
procfs_buffer_size = PROCFS_MAX_SIZE;
if (copy_from_user(procfs_buffer, buff, procfs_buffer_size))
return -EFAULT;
procfs_buffer[procfs_buffer_size & (PROCFS_MAX_SIZE - 1)] = '\0';
*off += procfs_buffer_size;
pr_info("写入项目文件 %s\n", procfs_buffer);
return procfs_buffer_size;
}
#ifdef HAVE_PROC_OPS
static const struct proc_ops proc_file_fops = {
.proc_read = procfile_read,
.proc_write = procfile_write,
};
#else
static const struct file_operations proc_file_fops = {
.read = procfile_read,
.write = procfile_write,
};
#endif
static int __init procfs2_init(void)
{
our_proc_file = proc_create(PROCFS_NAME, 0644, NULL, &proc_file_fops);
if (NULL == our_proc_file) {
pr_alert("错误: 无法初始化 /proc/%s\n", PROCFS_NAME);
return -ENOMEM;
}
pr_info("/proc/%s 已创建\n", PROCFS_NAME);
return 0;
}
static void __exit procfs2_exit(void)
{
proc_remove(our_proc_file);
pr_info("/proc/%s 已卸载\n", PROCFS_NAME);
}
module_init(procfs2_init);
module_exit(procfs2_exit);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,112 @@
/*
* procfs3.c
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
#include <linux/minmax.h>
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#define HAVE_PROC_OPS
#endif
#define PROCFS_MAX_SIZE 2048UL
#define PROCFS_ENTRY_FILENAME "buffer2k"
static struct proc_dir_entry *our_proc_file;
static char procfs_buffer[PROCFS_MAX_SIZE];
static unsigned long procfs_buffer_size = 0;
static ssize_t procfs_read(struct file *filp, char __user *buffer,
size_t length, loff_t *offset)
{
if (*offset || procfs_buffer_size == 0) {
pr_notice("procfs_read: 结束\n");
*offset = 0;
return 0;
}
procfs_buffer_size = min(procfs_buffer_size, length);
if (copy_to_user(buffer, procfs_buffer, procfs_buffer_size))
return -EFAULT;
*offset += procfs_buffer_size;
pr_notice("procfs_read: 读取 %lu 字节\n", procfs_buffer_size);
return procfs_buffer_size;
}
static ssize_t procfs_write(struct file *file, const char __user *buffer,
size_t len, loff_t *off)
{
procfs_buffer_size = min(PROCFS_MAX_SIZE, len);
if (copy_from_user(procfs_buffer, buffer, procfs_buffer_size))
return -EFAULT;
*off += procfs_buffer_size;
pr_notice("procfs_write: 写入 %lu 字节\n", procfs_buffer_size);
return procfs_buffer_size;
}
static int procfs_open(struct inode *inode, struct file *file)
{
try_module_get(THIS_MODULE);
pr_notice("procfs_open: 文件被打开, 目前文件正在被打开 -> %d \n", module_refcount(THIS_MODULE));
return 0;
}
static int procfs_close(struct inode *inode, struct file *file)
{
module_put(THIS_MODULE);
if (module_refcount(THIS_MODULE) == 0){
pr_notice("procfs_close: 文件已全部关闭, 可以安全卸载\n");
} else {
pr_notice("procfs_close: 文件剩余开启数为 -> %d \n", module_refcount(THIS_MODULE));
}
return 0;
}
#ifdef HAVE_PROC_OPS
static struct proc_ops file_ops_4_our_proc_file = {
.proc_read = procfs_read,
.proc_write = procfs_write,
.proc_open = procfs_open,
.proc_release = procfs_close,
};
#else
static const struct file_operations file_ops_4_our_proc_file = {
.read = procfs_read,
.write = procfs_write,
.open = procfs_open,
.release = procfs_close,
};
#endif
static int __init procfs3_init(void)
{
our_proc_file = proc_create(PROCFS_ENTRY_FILENAME, 0644, NULL,
&file_ops_4_our_proc_file);
if (our_proc_file == NULL) {
pr_notice("错误: 无法初始化 /proc/%s\n",
PROCFS_ENTRY_FILENAME);
return -ENOMEM;
}
proc_set_size(our_proc_file, 80);
proc_set_user(our_proc_file, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID);
pr_notice("/proc/%s 已创建\n", PROCFS_ENTRY_FILENAME);
return 0;
}
static void __exit procfs3_exit(void)
{
remove_proc_entry(PROCFS_ENTRY_FILENAME, NULL);
pr_notice("/proc/%s 已卸载\n", PROCFS_ENTRY_FILENAME);
}
module_init(procfs3_init);
module_exit(procfs3_exit);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,114 @@
/*
* procfs4.c - 使seq_file库来管理的/proc文件
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h> /* 引入 seq_file */
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#define HAVE_PROC_OPS
#endif
#define PROC_NAME "iter"
/* 这个函数在序列开始时被调用, 即:
* - /proc
* - stop
*/
static void *my_seq_start(struct seq_file *s, loff_t *pos)
{
static unsigned long counter = 0;
/* 是否开始一个新的序列? */
if (*pos == 0) {
/* yes => 返回非 NULL 值以开始序列 */
return &counter;
}
/* no => 这是序列的结束,返回 NULL 以停止读取 */
*pos = 0;
return NULL;
}
/* 这个函数在序列开始后被调用。它会被调用直到返回 NULL(序列结束) */
static void *my_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
unsigned long *tmp_v = (unsigned long *)v;
(*tmp_v)++;
(*pos)++;
return NULL;
}
/* 这个函数在序列结束时被调用 */
static void my_seq_stop(struct seq_file *s, void *v)
{
/* 无需操作,因为我们在 start() 中使用了静态值 */
}
/* 这个函数在序列的每一步中被调用 */
static int my_seq_show(struct seq_file *s, void *v)
{
loff_t *spos = (loff_t *)v;
seq_printf(s, "%Ld\n", *spos);
return 0;
}
/* 这个结构体包含了管理序列的函数 */
static struct seq_operations my_seq_ops = {
.start = my_seq_start,
.next = my_seq_next,
.stop = my_seq_stop,
.show = my_seq_show,
};
/* 这个函数在打开 /proc 文件时被调用 */
static int my_open(struct inode *inode, struct file *file)
{
return seq_open(file, &my_seq_ops);
};
/* 这个结构体包含了管理 /proc 文件的函数 */
#ifdef HAVE_PROC_OPS
static const struct proc_ops my_file_ops = {
.proc_open = my_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = seq_release,
};
#else
static const struct file_operations my_file_ops = {
.open = my_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
#endif
static int __init procfs4_init(void)
{
struct proc_dir_entry *entry;
entry = proc_create(PROC_NAME, 0, NULL, &my_file_ops);
if (entry == NULL) {
pr_debug("错误:无法初始化 /proc/%s\n", PROC_NAME);
return -ENOMEM;
}
return 0;
}
static void __exit procfs4_exit(void)
{
remove_proc_entry(PROC_NAME, NULL);
pr_debug("/proc/%s 已移除\n", PROC_NAME);
}
module_init(procfs4_init);
module_exit(procfs4_exit);
MODULE_LICENSE("GPL");

14
示例/3-ioctl/Makefile Normal file
View File

@ -0,0 +1,14 @@
obj-m += ioctl.o
obj-m += chardev2.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
gcc userspace_ioctl.c -o 读写_用户空间
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm 读写_用户空间

37
示例/3-ioctl/chardev.h Normal file
View File

@ -0,0 +1,37 @@
/*
* chardev.h - ioctl
* chardev2.c ioctl() userspace_ioctl.c
*/
#ifndef CHARDEV_H
#define CHARDEV_H
#include <linux/ioctl.h>
/* 主设备号。由于 ioctl 需要知道主设备号,我们不能再依赖动态注册。 */
#define MAJOR_NUM 100
/* 设置设备驱动的消息 */
#define IOCTL_SET_MSG _IOW(MAJOR_NUM, 0, char *)
/* _IOW 表示我们正在创建一个 ioctl 命令,用于将信息从用户进程传递到内核模块。
* MAJOR_NUM 使
*
*
*/
/* 获取设备驱动的消息 */
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)
/* 此 IOCTL 用于输出,获取设备驱动的消息。
*
*/
/* 获取消息的第 n 个字节 */
#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)
/* 此 IOCTL 用于输入和输出。它从用户获取一个数字 n并返回消息的第 n 个字节。
*/
/* 设备文件的名称 */
#define DEVICE_FILE_NAME "char_dev"
#define DEVICE_PATH "/dev/char_dev"
#endif

207
示例/3-ioctl/chardev2.c Normal file
View File

@ -0,0 +1,207 @@
/*
* chardev2.c - /
*/
#include <linux/atomic.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h> /* 引入 get_user 和 put_user */
#include <linux/version.h>
#include <asm/errno.h>
#include "chardev.h"
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80
enum {
CDEV_NOT_USED = 0,
CDEV_EXCLUSIVE_OPEN = 1,
};
/* 设备当前是否已打开?用于防止对同一设备的并发访问 */
static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
/* 设备在被请求时返回的消息 */
static char message[BUF_LEN + 1];
static struct class *cls;
/* 当进程尝试打开设备文件时调用此函数 */
static int device_open(struct inode *inode, struct file *file)
{
pr_info("[device_open]打开: (%p)\n", file);
try_module_get(THIS_MODULE);
return SUCCESS;
}
static int device_release(struct inode *inode, struct file *file)
{
pr_info("[device_release]释放: (%p,%p)\n", inode, file);
module_put(THIS_MODULE);
return SUCCESS;
}
/* 打开设备文件的进程尝试从中读取数据时调用此函数 */
static ssize_t device_read(struct file *file, /* 参见include/linux/fs.h */
char __user *buffer, /* 要填充的用户缓冲区 */
size_t length, /* 缓冲区的长度 */
loff_t *offset)
{
/* 实际写入到缓冲区的字节数 */
int bytes_read = 0;
/* 读取消息的进度指针,如果消息比缓冲区大,这很有用 */
const char *message_ptr = message;
if (!*(message_ptr + *offset)) { /* 到达消息末尾 */
*offset = 0; /* 重置偏移量 */
return 0; /* 表示文件结束 */
}
message_ptr += *offset;
/* 实际将数据写入缓冲区 */
while (length && *message_ptr) {
/* 由于缓冲区在用户数据段而非内核数据段,需要使用 put_user 将数据从内核复制到用户空间 */
put_user(*(message_ptr++), buffer++);
length--;
bytes_read++;
}
pr_info("读取 %d 字节, %ld 字节剩余\n", bytes_read, length);
*offset += bytes_read;
/* 读取函数应返回实际写入缓冲区的字节数 */
return bytes_read;
}
/* 当有人尝试向我们的设备文件写入数据时调用此函数 */
static ssize_t device_write(struct file *file, const char __user *buffer,
size_t length, loff_t *offset)
{
int i;
pr_info("[device_write]写入: %p,%p,%ld", file, buffer, length);
/* 将数据从用户空间缓冲区读取到内核空间的 message 中 */
for (i = 0; i < length && i < BUF_LEN; i++)
get_user(message[i], buffer + i);
/* 返回实际写入 message 中的字符数 */
return i;
}
/* 当进程尝试对设备文件执行 ioctl 操作时调用此函数。除了 inode 和 file 结构外我们还接收到两个额外的参数ioctl 的编号和传递给 ioctl 的参数。
* ioctl /ioctl
*/
static long
device_ioctl(struct file *file,
unsigned int ioctl_num, /* ioctl 操作编号 */
unsigned long ioctl_param) /* ioctl 参数 */
{
int i;
long ret = SUCCESS;
/* 不允许同时与两个进程交互 */
if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN))
return -EBUSY;
/* 根据 ioctl 操作编号进行切换 */
switch (ioctl_num) {
case IOCTL_SET_MSG: {
/* 接收一个用户空间的消息指针,并将其设置为设备的消息 */
char __user *tmp = (char __user *)ioctl_param;
char ch;
/* 查找消息的长度 */
get_user(ch, tmp);
for (i = 0; ch && i < BUF_LEN; i++, tmp++)
get_user(ch, tmp);
device_write(file, (char __user *)ioctl_param, i, NULL);
break;
}
case IOCTL_GET_MSG: {
loff_t offset = 0;
/* 将当前消息返回给调用进程,参数是一个指针,将数据填充到该指针指向的缓冲区中 */
i = device_read(file, (char __user *)ioctl_param, 99, &offset);
/* 在缓冲区末尾添加一个终止符 */
put_user('\0', (char __user *)ioctl_param + i);
break;
}
case IOCTL_GET_NTH_BYTE:
/* 这个 ioctl 同时用于输入ioctl_param和输出函数的返回值 */
ret = (long)message[ioctl_param];
break;
}
/* 准备好接待下一个调用者 */
atomic_set(&already_open, CDEV_NOT_USED);
return ret;
}
/* 模块声明 */
/* 这个结构体将保存当进程对我们创建的设备进行操作时调用的函数。
* init_module
* NULL
*/
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
.open = device_open,
.release = device_release, /* 类似 关闭`close` 操作 */
};
/* 初始化模块-注册字符设备 */
static int __init chardev2_init(void)
{
/* 注册字符设备(尝试一下) */
int ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);
/* 出现负值代表有个错误 */
if (ret_val < 0) {
pr_alert("注册字符设备 %d 失败\n", ret_val);
return ret_val;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
cls = class_create(DEVICE_FILE_NAME);
#else
cls = class_create(THIS_MODULE, DEVICE_FILE_NAME);
#endif
device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_FILE_NAME);
pr_info("设备创建于 /dev/%s\n", DEVICE_FILE_NAME);
return 0;
}
/* 清理模块 - 从/proc注销移除相关文件 */
static void __exit chardev2_exit(void)
{
device_destroy(cls, MKDEV(MAJOR_NUM, 0));
class_destroy(cls);
/* 注销设备 */
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}
module_init(chardev2_init);
module_exit(chardev2_exit);
MODULE_LICENSE("GPL");

212
示例/3-ioctl/ioctl.c Normal file
View File

@ -0,0 +1,212 @@
/*
* ioctl.c
*/
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/version.h>
// 定义用于 ioctl 操作的数据结构
struct ioctl_arg {
unsigned int val;
};
// 定义 ioctl 操作的宏
/* Documentation/userspace-api/ioctl/ioctl-number.rst */
#define IOC_MAGIC '\x66' // 自定义的魔术数字,用于标识 ioctl 操作的类别
// 定义不同的 ioctl 操作
#define IOCTL_VALSET _IOW(IOC_MAGIC, 0, struct ioctl_arg) // 写操作,设置一个值
#define IOCTL_VALGET _IOR(IOC_MAGIC, 1, struct ioctl_arg) // 读操作,获取一个值
#define IOCTL_VALGET_NUM _IOR(IOC_MAGIC, 2, int) // 读操作,获取一个整数
#define IOCTL_VALSET_NUM _IOW(IOC_MAGIC, 3, int) // 写操作,设置一个整数
#define IOCTL_VAL_MAXNR 3 // 最大的 ioctl 命令号
#define DRIVER_NAME "ioctltest" // 驱动名称
// 定义一些全局变量
static unsigned int test_ioctl_major = 0; // 主设备号
static unsigned int num_of_dev = 1; // 设备数量
static struct cdev test_ioctl_cdev; // 字符设备结构体
static int ioctl_num = 0; // 用于存储整数值的全局变量
// 定义用于保存设备数据的结构体
struct test_ioctl_data {
unsigned char val; // 存储一个值
rwlock_t lock; // 读写锁,用于保护数据的并发访问
};
// 处理 ioctl 操作的函数
static long test_ioctl_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct test_ioctl_data *ioctl_data = filp->private_data; // 从文件私有数据中获取设备数据
int retval = 0; // 操作结果
unsigned char val;
struct ioctl_arg data;
memset(&data, 0, sizeof(data)); // 清空 data 结构体
switch (cmd) {
case IOCTL_VALSET:
// 处理 IOCTL_VALSET 命令,将用户空间的数据复制到内核空间
if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
retval = -EFAULT;
goto done;
}
pr_alert("IOCTL 设置值:%x .\n", data.val); // 打印设置的值
write_lock(&ioctl_data->lock); // 获取写锁
ioctl_data->val = data.val; // 更新设备数据
write_unlock(&ioctl_data->lock); // 释放写锁
break;
case IOCTL_VALGET:
// 处理 IOCTL_VALGET 命令,将内核空间的数据复制到用户空间
read_lock(&ioctl_data->lock); // 获取读锁
val = ioctl_data->val; // 读取设备数据
read_unlock(&ioctl_data->lock); // 释放读锁
data.val = val;
if (copy_to_user((int __user *)arg, &data, sizeof(data))) {
retval = -EFAULT; // 复制失败
goto done;
}
break;
case IOCTL_VALGET_NUM:
// 处理 IOCTL_VALGET_NUM 命令,将整数值返回给用户空间
retval = __put_user(ioctl_num, (int __user *)arg);
break;
case IOCTL_VALSET_NUM:
// 处理 IOCTL_VALSET_NUM 命令,从用户空间获取整数值
ioctl_num = arg;
break;
default:
retval = -ENOTTY; // 命令不被支持
}
done:
return retval; // 返回操作结果
}
// 处理文件读取操作的函数
static ssize_t test_ioctl_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
struct test_ioctl_data *ioctl_data = filp->private_data; // 从文件私有数据中获取设备数据
unsigned char val;
int retval;
int i = 0;
read_lock(&ioctl_data->lock); // 获取读锁
val = ioctl_data->val; // 读取设备数据
read_unlock(&ioctl_data->lock); // 释放读锁
// 将数据从内核空间复制到用户空间
for (; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT; // 复制失败
goto out;
}
}
retval = count; // 成功读取的字节数
out:
return retval; // 返回读取结果
}
// 处理文件关闭操作的函数
static int test_ioctl_close(struct inode *inode, struct file *filp)
{
pr_alert("%s 呼叫\n", __func__); // 打印函数名
if (filp->private_data) {
kfree(filp->private_data); // 释放内存
filp->private_data = NULL;
}
return 0; // 成功关闭文件
}
// 处理文件打开操作的函数
static int test_ioctl_open(struct inode *inode, struct file *filp)
{
struct test_ioctl_data *ioctl_data;
pr_alert("%s 呼叫\n", __func__); // 打印函数名
ioctl_data = kmalloc(sizeof(struct test_ioctl_data), GFP_KERNEL); // 分配内存
if (ioctl_data == NULL)
return -ENOMEM; // 内存分配失败
rwlock_init(&ioctl_data->lock); // 初始化读写锁
ioctl_data->val = 0xFF; // 初始化设备数据
filp->private_data = ioctl_data; // 将设备数据指针保存到文件私有数据中
return 0;
}
// 文件操作结构体,定义了设备文件的操作函数
static struct file_operations fops = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
.owner = THIS_MODULE, // 设置模块所有者
#endif
.open = test_ioctl_open, // 打开文件操作函数
.release = test_ioctl_close, // 关闭文件操作函数
.read = test_ioctl_read, // 读取文件操作函数
.unlocked_ioctl = test_ioctl_ioctl, // ioctl 操作函数
};
// 模块初始化函数
static int __init ioctl_init(void)
{
dev_t dev;
int alloc_ret = -1;
int cdev_ret = -1;
// 分配字符设备号
alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error; // 分配失败,跳转到错误处理
test_ioctl_major = MAJOR(dev); // 获取主设备号
cdev_init(&test_ioctl_cdev, &fops); // 初始化字符设备结构体
cdev_ret = cdev_add(&test_ioctl_cdev, dev, num_of_dev); // 添加字符设备
if (cdev_ret)
goto error; // 添加失败,跳转到错误处理
pr_alert("%s 驱动(主编号: %d) 已安装\n", DRIVER_NAME,
test_ioctl_major); // 打印安装成功信息
return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_ioctl_cdev); // 释放字符设备
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev); // 释放设备号
return -1;
}
// 模块卸载函数
static void __exit ioctl_exit(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0); // 创建 dev_t 变量
cdev_del(&test_ioctl_cdev); // 删除字符设备
unregister_chrdev_region(dev, num_of_dev); // 释放字符设备号
pr_alert("%s 模块卸载 \n", DRIVER_NAME); // 打印卸载信息
}
module_init(ioctl_init);
module_exit(ioctl_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("这是 ioctl 测试模块");

View File

@ -0,0 +1,103 @@
/* userspace_ioctl.c - 用于通过 ioctl 控制内核模块的进程
*
* 使 cat 使 ioctl
*/
/* 包含设备特定的信息,如 ioctl 编号和主设备号。 */
#include "./chardev.h"
#include <stdio.h> /* 标准输入/输出 */
#include <fcntl.h> /* 打开文件 */
#include <unistd.h> /* 关闭文件 */
#include <stdlib.h> /* 退出程序 */
#include <sys/ioctl.h> /* ioctl 调用 */
/* ioctl 调用的函数 */
int ioctl_set_msg(int file_desc, char *message)
{
int ret_val;
ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);
if (ret_val < 0) {
printf("ioctl_set_msg 失败: %d\n", ret_val);
}
return ret_val;
}
int ioctl_get_msg(int file_desc)
{
int ret_val;
char message[100] = { 0 };
/* 警告 - 这里存在风险,因为我们没有告知内核写入的缓冲区长度,可能会导致缓冲区溢出。在实际生产程序中,
* 使 ioctl -
*/
ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);
if (ret_val < 0) {
printf("ioctl_get_msg 失败: %d\n", ret_val);
}
printf("获取的消息: %s", message);
return ret_val;
}
/* 注意:这种按字节读取消息的方式无法正常读取中文 */
int ioctl_get_nth_byte(int file_desc)
{
int i, c, err;
printf("按字节获取到的消息: ");
err = 0;
i = 0;
do {
c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);
if (c < 0) {
err = 1;
continue;
}
putchar(c);
} while (c != 0);
if (err == 1){
printf("(非ascii字符已被省略)\n\n");
}
return 0;
}
/* 主函数 - 调用 ioctl 函数 */
int main(void)
{
int file_desc, ret_val;
char *msg = "通过 ioctl 传递的消息\n";
/* char *msg = "qwer1234\n"; */
file_desc = open(DEVICE_PATH, O_RDWR);
if (file_desc < 0) {
printf("无法打开设备文件: %s, 错误: %d\n", DEVICE_PATH, file_desc);
exit(EXIT_FAILURE);
}
ret_val = ioctl_set_msg(file_desc, msg);
if (ret_val)
goto error;
ret_val = ioctl_get_nth_byte(file_desc);
if (ret_val)
goto error;
ret_val = ioctl_get_msg(file_desc);
if (ret_val)
goto error;
close(file_desc);
return 0;
error:
close(file_desc);
exit(EXIT_FAILURE);
}

View File

@ -0,0 +1,11 @@
obj-m += syscall-steal_loong64.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

View File

@ -0,0 +1,275 @@
/*
* syscall-steal.c
* "监视" ()
* fcsr 16
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h> /* 包含一些参数 */
#include <linux/unistd.h> /* 系统调用列表 */
#include <linux/cred.h> /* 引入 current_uid() */
#include <linux/uidgid.h> /* 引入 __kuid_val() */
#include <linux/version.h>
/* 根据本程序的需求我们需要了解当前用户是谁 */
#include <linux/sched.h>
#include <linux/uaccess.h>
/* 访问“sys_call_table”的方式随着内核内部的变化而变化。
* <= v5.4 :
* == v5.5 v5.6 : 使 kallsyms_lookup_name()
* >= v5.7 : Kprobes
*/
/* 在 Linux v5.11 及以上版本中,内核调用 ksys_close() 的方式被移除 */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0))
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 4, 0)
#define HAVE_KSYS_CLOSE 1
#include <linux/syscalls.h> /* 引入 ksys_close() */
#else
#include <linux/kallsyms.h> /* 引入 kallsyms_lookup_name */
#endif
#else
#if defined(CONFIG_KPROBES)
#define HAVE_KPROBES 1
#if defined(CONFIG_X86_64)
/* 如果你尝试使用系统调用表来拦截系统调用但未成功,你可以尝试使用 Kprobes 来拦截系统调用。
* USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL 1
*/
#define USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL 0
#endif
#include <linux/kprobes.h>
#else
#define HAVE_PARAM 1
#include <linux/kallsyms.h> /* 引入 sprint_symbol */
/* sys_call_table 的地址可以通过查看 "/boot/System.map" 或 "/proc/kallsyms" 获得。
* v5.7 CONFIG_KPROBES
*/
static unsigned long sym = 0;
module_param(sym, ulong, 0644);
#endif /* CONFIG_KPROBES */
#endif /* 版本 < v5.7 */
/* 我们想要监视的 UID - 将从命令行填充。 */
static uid_t uid = -1;
module_param(uid, int, 0644);
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
/* syscall_sym 是要监视的系统调用的符号名称。默认值为"__x64_sys_openat"
* /proc/kallsyms
*/
static char *syscall_sym = "__x64_sys_openat";
module_param(syscall_sym, charp, 0644);
static int sys_call_kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
if (__kuid_val(current_uid()) != uid) {
return 0;
}
pr_info("%s 被 %d 调用\n", syscall_sym, uid);
return 0;
}
static struct kprobe syscall_kprobe = {
.symbol_name = "__x64_sys_openat",
.pre_handler = sys_call_kprobe_pre_handler,
};
#else
static unsigned long **sys_call_table_stolen;
/* 指向原始系统调用的指针。我们保留这个指针,而不是直接调用原始函数 (sys_openat),是因为可能有其他模块在我们之前已经替换了系统调用。
* 100% sys_openat -
*
*
* sys_openat
*/
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long (*original_call)(const struct pt_regs *);
#else
static asmlinkage long (*original_call)(int, const char __user *, int, umode_t);
#endif
/* 我们将用来替换 sys_openat (即调用 open 系统调用时调用的函数)的函数
* ( fs/open.c )
*
*
* ()
*/
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long our_sys_openat(const struct pt_regs *regs)
#else
static asmlinkage long our_sys_openat(int dfd, const char __user *filename,
int flags, umode_t mode)
#endif
{
int i = 0;
char ch;
if (__kuid_val(current_uid()) != uid)
goto orig_call;
/* 报告相关文件 */
pr_info("Opened file by %d: ", uid);
do {
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
get_user(ch, (char __user *)regs->si + i);
#else
get_user(ch, (char __user *)filename + i);
#endif
i++;
pr_info("%c", ch);
} while (ch != 0);
pr_info("\n");
orig_call:
/* 调用原始的sys_openat-否则,我们将失去打开文件的能力 */
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
return original_call(regs);
#else
return original_call(dfd, filename, flags, mode);
#endif
}
static unsigned long **acquire_sys_call_table(void)
{
#ifdef HAVE_KSYS_CLOSE
unsigned long int offset = PAGE_OFFSET;
unsigned long **sct;
while (offset < ULLONG_MAX) {
sct = (unsigned long **)offset;
if (sct[__NR_close] == (unsigned long *)ksys_close)
return sct;
offset += sizeof(void *);
}
return NULL;
#endif
#ifdef HAVE_PARAM
const char sct_name[15] = "sys_call_table";
char symbol[40] = { 0 };
if (sym == 0) {
pr_alert("对于 Linux v5.7 以上版本Kprobes 是获取符号的首选方法\n");
pr_info("如果没有 Kprobes则必须指定 sys_call_table 符号的地址\n");
pr_info("通过 /boot/System.map 或 /proc/kallsyms 将包含所有符号地址的符号转换为sym参数\n");
return NULL;
}
sprint_symbol(symbol, sym);
if (!strncmp(sct_name, symbol, sizeof(sct_name) - 1))
return (unsigned long **)sym;
return NULL;
#endif
#ifdef HAVE_KPROBES
unsigned long (*kallsyms_lookup_name)(const char *name);
struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name",
};
if (register_kprobe(&kp) < 0)
return NULL;
kallsyms_lookup_name = (unsigned long (*)(const char *name))kp.addr;
unregister_kprobe(&kp);
#endif
return (unsigned long **)kallsyms_lookup_name("sys_call_table");
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
static inline void __write_fcsr(unsigned long fcsr)
{
asm volatile("mov %0,%%fcsr" : "+r"(fcsr) : : "memory");
}
#else
#define __write_fcsr write_fcsr
#endif
static void enable_write_protection(void)
{
unsigned long fcsr = read_fcsr();
set_bit(16, &fcsr);
__write_fcsr(fcsr);
}
static void disable_write_protection(void)
{
unsigned long fcsr = read_fcsr();
clear_bit(16, &fcsr);
__write_fcsr(fcsr);
}
#endif
static int __init syscall_steal_start(void)
{
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
int err;
/* use symbol name from the module parameter */
syscall_kprobe.symbol_name = syscall_sym;
err = register_kprobe(&syscall_kprobe);
if (err) {
pr_err("register_kprobe() 在 %s 失败: %d\n", syscall_sym, err);
pr_err("请检查 “syscall_sym ”参数中的符号名称\n");
return err;
}
#else
if (!(sys_call_table_stolen = acquire_sys_call_table()))
return -1;
disable_write_protection();
/* 跟踪原始的打开函数 */
original_call = (void *)sys_call_table_stolen[__NR_openat];
/* 请改用我们的openat函数 */
sys_call_table_stolen[__NR_openat] = (unsigned long *)our_sys_openat;
enable_write_protection();
#endif
pr_info("监视 UID:%d\n", uid);
return 0;
}
static void __exit syscall_steal_end(void)
{
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
unregister_kprobe(&syscall_kprobe);
#else
if (!sys_call_table_stolen)
return;
/* 将系统调用恢复正常 */
if (sys_call_table_stolen[__NR_openat] != (unsigned long *)our_sys_openat) {
pr_alert("发现系统调用被其他人修改");
pr_alert("系统可能处于不稳定状态");
}
disable_write_protection();
sys_call_table_stolen[__NR_openat] = (unsigned long *)original_call;
enable_write_protection();
#endif
msleep(2000);
}
module_init(syscall_steal_start);
module_exit(syscall_steal_end);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,275 @@
/*
* syscall-steal.c
* "监视"
* cr0 16 ( Intel )
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h> /* 包含一些参数 */
#include <linux/unistd.h> /* 系统调用列表 */
#include <linux/cred.h> /* 引入 current_uid() */
#include <linux/uidgid.h> /* 引入 __kuid_val() */
#include <linux/version.h>
/* 根据本程序的需求我们需要了解当前用户是谁 */
#include <linux/sched.h>
#include <linux/uaccess.h>
/* 访问“sys_call_table”的方式随着内核内部的变化而变化。
* <= v5.4 :
* == v5.5 v5.6 : 使 kallsyms_lookup_name()
* >= v5.7 : Kprobes
*/
/* 在 Linux v5.11 及以上版本中,内核调用 ksys_close() 的方式被移除 */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0))
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5, 4, 0)
#define HAVE_KSYS_CLOSE 1
#include <linux/syscalls.h> /* 引入 ksys_close() */
#else
#include <linux/kallsyms.h> /* 引入 kallsyms_lookup_name */
#endif
#else
#if defined(CONFIG_KPROBES)
#define HAVE_KPROBES 1
#if defined(CONFIG_X86_64)
/* 如果你尝试使用系统调用表来拦截系统调用但未成功,你可以尝试使用 Kprobes 来拦截系统调用。
* USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL 1
*/
#define USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL 0
#endif
#include <linux/kprobes.h>
#else
#define HAVE_PARAM 1
#include <linux/kallsyms.h> /* 引入 sprint_symbol */
/* sys_call_table 的地址可以通过查看 "/boot/System.map" 或 "/proc/kallsyms" 获得。
* v5.7 CONFIG_KPROBES
*/
static unsigned long sym = 0;
module_param(sym, ulong, 0644);
#endif /* CONFIG_KPROBES */
#endif /* 版本 < v5.7 */
/* 我们想要监视的 UID - 将从命令行填充。 */
static uid_t uid = -1;
module_param(uid, int, 0644);
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
/* syscall_sym 是要监视的系统调用的符号名称。默认值为"__x64_sys_openat"
* /proc/kallsyms
*/
static char *syscall_sym = "__x64_sys_openat";
module_param(syscall_sym, charp, 0644);
static int sys_call_kprobe_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
if (__kuid_val(current_uid()) != uid) {
return 0;
}
pr_info("%s 被 %d 调用\n", syscall_sym, uid);
return 0;
}
static struct kprobe syscall_kprobe = {
.symbol_name = "__x64_sys_openat",
.pre_handler = sys_call_kprobe_pre_handler,
};
#else
static unsigned long **sys_call_table_stolen;
/* 指向原始系统调用的指针。我们保留这个指针,而不是直接调用原始函数 (sys_openat),是因为可能有其他模块在我们之前已经替换了系统调用。
* 100% sys_openat -
*
*
* sys_openat
*/
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long (*original_call)(const struct pt_regs *);
#else
static asmlinkage long (*original_call)(int, const char __user *, int, umode_t);
#endif
/* 我们将用来替换 sys_openat (即调用 open 系统调用时调用的函数)的函数
* ( fs/open.c )
*
*
* ()
*/
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
static asmlinkage long our_sys_openat(const struct pt_regs *regs)
#else
static asmlinkage long our_sys_openat(int dfd, const char __user *filename,
int flags, umode_t mode)
#endif
{
int i = 0;
char ch;
if (__kuid_val(current_uid()) != uid)
goto orig_call;
/* 报告相关文件 */
pr_info("Opened file by %d: ", uid);
do {
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
get_user(ch, (char __user *)regs->si + i);
#else
get_user(ch, (char __user *)filename + i);
#endif
i++;
pr_info("%c", ch);
} while (ch != 0);
pr_info("\n");
orig_call:
/* 调用原始的sys_openat-否则,我们将失去打开文件的能力 */
#ifdef CONFIG_ARCH_HAS_SYSCALL_WRAPPER
return original_call(regs);
#else
return original_call(dfd, filename, flags, mode);
#endif
}
static unsigned long **acquire_sys_call_table(void)
{
#ifdef HAVE_KSYS_CLOSE
unsigned long int offset = PAGE_OFFSET;
unsigned long **sct;
while (offset < ULLONG_MAX) {
sct = (unsigned long **)offset;
if (sct[__NR_close] == (unsigned long *)ksys_close)
return sct;
offset += sizeof(void *);
}
return NULL;
#endif
#ifdef HAVE_PARAM
const char sct_name[15] = "sys_call_table";
char symbol[40] = { 0 };
if (sym == 0) {
pr_alert("对于 Linux v5.7 以上版本Kprobes 是获取符号的首选方法\n");
pr_info("如果没有 Kprobes则必须指定 sys_call_table 符号的地址\n");
pr_info("通过 /boot/System.map 或 /proc/kallsyms 将包含所有符号地址的符号转换为sym参数\n");
return NULL;
}
sprint_symbol(symbol, sym);
if (!strncmp(sct_name, symbol, sizeof(sct_name) - 1))
return (unsigned long **)sym;
return NULL;
#endif
#ifdef HAVE_KPROBES
unsigned long (*kallsyms_lookup_name)(const char *name);
struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name",
};
if (register_kprobe(&kp) < 0)
return NULL;
kallsyms_lookup_name = (unsigned long (*)(const char *name))kp.addr;
unregister_kprobe(&kp);
#endif
return (unsigned long **)kallsyms_lookup_name("sys_call_table");
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)
static inline void __write_cr0(unsigned long cr0)
{
asm volatile("mov %0,%%cr0" : "+r"(cr0) : : "memory");
}
#else
#define __write_cr0 write_cr0
#endif
static void enable_write_protection(void)
{
unsigned long cr0 = read_cr0();
set_bit(16, &cr0);
__write_cr0(cr0);
}
static void disable_write_protection(void)
{
unsigned long cr0 = read_cr0();
clear_bit(16, &cr0);
__write_cr0(cr0);
}
#endif
static int __init syscall_steal_start(void)
{
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
int err;
/* use symbol name from the module parameter */
syscall_kprobe.symbol_name = syscall_sym;
err = register_kprobe(&syscall_kprobe);
if (err) {
pr_err("register_kprobe() 在 %s 失败: %d\n", syscall_sym, err);
pr_err("请检查 “syscall_sym ”参数中的符号名称\n");
return err;
}
#else
if (!(sys_call_table_stolen = acquire_sys_call_table()))
return -1;
disable_write_protection();
/* 跟踪原始的打开函数 */
original_call = (void *)sys_call_table_stolen[__NR_openat];
/* 请改用我们的openat函数 */
sys_call_table_stolen[__NR_openat] = (unsigned long *)our_sys_openat;
enable_write_protection();
#endif
pr_info("监视 UID:%d\n", uid);
return 0;
}
static void __exit syscall_steal_end(void)
{
#if USE_KPROBES_PRE_HANDLER_BEFORE_SYSCALL
unregister_kprobe(&syscall_kprobe);
#else
if (!sys_call_table_stolen)
return;
/* 将系统调用恢复正常 */
if (sys_call_table_stolen[__NR_openat] != (unsigned long *)our_sys_openat) {
pr_alert("发现系统调用被其他人修改");
pr_alert("系统可能处于不稳定状态");
}
disable_write_protection();
sys_call_table_stolen[__NR_openat] = (unsigned long *)original_call;
enable_write_protection();
#endif
msleep(2000);
}
module_init(syscall_steal_start);
module_exit(syscall_steal_end);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,13 @@
obj-m += sleep.o
obj-m += completions.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
gcc cat_nonblock.c -o 查看文件
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm 查看文件

View File

@ -0,0 +1,59 @@
/*
* cat_nonblock.c - 退
*/
#include <errno.h> /* 用于 errno */
#include <fcntl.h> /* 用于 open */
#include <stdio.h> /* 标准 I/O */
#include <stdlib.h> /* 用于 exit */
#include <unistd.h> /* 用于 read */
#define MAX_BYTES 1024 * 4 /* 最大读取字节数 */
int main(int argc, char *argv[])
{
int fd; /* 要读取的文件的文件描述符 */
size_t bytes; /* 读取的字节数 */
char buffer[MAX_BYTES]; /* 存储读取字节的缓冲区 */
/* 用法说明 */
if (argc != 2) {
printf("用法: %s <文件名>\n", argv[0]);
puts("读取文件的内容,但不等待输入");
exit(-1);
}
/* 以非阻塞模式打开文件 */
fd = open(argv[1], O_RDONLY | O_NONBLOCK);
/* 如果打开失败 */
if (fd == -1) {
puts(errno == EAGAIN ? "打开会阻塞" : "打开失败");
exit(-1);
}
/* 读取文件并输出其内容 */
do {
/* 从文件中读取字符 */
bytes = read(fd, buffer, MAX_BYTES);
/* 如果发生错误,报告错误并退出 */
if (bytes == -1) {
if (errno == EAGAIN)
puts("正常情况下会阻塞,但你告诉我不阻塞");
else
puts("读取错误");
exit(-1);
}
/* 打印读取的字符 */
if (bytes > 0) {
for (int i = 0; i < bytes; i++)
putchar(buffer[i]);
}
/* 当没有错误且文件未结束时继续 */
} while (bytes > 0);
return 0;
}

View File

@ -0,0 +1,89 @@
/*
* completions.c
*/
#include <linux/completion.h>
#include <linux/err.h> /* 引入 IS_ERR() */
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/version.h>
static struct completion crank_comp; /* 定义完成情况 crank_comp */
static struct completion flywheel_comp; /* 定义完成情况 flywheel_comp */
/* crank线程函数 */
static int machine_crank_thread(void *arg)
{
pr_info("crank转动\n");
complete_all(&crank_comp); /* 设置 crank_comp 完成情况 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)
kthread_complete_and_exit(&crank_comp, 0); /* 内核版本 >= 5.17,使用 kthread 完成并退出 */
#else
complete_and_exit(&crank_comp, 0); /* 否则,使用旧版本接口完成并退出 */
#endif
}
/* flywheel启动线程函数 */
static int machine_flywheel_spinup_thread(void *arg)
{
wait_for_completion(&crank_comp); /* 等待 crank_comp 完成情况 */
pr_info("flywheel启动\n");
complete_all(&flywheel_comp); /* 设置 flywheel_comp 完成情况 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)
kthread_complete_and_exit(&flywheel_comp, 0); /* 内核版本 >= 5.17,使用 kthread 完成并退出 */
#else
complete_and_exit(&flywheel_comp, 0); /* 否则,使用旧版本接口完成并退出 */
#endif
}
static int __init completions_init(void)
{
struct task_struct *crank_thread; /* crank线程 */
struct task_struct *flywheel_thread; /* flywheel线程 */
pr_info("完成情况示例加载\n");
init_completion(&crank_comp); /* 初始化 crank_comp 完成情况 */
init_completion(&flywheel_comp); /* 初始化 flywheel_comp 完成情况 */
crank_thread = kthread_create(machine_crank_thread, NULL, "KThread Crank"); /* 创建crank线程 */
if (IS_ERR(crank_thread))
goto ERROR_THREAD_1;
flywheel_thread = kthread_create(machine_flywheel_spinup_thread, NULL,
"KThread Flywheel"); /* 创建flywheel线程 */
if (IS_ERR(flywheel_thread))
goto ERROR_THREAD_2;
wake_up_process(flywheel_thread); /* 启动flywheel线程 */
wake_up_process(crank_thread); /* 启动crank线程 */
return 0;
ERROR_THREAD_2:
kthread_stop(crank_thread); /* 停止crank线程 */
ERROR_THREAD_1:
return -1;
}
static void __exit completions_exit(void)
{
wait_for_completion(&crank_comp); /* 等待crank线程完成 */
wait_for_completion(&flywheel_comp); /* 等待flywheel线程完成 */
pr_info("完成情况示例卸载\n");
}
module_init(completions_init);
module_exit(completions_exit);
MODULE_DESCRIPTION("完成情况示例");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,191 @@
/*
* sleep.c - /proc , , ,
*/
#include <linux/atomic.h>
#include <linux/fs.h>
#include <linux/kernel.h> /* 引入 sprintf() */
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/proc_fs.h>
#include <linux/types.h>
#include <linux/uaccess.h> /* 引入 get_user 和 put_user */
#include <linux/version.h>
#include <linux/wait.h> /* 用于让程序休眠和唤醒 */
#include <asm/current.h>
#include <asm/errno.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#define HAVE_PROC_OPS
#endif
/* 在这里我们保存最后接收到的消息, 以证明我们可以处理输入 */
#define MESSAGE_LENGTH 80
static char message[MESSAGE_LENGTH];
static struct proc_dir_entry *our_proc_file;
#define PROC_ENTRY_FILENAME "sleep"
/* 从文件"读取": 由于使用的是 file_operations (内核中定义文件操作的结构体),
* 使procfs函数, 使(sprintf)
*/
static ssize_t module_output(struct file *file, /* 参见 include/linux/fs.h */
char __user *buf, /* 将数据放入的缓冲区(在用户段) */
size_t len, /* 缓冲区的长度 */
loff_t *offset)
{
static int finished = 0;
int i;
char output_msg[MESSAGE_LENGTH + 30];
/* 返回 0 表示文件结束 */
if (finished) {
finished = 0;
return 0;
}
sprintf(output_msg, "最后输入:%s\n", message);
for (i = 0; i < len && output_msg[i]; i++)
put_user(output_msg[i], buf + i);
finished = 1;
return i; /* 返回"读取"的字节数 */
}
/* 当用户向 /proc 文件写入时,此函数接收输入 */
static ssize_t module_input(struct file *file, /* 文件本身 */
const char __user *buf, /* 输入缓冲区 */
size_t length, /* 缓冲区的长度 */
loff_t *offset) /* 文件的偏移 - 忽略 */
{
int i;
/* 将输入放入 Message 中,以便 module_output 可以在后面使用它 */
for (i = 0; i < MESSAGE_LENGTH - 1 && i < length; i++)
get_user(message[i], buf + i);
/* 标准的、以零终止的字符串 */
message[i] = '\0';
/* 返回使用的输入字符数 */
return i;
}
/* 如果文件当前被某人打开,则为 1 */
static atomic_t already_open = ATOMIC_INIT(0);
/* 等待我们文件的进程队列 */
static DECLARE_WAIT_QUEUE_HEAD(waitq);
/* 当 /proc 文件被打开时调用 */
static int module_open(struct inode *inode, struct file *file)
{
/* 尝试在不阻塞的情况下获取 */
if (!atomic_cmpxchg(&already_open, 0, 1)) {
/* 在不阻塞的情况下成功,允许访问 */
try_module_get(THIS_MODULE);
return 0;
}
/* 如果文件标志包含 O_NONBLOCK意味着进程不希望等待文件。在这种情况下因为文件已经被打开
* -EAGAIN
*/
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
/* 这是调用 try_module_get(THIS_MODULE) 的正确位置,因为如果进程在模块内部的循环中,内核模块不能被移除 */
try_module_get(THIS_MODULE);
while (atomic_cmpxchg(&already_open, 0, 1)) {
int i, is_sig = 0;
pr_info("阻塞等待中\n");
/* 这个函数会将当前进程(包括任何系统调用)挂起, 并在函数调用wake_up(&waitq)后恢复
* 退使( ctrl-c)
*/
wait_event_interruptible(waitq, !atomic_read(&already_open));
/* 判断当前进程是否有待处理的非阻塞信号(主要用于在等待途中随时可以被中断) */
for (i = 0; i < _NSIG_WORDS && !is_sig; i++)
is_sig = current->pending.signal.sig[i] & ~current->blocked.sig[i];
if (is_sig) {
/* 对于那些被中断的打开操作,不会自动关闭。不在这里减少计数器,后续程序就无法按照预期将计数器降到零,
*
*/
pr_info("已被中断, 退出完成\n");
module_put(THIS_MODULE);
return -EINTR;
}
}
return 0; /* 允许访问 */
}
/* 当 /proc 文件被关闭时调用 */
static int module_close(struct inode *inode, struct file *file)
{
/* 将 already_open 设置为零,以便 waitq 中的一个进程可以将 already_open 设置回1并打开文件。
* already_open 1
*/
atomic_set(&already_open, 0);
/* 唤醒 waitq 中的所有进程,如果有人在等待文件,他们可以获得它 */
wake_up(&waitq);
module_put(THIS_MODULE);
return 0; /* 成功 */
}
/* 注册为 /proc 文件的结构,包含指向所有相关函数的指针 */
/* 我们的 proc 文件的文件操作。这是我们放置所有处理对文件进行操作
* NULL表示我们不想处理某些操作
*/
#ifdef HAVE_PROC_OPS
static const struct proc_ops file_ops_4_our_proc_file = {
.proc_read = module_output, /* 从文件"读取" */
.proc_write = module_input, /* 向文件"写入" */
.proc_open = module_open, /* 当 /proc 文件被打开时调用 */
.proc_release = module_close, /* 当文件关闭时调用 */
.proc_lseek = noop_llseek, /* 返回 file->f_pos */
};
#else
static const struct file_operations file_ops_4_our_proc_file = {
.read = module_output,
.write = module_input,
.open = module_open,
.release = module_close,
.llseek = noop_llseek,
};
#endif
/* 初始化模块 - 注册 proc 文件 */
static int __init sleep_init(void)
{
our_proc_file =
proc_create(PROC_ENTRY_FILENAME, 0644, NULL, &file_ops_4_our_proc_file);
if (our_proc_file == NULL) {
pr_debug("错误: 无法初始化 /proc/%s\n", PROC_ENTRY_FILENAME);
return -ENOMEM;
}
proc_set_size(our_proc_file, 80);
proc_set_user(our_proc_file, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID);
pr_info("/proc/%s 已创建\n", PROC_ENTRY_FILENAME);
return 0;
}
/* 清理 - 从 /proc 注销我们的文件。如果在 waitq 中仍有进程等待,
*
*
*/
static void __exit sleep_exit(void)
{
remove_proc_entry(PROC_ENTRY_FILENAME, NULL);
pr_debug("/proc/%s 已移除\n", PROC_ENTRY_FILENAME);
}
module_init(sleep_init);
module_exit(sleep_exit);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,14 @@
obj-m += example_mutex.o
obj-m += example_spinlock.o
obj-m += example_rwlock.o
obj-m += example_atomic.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

View File

@ -0,0 +1,76 @@
/*
* example_atomic.c
*/
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/printk.h>
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte) \
((byte & 0x80) ? '1' : '0'), ((byte & 0x40) ? '1' : '0'), \
((byte & 0x20) ? '1' : '0'), ((byte & 0x10) ? '1' : '0'), \
((byte & 0x08) ? '1' : '0'), ((byte & 0x04) ? '1' : '0'), \
((byte & 0x02) ? '1' : '0'), ((byte & 0x01) ? '1' : '0')
/* 演示原子加法和减法操作 */
static void atomic_add_subtract(void)
{
atomic_t rimian;
atomic_t xunmi = ATOMIC_INIT(50);
/* 赋值为45 */
atomic_set(&rimian, 45);
/* 减去1(45-1=44) */
atomic_dec(&rimian);
/* 加上7(44+7=51) */
atomic_add(7, &rimian);
/* 加1(51+1=52) */
atomic_inc(&rimian);
/* 两个名字, 没有其他意义 */
pr_info("寻觅: %d, 日冕: %d\n", atomic_read(&xunmi),
atomic_read(&rimian));
}
/* 演示原子位操作 */
static void atomic_bitwise(void)
{
unsigned long word = 0;
pr_info("初始 0: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word));
set_bit(3, &word); // 设置第3位为1
set_bit(5, &word); // 设置第5位为1
pr_info("修正 1: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word));
clear_bit(5, &word); // 清除第5位
pr_info("修正 2: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word));
change_bit(3, &word); // 切换第3位的值
pr_info("修正 3: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word));
// 检查第3位如果原本为1则返回 `非预期值` 的输出, 如果不是1则将其设置为1
if (test_and_set_bit(3, &word))
pr_info("非预期值\n");
pr_info("修正 4: " BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(word));
word = 255; // 设置word的所有位为1
pr_info("修正 5: " BYTE_TO_BINARY_PATTERN "\n", BYTE_TO_BINARY(word));
}
static int __init example_atomic_init(void)
{
pr_info("原子操作示例开始\n");
atomic_add_subtract();
atomic_bitwise();
return 0;
}
static void __exit example_atomic_exit(void)
{
pr_info("原子操作示例退出\n");
}
module_init(example_atomic_init);
module_exit(example_atomic_exit);
MODULE_DESCRIPTION("原子操作示例");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,40 @@
/*
* example_mutex.c
*/
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/printk.h>
static DEFINE_MUTEX(mymutex);
static int __init example_mutex_init(void)
{
int ret;
pr_info("example_mutex 初始化\n");
ret = mutex_trylock(&mymutex);
if (ret != 0) {
pr_info("已锁定\n");
if (mutex_is_locked(&mymutex) == 0)
pr_info("锁定失败!\n");
mutex_unlock(&mymutex);
pr_info("已解锁\n");
} else
pr_info("锁定失败\n");
return 0;
}
static void __exit example_mutex_exit(void)
{
pr_info("example_mutex 卸载\n");
}
module_init(example_mutex_init);
module_exit(example_mutex_exit);
MODULE_DESCRIPTION("互斥锁示例");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,55 @@
/*
* example_rwlock.c
*/
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/rwlock.h>
static DEFINE_RWLOCK(myrwlock);
static void example_read_lock(void)
{
unsigned long flags;
read_lock_irqsave(&myrwlock, flags);
pr_info("读锁已加锁\n");
/* 从某处读取数据 */
read_unlock_irqrestore(&myrwlock, flags);
pr_info("读锁已解锁\n");
}
static void example_write_lock(void)
{
unsigned long flags;
write_lock_irqsave(&myrwlock, flags);
pr_info("写锁已加锁\n");
/* 向某处写入数据 */
write_unlock_irqrestore(&myrwlock, flags);
pr_info("写锁已解锁\n");
}
static int __init example_rwlock_init(void)
{
pr_info("example_rwlock 模块启动\n");
example_read_lock();
example_write_lock();
return 0;
}
static void __exit example_rwlock_exit(void)
{
pr_info("example_rwlock 模块退出\n");
}
module_init(example_rwlock_init);
module_exit(example_rwlock_exit);
MODULE_DESCRIPTION("读/写锁示例");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,62 @@
/*
* example_spinlock.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(sl_static);
static spinlock_t sl_dynamic;
static void example_spinlock_static(void)
{
unsigned long flags;
spin_lock_irqsave(&sl_static, flags);
pr_info("已锁定静态自旋锁\n");
/* 执行一些安全的操作。由于这会占用 100% 的 CPU 时间,
*
*/
spin_unlock_irqrestore(&sl_static, flags);
pr_info("已解锁静态自旋锁\n");
}
static void example_spinlock_dynamic(void)
{
unsigned long flags;
spin_lock_init(&sl_dynamic);
spin_lock_irqsave(&sl_dynamic, flags);
pr_info("已锁定动态自旋锁\n");
/* 执行一些安全的操作。由于这会占用 100% 的 CPU 时间,
*
*/
spin_unlock_irqrestore(&sl_dynamic, flags);
pr_info("已解锁动态自旋锁\n");
}
static int __init example_spinlock_init(void)
{
pr_info("自旋锁示例启动\n");
example_spinlock_static();
example_spinlock_dynamic();
return 0;
}
static void __exit example_spinlock_exit(void)
{
pr_info("自旋锁示例退出\n");
}
module_init(example_spinlock_init);
module_exit(example_spinlock_exit);
MODULE_DESCRIPTION("自旋锁示例");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,12 @@
obj-m += print_string.o
obj-m += kbleds.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

View File

@ -0,0 +1,81 @@
/*
* kbleds.c - LED直到模块被卸载
*/
#include <linux/init.h>
#include <linux/kd.h> /* 引入 KDSETLED */
#include <linux/module.h>
#include <linux/tty.h> /* 引入 tty_struct */
#include <linux/vt.h> /* 引入 MAX_NR_CONSOLES */
#include <linux/vt_kern.h> /* 引入 fg_console */
#include <linux/console_struct.h> /* 引入 vc_cons */
MODULE_DESCRIPTION("示例模块演示如何使用键盘LED。");
static struct timer_list my_timer;
static struct tty_driver *my_driver;
static unsigned long kbledstatus = 0;
#define BLINK_DELAY HZ / 5
#define ALL_LEDS_ON 0x07
#define RESTORE_LEDS 0xFF
/* 函数 my_timer_func 定期闪烁键盘LED通过在键盘驱动上调用 ioctl() 命令 KDSETLED 实现。
* ioctl drivers/tty/vt/vt_ioctl.c, vt_ioctl()
*
* KDSETLED 7使 LED LED_SHOW_IOCTL LED 0xFF
* 7 LED LED_SHOW_FLAGS LED
* drivers/tty/vt/keyboard.c, setledstate()
*/
static void my_timer_func(struct timer_list *unused)
{
struct tty_struct *t = vc_cons[fg_console].d->port.tty;
if (kbledstatus == ALL_LEDS_ON)
kbledstatus = RESTORE_LEDS;
else
kbledstatus = ALL_LEDS_ON;
(my_driver->ops->ioctl)(t, KDSETLED, kbledstatus);
my_timer.expires = jiffies + BLINK_DELAY;
add_timer(&my_timer);
}
static int __init kbleds_init(void)
{
int i;
pr_info("kbleds: 正在加载\n");
pr_info("kbleds: 前台控制台为 %x\n", fg_console);
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (!vc_cons[i].d)
break;
pr_info("poet_atkm: 控制台[%i/%i] #%i, tty %p\n", i, MAX_NR_CONSOLES,
vc_cons[i].d->vc_num, (void *)vc_cons[i].d->port.tty);
}
pr_info("kbleds: 扫描控制台完成\n");
my_driver = vc_cons[fg_console].d->port.tty->driver;
pr_info("kbleds: tty 驱动程序名称 %s\n", my_driver->driver_name);
/* 设置 LED 闪烁定时器。 */
timer_setup(&my_timer, my_timer_func, 0);
my_timer.expires = jiffies + BLINK_DELAY;
add_timer(&my_timer);
return 0;
}
static void __exit kbleds_cleanup(void)
{
pr_info("kbleds: 正在卸载...\n");
del_timer(&my_timer);
(my_driver->ops->ioctl)(vc_cons[fg_console].d->port.tty, KDSETLED,
RESTORE_LEDS);
}
module_init(kbleds_init);
module_exit(kbleds_cleanup);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,59 @@
/*
* print_string.c - ttyX11telnet等tty
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h> /* 引入 current */
#include <linux/tty.h> /* 引入 tty 相关声明 */
static void print_string(char *str)
{
/* 当前任务的tty */
struct tty_struct *my_tty = get_current_tty();
/* 如果my_tty为NULL则当前任务没有可以打印的tty(例如,如果它是一个守护进程)。如果是这样,我们无法执行任何操作 */
if (my_tty) {
const struct tty_operations *ttyops = my_tty->driver->ops;
/* my_tty->driver是一个包含tty函数的结构体其中一个函数write用于将字符串写入tty。
*
*
* ttytty
*
*
* 使
* linux/Documentation/SubmittingPatches的第2节中有描述
*/
(ttyops->write)(my_tty, /* tty本身 */
str, /* 字符串 */
strlen(str)); /* 长度 */
/* tty最初是硬件设备通常严格遵循ASCII标准。在ASCII中要换行需要两个字符即回车符(结束当前行)和换行符(开始新行)。
* Unix中ASCII换行符用于两个目的 - 使\n
*
* Unix MS Windows
* - Unix 使 LF\n LF
* - Windows 使 CR+LF\r\n CR+LF
*
* \015 Carriage Return (CR) ASCII 13
* \012 Line Feed (LF) ASCII 10
*/
(ttyops->write)(my_tty, "\015\012", 2);
}
}
static int __init print_string_init(void)
{
print_string("模块已插入。你好, 世界! ");
return 0;
}
static void __exit print_string_exit(void)
{
print_string("模块已移除。再见, 世界! ");
}
module_init(print_string_init);
module_exit(print_string_exit);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,14 @@
obj-m += example_tasklet.o
obj-m += sched.o
obj-m += intrpt.o
obj-m += intrpt_old.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

View File

@ -0,0 +1,47 @@
/*
* example_tasklet.c
*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/printk.h>
/* 宏 DECLARE_TASKLET_OLD 存在是为了兼容性 参见 https://lwn.net/Articles/830964/ */
#ifndef DECLARE_TASKLET_OLD
#define DECLARE_TASKLET_OLD(arg1, arg2) DECLARE_TASKLET(arg1, arg2, 0L)
#endif
/* tasklet_fn 是 tasklet 执行的函数,接收一个 unsigned long 类型的参数 */
static void tasklet_fn(unsigned long data)
{
pr_info("示例 tasklet 开始执行!\n");
mdelay(20000); // 延迟 10 秒
pr_info("示例 tasklet 执行结束!\n");
}
/* 定义一个 tasklet并将 tasklet_fn 作为其处理函数 */
static DECLARE_TASKLET_OLD(mytask, tasklet_fn);
/* 模块初始化函数 */
static int __init example_tasklet_init(void)
{
pr_info("tasklet 示例初始化\n");
tasklet_schedule(&mytask); // 安排 tasklet 执行
mdelay(10);
pr_info("示例 tasklet 初始化继续...\n");
return 0;
}
/* 模块退出函数 */
static void __exit example_tasklet_exit(void)
{
pr_info("tasklet 示例退出\n");
tasklet_kill(&mytask); // 终止 tasklet 的执行
}
module_init(example_tasklet_init);
module_exit(example_tasklet_exit);
MODULE_DESCRIPTION("Tasklet 示例");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,151 @@
/*
* intrpt.c - GPIO
*
* Stefan Wendler (devnull@kaltpost.de) RPi
* https://github.com/wendlers/rpi-kmod-samples
*
* LED LED
*/
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/kernel.h> /* 用于 ARRAY_SIZE() */
#include <linux/module.h>
#include <linux/printk.h>
static int button_irqs[] = { -1, -1 };
/* 定义 LED 的 GPIO。
* TODO: GPIO
*/
static struct gpio leds[] = { { 1, GPIOF_OUT_INIT_LOW, "LED 1" } };
/* 定义按钮的 GPIO。
* TODO: GPIO
*/
static struct gpio buttons[] = { { 2, GPIOF_IN, "LED 1 打开按钮" },
{ 3, GPIOF_IN, "LED 1 关闭按钮" } };
/* 按钮按下时触发的中断函数。 */
static irqreturn_t button_isr(int irq, void *data)
{
/* 第一个按钮 */
if (irq == button_irqs[0] && !gpio_get_value(leds[0].gpio))
gpio_set_value(leds[0].gpio, 1); /* 点亮 LED */
/* 第二个按钮 */
else if (irq == button_irqs[1] && gpio_get_value(leds[0].gpio))
gpio_set_value(leds[0].gpio, 0); /* 熄灭 LED */
return IRQ_HANDLED;
}
static int __init intrpt_init(void)
{
int ret = 0;
pr_info("%s\n", __func__);
/* 注册 LED GPIO */
ret = gpio_request_array(leds, ARRAY_SIZE(leds));
if (ret) {
pr_err("无法请求 LED 的 GPIO: %d\n", ret);
return ret;
}
/* 注册按钮 GPIO */
ret = gpio_request_array(buttons, ARRAY_SIZE(buttons));
if (ret) {
pr_err("无法请求按钮的 GPIO: %d\n", ret);
goto fail1;
}
pr_info("当前按钮1的值: %d\n", gpio_get_value(buttons[0].gpio));
ret = gpio_to_irq(buttons[0].gpio);
if (ret < 0) {
pr_err("无法请求 IRQ: %d\n", ret);
goto fail2;
}
button_irqs[0] = ret;
pr_info("成功请求 BUTTON1 的 IRQ # %d\n", button_irqs[0]);
/* 请求按钮1的中断
* IRQF_TRIGGER_RISING
* IRQF_TRIGGER_FALLING
* 使
* */
ret = request_irq(button_irqs[0], button_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"gpio模块#按钮1", NULL);
if (ret) {
pr_err("无法请求 IRQ: %d\n", ret);
goto fail2;
}
ret = gpio_to_irq(buttons[1].gpio);
if (ret < 0) {
pr_err("无法请求 IRQ: %d\n", ret);
goto fail2;
}
button_irqs[1] = ret;
pr_info("成功请求 BUTTON2 的 IRQ # %d\n", button_irqs[1]);
/* 请求按钮2的中断, 标志位同上 */
ret = request_irq(button_irqs[1], button_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"gpio模块#按钮2", NULL);
if (ret) {
pr_err("无法请求 IRQ: %d\n", ret);
goto fail3;
}
return 0;
/* 清理已经设置的部分 */
fail3:
free_irq(button_irqs[0], NULL); /* 释放按钮1的中断请求 */
fail2:
gpio_free_array(buttons, ARRAY_SIZE(buttons)); /* 释放按钮的GPIO */
fail1:
gpio_free_array(leds, ARRAY_SIZE(leds)); /* 释放LED的GPIO */
return ret;
}
static void __exit intrpt_exit(void)
{
int i;
pr_info("%s\n", __func__);
/* 释放 IRQ */
free_irq(button_irqs[0], NULL);
free_irq(button_irqs[1], NULL);
/* 关闭所有 LED */
for (i = 0; i < ARRAY_SIZE(leds); i++)
gpio_set_value(leds[i].gpio, 0);
/* 注销 GPIO */
gpio_free_array(leds, ARRAY_SIZE(leds));
gpio_free_array(buttons, ARRAY_SIZE(buttons));
}
module_init(intrpt_init);
module_exit(intrpt_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("处理一些 GPIO 中断");

View File

@ -0,0 +1,34 @@
/*
* sched.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
static struct workqueue_struct *queue = NULL;
static struct work_struct work;
static void work_handler(struct work_struct *data)
{
pr_info("工作队列函数\n");
}
static int __init sched_init(void)
{
queue = alloc_workqueue("HELLOWORLD", WQ_UNBOUND, 1);
INIT_WORK(&work, work_handler);
queue_work(queue, &work);
return 0;
}
static void __exit sched_exit(void)
{
destroy_workqueue(queue);
}
module_init(sched_init);
module_exit(sched_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("工作队列示例");

11
示例/Makefile Normal file
View File

@ -0,0 +1,11 @@
obj-m += example_taskle.o
PWD := $(CURDIR)
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean