使用md格式重写翻译
This commit is contained in:
parent
b8430a39b5
commit
88db7fbc28
30
.mailmap
30
.mailmap
|
@ -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
|
24
Makefile
24
Makefile
|
@ -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
|
28
README.md
28
README.md
|
@ -2,25 +2,25 @@
|
|||
|
||||
# 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. 安装先决条件
|
||||
> 要从源代码生成图书,需要使用 make 和 [TeXLive](https://www.tug.org/texlive/) ([MacTeX](https://www.tug.org/mactex/))。
|
||||
> 对于不同的发行版会有不同的包名其中主要包名有texlive(安同). texlive-full(debian系), texlive-binextra texlive-bin(arch系) mactex(mac)
|
||||
3. 生成 PDF 和/或 HTML 文档
|
||||
> 可以用以下命令生成文档:
|
||||
```bash
|
||||
make all # 生成PDF文档
|
||||
make html # 将 TeX 转换为 HTML
|
||||
make clean # 删除生成的文件
|
||||
```
|
||||
> 目录:
|
||||
> * [0 准备工作](文档/0-准备工作)
|
||||
> * [1 编写驱动](文档/1-编写驱动)
|
||||
> * [2 驱动相关文件](文档/2-驱动相关文件)
|
||||
> * [3 ioctl](文档/3-ioctl)
|
||||
> * [4 系统调用](文档/4-系统调用)
|
||||
> * [5 阻塞进程和线程](文档/5-阻塞进程和线程)
|
||||
> * [6 内核模块的锁](文档/6-内核模块的锁)
|
||||
> * [7 驱动与用户交互](文档/7-驱动与用户交互)
|
||||
> * [8 调度与中断](文档/8-调度与中断)
|
||||
> * [](文档/)
|
||||
|
||||
## 版权说明
|
||||
|
||||
|
|
Binary file not shown.
59
contrib.tex
59
contrib.tex
|
@ -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>
|
91
html.cfg
91
html.cfg
|
@ -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
|
|
@ -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}{}
|
|
@ -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}}}
|
|
@ -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>
|
|
@ -1 +0,0 @@
|
|||
Jim Huang, One of main author
|
|
@ -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>
|
|
@ -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
17
示例/0-hello/Makefile
Normal 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
21
示例/0-hello/hello-1.c
Normal 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
24
示例/0-hello/hello-2.c
Normal 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
25
示例/0-hello/hello-3.c
Normal 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
25
示例/0-hello/hello-4.c
Normal 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
67
示例/0-hello/hello-5.c
Normal 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
7
示例/0-hello/hello.c
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("你好");
|
||||
return 0;
|
||||
}
|
15
示例/0-hello/start.c
Normal file
15
示例/0-hello/start.c
Normal 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
14
示例/0-hello/stop.c
Normal 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
11
示例/1-chardev/Makefile
Normal 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
161
示例/1-chardev/chardev.c
Normal 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");
|
||||
|
15
示例/2-驱动相关文件/Makefile
Normal file
15
示例/2-驱动相关文件/Makefile
Normal 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 占位
|
61
示例/2-驱动相关文件/hello-sysfs.c
Normal file
61
示例/2-驱动相关文件/hello-sysfs.c
Normal 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");
|
||||
|
37
示例/2-驱动相关文件/procfs.c
Normal file
37
示例/2-驱动相关文件/procfs.c
Normal 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;
|
||||
}
|
69
示例/2-驱动相关文件/procfs1.c
Normal file
69
示例/2-驱动相关文件/procfs1.c
Normal 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");
|
||||
|
98
示例/2-驱动相关文件/procfs2.c
Normal file
98
示例/2-驱动相关文件/procfs2.c
Normal 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");
|
||||
|
112
示例/2-驱动相关文件/procfs3.c
Normal file
112
示例/2-驱动相关文件/procfs3.c
Normal 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");
|
||||
|
114
示例/2-驱动相关文件/procfs4.c
Normal file
114
示例/2-驱动相关文件/procfs4.c
Normal 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
14
示例/3-ioctl/Makefile
Normal 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
37
示例/3-ioctl/chardev.h
Normal 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
207
示例/3-ioctl/chardev2.c
Normal 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
212
示例/3-ioctl/ioctl.c
Normal 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 测试模块");
|
||||
|
103
示例/3-ioctl/userspace_ioctl.c
Normal file
103
示例/3-ioctl/userspace_ioctl.c
Normal 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);
|
||||
}
|
||||
|
11
示例/4-系统调用/Makefile
Normal file
11
示例/4-系统调用/Makefile
Normal 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
|
||||
|
275
示例/4-系统调用/syscall-steal_loong64.c
Normal file
275
示例/4-系统调用/syscall-steal_loong64.c
Normal 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");
|
275
示例/4-系统调用/syscall-steal_x86.c
Normal file
275
示例/4-系统调用/syscall-steal_x86.c
Normal 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");
|
13
示例/5-阻塞进程和线程/Makefile
Normal file
13
示例/5-阻塞进程和线程/Makefile
Normal 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 查看文件
|
59
示例/5-阻塞进程和线程/cat_nonblock.c
Normal file
59
示例/5-阻塞进程和线程/cat_nonblock.c
Normal 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;
|
||||
}
|
||||
|
89
示例/5-阻塞进程和线程/completions.c
Normal file
89
示例/5-阻塞进程和线程/completions.c
Normal 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");
|
||||
|
191
示例/5-阻塞进程和线程/sleep.c
Normal file
191
示例/5-阻塞进程和线程/sleep.c
Normal 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");
|
||||
|
14
示例/6-内核模块的锁/Makefile
Normal file
14
示例/6-内核模块的锁/Makefile
Normal 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
|
||||
|
76
示例/6-内核模块的锁/example_atomic.c
Normal file
76
示例/6-内核模块的锁/example_atomic.c
Normal 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");
|
40
示例/6-内核模块的锁/example_mutex.c
Normal file
40
示例/6-内核模块的锁/example_mutex.c
Normal 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");
|
55
示例/6-内核模块的锁/example_rwlock.c
Normal file
55
示例/6-内核模块的锁/example_rwlock.c
Normal 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");
|
62
示例/6-内核模块的锁/example_spinlock.c
Normal file
62
示例/6-内核模块的锁/example_spinlock.c
Normal 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");
|
12
示例/7-驱动与用户交互/Makefile
Normal file
12
示例/7-驱动与用户交互/Makefile
Normal 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
|
||||
|
81
示例/7-驱动与用户交互/kbleds.c
Normal file
81
示例/7-驱动与用户交互/kbleds.c
Normal 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");
|
59
示例/7-驱动与用户交互/print_string.c
Normal file
59
示例/7-驱动与用户交互/print_string.c
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* print_string.c - 将输出发送到我们运行的tty,无论是通过X11、telnet等。我们通过将字符串打印到当前任务关联的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。
|
||||
* 它可以用于从用户或内核的内存段获取字符串。
|
||||
*
|
||||
* 该函数的第一个参数是要写入的tty,因为同样的函数通常会用于所有相同类型的tty。
|
||||
* 第二个参数是指向字符串的指针。第三个参数是字符串的长度。
|
||||
*
|
||||
* 如下所示,有时需要使用预处理器来创建适用于不同内核版本的代码。我们在这里采取的方法不具备通用性
|
||||
* 处理这种情况的正确方法在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");
|
14
示例/8-调度与中断/Makefile
Normal file
14
示例/8-调度与中断/Makefile
Normal 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
|
||||
|
47
示例/8-调度与中断/example_tasklet.c
Normal file
47
示例/8-调度与中断/example_tasklet.c
Normal 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");
|
||||
|
151
示例/8-调度与中断/intrpt.c
Normal file
151
示例/8-调度与中断/intrpt.c
Normal 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 中断");
|
||||
|
34
示例/8-调度与中断/sched.c
Normal file
34
示例/8-调度与中断/sched.c
Normal 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
11
示例/Makefile
Normal 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
|
||||
|
Loading…
Reference in New Issue
Block a user