前言

之前成功编译了内核,这次学习如何修改增加删除内核模块,为了保证内核的纯净,我特意重新编译安装了一个新的5.11.8的内核,其他内核同理。
本文原创,创作不易,转载请注明!!!
本文链接
个人博客:https://ronglin.fun/?p=169
PDF链接:见博客网站
CSDN: https://blog.csdn.net/RongLin02/article/details/115422459

通过内核模块显示进程控制块信息

环境

主机:联想Y7000P;64位windows10;CPU:i7-9750H;显卡:GTX 1660 Ti;内存:16G
虚拟机:Ubuntu 18.04 LTS;硬盘100G;内存4G;64位;4核心
Linux内核:5.11.8

实验简述

首先先看实验内容,实验内容来自指导书,如有侵权联系我删除:

实验说明

在内核中,所有进程控制块都被一个双向链表连接起来,该链表中的第一个进程控制块为init_task。编写一个内核模块,模块接收用户传递的一个参数num,num指定要打印的进程控制块的数量;若用户不指定num或者num<0,模块则打印所有进程控制块的信息。需要打印的进程控制块信息有:进程PID和进程的可执行文件名。

解决方案

(1)定义模块参数
该模块需要接受用户传递的参数,在使用该参数之前,需要在代码中预先定义好该参数,将该参数的类型设置为整型,并且在sysfs文件系统中的权限是只读的。定义的方法为:
static int num=-1;
module _param(num, int, S_IRUGO);
该参数的初始值被设置为-1。-1将作为打印所有进程控制块的标记,默认值为-1,意味着当用户不传入任何参数时,模块将打印所有的进程的信息。
(2)访问进程控制块链表
在内核中,进程控制块被组织成多个双向链表,其中有一个双向链表包含所有的进程
精袈
块,只需要访问该双向链表,就可以访问到所有进程控制块。Linux内核中几乎所有双向
都采用相同的数据结构来实现,内核中定义list_head通用数据结构,其定义如下:

struct list_head {struct list head *next,*prev;
};

list_head中,next指向链表中的下一个list_head 数据结构,prev指向链表中的前一个list_head 数据结构。之所以说该结构是用于实现一个通用的双向链表是因为:如果一个数据结构包含list_head结构,开发者就可以通过内核提供的一组宏创建并操作一个双向链表,而该链表中元素的类型为该数据结构,如图12-1所示:

在进程控制块task_struct中,包含一个名为tasks 的成员,该成员的类型为list_head,这意味着进程控制块能够通过该成员将进程控制块串成一个双向链表。Linux内核通过该成员将所有的进程都放入同一个双向链表,因为 list_head 结构中的next 和 prev指针并不是指向包含list_head 的数据结构,而是指向另一个list_head数据结构。为了访问包含list_head的数据结构,内核提供一个宏:

list_entry(ptr,type,member);

在该宏中, ptr是一个指向list_head的指针, type是包含list_head的数据结构类型,而member是list_head在该数据结构中的成员名。例如,若一个进程控制块中的tasks 的地址为p,为了访问该进程控制块,可以采用:

list_entry(p,struct task _struct,tasks);

该宏便会返回该进程控制块的地址。
知道如何使用双向链表后,就可以方便地访问内核中所有的进程控制块,因为它通过tasks成员串成一个双向链表,如果得到一个进程控制块的地址p,开发者可以通过:

list_entry(p->tasks.next, struct task _struct, tasks);

访问该双向链表中的下一个进程控制块。在该双向链表中,第一个进程控制块为init_task,如果开发者发现下一个进程控制块为init_task时,说明已经完整地遍历过所有进程控制块。
内核定义宏for_each process用于遍历所有的进程控制块,开发者通过该宏就能将所有的进程控制块访问一遍,该宏展开的形式为:

for (= &init_task ; (p= list_entry((p)->tasks.next, struct task_struct, tasks) !=&init_task ; )

(3)输出进程控制块信息
进程控制块中包含进程大部分信息,根据实验要求,模块需要打印进程的 pid和可执行文件名,在进程控制块的数据结构中,成员pid为进程的PD,而成员comm包含进程的可执行文件名。在内核中,模块可以通过 printk( )内核函数将这些信息打印到系统日志中。

程序框架


实操

实验说明太多了,看的有点头大,幸好它给了源码,直接上手实操,出了问题再看资料。

模块文件知识

先把程序框架中的代码敲出来,exp_exit()函数中就添加一句

printk("Good bye,Ronglin\n");

先大概的讲述一下c文件的内容:
头文件声明
头两行是模块头文件,头文件module.h和 init.h是必不可少的。module.h包含加载模块需要的函数和符号定义; init.h中包含模块初始化和清理函数的定义。如果在加载时允许用户传递参数,模块还应该包含moduleparam.h头文件
模块许可声明
从内核v2.4.10版本开始,模块必须通过MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,内核会显示“kernel tainted”(内核被污染)的警告信息。从linux/module.h文件中可看到,被内核接受的许可证有GPL,GPL v2,GPL and additional rights,Dual BSD/GPL,Dual MPL/GPL,Dual MIT/GPL和Proprietary。
初始化和清理函数
声明内核模块必须调用宏module_init和 module_exit 去注册初始化与清理函数。在模块源代码的最后两行已声明该模块被加载时的初始化函数是exp_init(),模块被卸载时的清理函数是 exp_exit()。需要注意,初始化与清理函数必须在宏module_init和 module_exit使用前定义,否则会出现编译错误。这两个函数配对使用,例如,当module_init()申请一个资源,那么module_exit()中就应释放这个资源,使得模块不留下任何副作用。
一般来说有这上述3部分足以。

大概了解了c文件结构,再来看看makefile文件的结构

obj-m:=listprocess.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesobj-m:=listprocess.o

其实核心部分就是这一句,obj-m:=listprocess.o,生成的内核模块名为listprocess.ko。如果需要生成一个名为mymodule.ko 的内核模块,并且该内核模块的源代码来源于modulesrc1.c和 modulesrc2.c两个文件, makefile文件应该写成如下形式:

obj -m:-mymodule.o
module objs:-modulesrc1.o modulesrc2.o

如果用户采用这种makefile,在调用make命令时,需要将内核源代码所在目录作为一个参数传递给make命令。
例如,如果v2.6的内核源代码位于/usr/src/linux-2.6目录下,用户模块源代码所在目录应该使用的make命令为:

make -C/usrlsrc/linux-2.6 M='pwd'modules

makefile还提供另一种形式,用户可指定内核源代码所在的目录,而不用每次都把该目录作为参数传递给make命令。对于“hello world!”示例,在 makefile 中指定内核源代码的方式为上面的开头的代码。
在这个makefile文件中,KERNELDIR指定内核的源代码目录,该目录通过当前运行内核使用的模块目录中的build符号链接指定。
巴拉巴拉这么一大堆理论看着头疼马上开始实操。

安装模块

将两个文件放在Ubuntu下,并且命名为mykernel。本机文件结构如下图

mykernel文件夹下,直接make编译,如果没有make指令的话,可以输入sudo apt install make安装make。
输入

sudo make

直接make编译报错了,看输出信息

error: function declaration isn’t a prototype [-Werror=strict-prototypes]

查了资料,是函数参数表没指明,如果没有参数表应该写void而不能为空,用nano打开.c文件,改一下,输入

sudo nano listprocess.c

exp_initexp_exit的括号里加上void

然后Ctrl+O写入,回车确定,然后Ctrl+X退出,然后再输入sudo make编译

又报错了,提示

obj-m:=listprocess.o
/bin/sh: 1: obj-m:=listprocess.o: not found
Makefile:6: recipe for target 'default' failed
make: *** [default] Error 127

用nano打开makefile,然后然后删除最后一句,输入

sudo nano Makefile


删除最后一句的obj-m:=listprocess.o
然后Ctrl+O写入,回车确定,然后Ctrl+X退出,然后再输入sudo make编译
没有报错了
开始安装,输入

sudo insmod listprocess.ko

注意是.ko文件,然后输入dmesg查看效果
有了pid,成功了,卸载的话输入

sudo rmmod listprocess.ko

然后输入dmesg查看效果

卸载成功,模块测试成功,=w=

补充:
问题:for_each_process报错
原因:有些人编译的时候会有报错没找到 for_each_process 函数,可能是对于不同的内核版本,for_each_process 位置不同,可以去查看一下源码,找一下for_each_process 函数。以本人尝试结果为例,4.11以后,for_each_process 在 include/linux/sched/signal.h中。
解决方案:在.c文件的头中加入一行代码#include <linux/sched/signal.h>就行了

Linux学习之内核模块编程相关推荐

  1. linux学习笔记 -- 系统编程

    系统编程 相关概念 概念 简易cpu结构 mmu内存管理单元 环境变量 PATH SHELL HOME LANG TERM getenv setenv unsetenv 进程控制 fork函数 get ...

  2. Linux学习之socket编程(一)

    socket编程 socket的概念: 在TCP/IP协议中,"IP地址+TCP或UDP端口号"唯一标识网络通讯中的一个进程,"IP地址+端口号"就称为sock ...

  3. Linux学习:Shell编程总结

    在学习L为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是shell仍然是一个非常灵活的工具.Shell不仅仅是命令的收集,而且是一门非常棒的编程语言.您可以通过使用 ...

  4. linux学习杂记_socket编程(含select)

    http://blog.csdn.net/chenxun_2010/article/details/50488394 上面是很好的一个流程. 网络的一些相关知识: 网卡功能: 载波侦听, 冲突检测, ...

  5. Linux学习:sheel编程

    Shell编程 1.Shell概述 ​ Shell是一个命令行解释器,他为用户提供了一个向Linux内核发送请求一边运行程序的界面系统级程序,用户可以用Shell来启动.挂起.停止甚至是编写一些程序. ...

  6. Linux 学习笔记_10_Shell编程_2_Shell编程语法(三)

    三.select/in [用的相对来说比较少] 格式: select 变量 in 关键字  do  command 1  ... ...  command n  done  select把关键字中的每 ...

  7. Linux学习之系统编程篇:对线程的基本认识

    (1)fork()后创建的子进程是父进程的拷贝,那么pthread_create,创建线程,创建的线程跟原进程有什么关系呢? fork 会通过拷贝产生新的虚拟地址空间(PCB 会变化),而 pthre ...

  8. Linux学习之系统编程篇:ps 和 kill 命令以及父子进程间数据共享模式

    一.ps 和 kill 命令 1.ps 命令 常用方式: ps aux :查看正在运行进程信息(主要查 pid). ps ajx :更加详细(PID. PPID:父进程 id. PGID:进程组 id ...

  9. Linux学习之系统编程篇:MMU(Memory Manager Unit 内存管理单元)

    一.虚拟内存地址 对应于上图的两端,其中 0 - 3G 是用户区 ,3 - 4G 是内核区.编码的内存地址都是虚拟地址. 在3G到4G之间是PCB 进程控制块.从3G到0依次为: (1)命令行参数 和 ...

最新文章

  1. GDAL2.1.1库在Ubuntu14.04下编译时遇到的问题处理方法
  2. win7 mysql8.0.11安装教程_Win7系统安装 MySQL 8.0.11
  3. 创业思路(1) - 收藏夹分享平台
  4. P4396 [AHOI2013]作业 cdq分治
  5. 写一个http服务器
  6. mysql添加新用户
  7. python怎样编写定时程序_Python如何实现定时器功能
  8. 升级计算机方案,关于计算机升级方案
  9. 用c语言实现循环神经网络,浅谈LSTM循环神经网络
  10. 支付宝首页新增商家服务进度卡片 目前正在灰度测试中
  11. PSIM软件学习---07 元件参数动态扫描
  12. 十三、Swing绘图技术
  13. wordpress配置邮箱发送功能
  14. 易飞erp postgre mysql_pgadmin 执行sql
  15. 使用记账软件 记录日常收支并将数据导出表格保存
  16. c 易语言置入代码6,易语言置入代码 , 谁碰到这种情况_精易论坛
  17. MoveIt与RobotStudio
  18. 大数据与云计算、物联网三者的区别和关联
  19. CVTE 2022届实习生 Web后台开发笔试
  20. dr优先级默认_OSPF路由器不能成为DR/BDR唯一的方法:DR优先级=0

热门文章

  1. 2018.5.28 PSOC第一枪:基于cypress的蓝牙开发
  2. 前端httpd+keepalived加后端heartbeat+nfs+drbd实现httpd服务的高效应用及资源统一管理...
  3. [链接地址] Express 4.x API 中文手册
  4. Widget开发中遇到的坑
  5. Mysql Packet for query is too large解决方法
  6. ubuntu源列表(清华,阿里,官方,选一即可)
  7. Xcode模版生成文件头部注释
  8. xe6+firedac 连接sybase
  9. 云计算时代下的数据中心运维之路
  10. 飞鸽传书2011绿色版简单性