实验要求:
1.查看task_struct的结构,找到其中的pid,state,prio,parent pid字段
2.在task_struct结构中找到vma相关字段,vm_start,vm_end,vm_next
3.打印指定pid的基本信息,包括基本信息及VMA内存区的信息。
4.显示dmesg的查看内核模块的输出信息里的vma地址,和通过cat /proc/pid/maps命令显示内容一致。
实验原理:
1.linux内核中进程控制块结构,linux内核管理进程的数据结构及其访问方法:
其结构大概如上图,即task_struct。其中定义了子进程、父进程、优先级和进程状态等字段。不同内核版本的字段名可能不一样。
访问方法就是定义一个进程控制块task_struct类型的指针,然后通过指针获取进程号、优先级等字段属性。
2.linux系统中vm结构,linux系统中vm的管理以及vm链表的组织形式:
Linux中,虚存是通过链表的形式管理的,利用vm_next指针可以指向下一个地址块节点。
获取虚存地址时,先定义一个进程控制块指针p,利用p的mm指针找到mm_struct中的mmap指针(指向虚存区域列表),进而找到每块虚存地址的起始地址。
3.内核模块参数传递,加载内核模块时候的参数传递方法:
其实就是函数module_param(pid,int,0644)的使用,第一个参数为参数名,第二个参数为参数类型,第三个参数为指定了在sysfs中相应文件的访问权限。
加载内核模块时,需要在命令后边给参数赋值,比如:
本实验中,pid对应的进程必须存在且正在运行,否则会提示“已杀死”:
参考代码:
task_struct.c:
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/sched.h>
# include <linux/init_task.h>
# include <linux/init.h>
# include <linux/fdtable.h>
# include <linux/fs_struct.h>
# include <linux/mm_types.h>
MODULE_LICENSE("GPL");
//内核模块初始化函数
static int __init print_pid(void)
{
struct task_struct *task, *p;
struct list_head *pos;
int count=0;
printk("Printf process'message begin:\n");
task = &init_task;
//遍历进程链表
list_for_each(pos,&task->tasks)
{
p = list_entry(pos,struct task_struct,tasks);
count++;
printk("\n\n");
printk("pid:%d; state:%lx; prio:%d; static_prio:%d; parent'pid:%d; count:%d; umask:%d;", \
p->pid,p->__state,p->prio,p->static_prio,(p->parent)->pid, \
atomic_read((&(p->files)->count)),(p->fs)->umask);
if((p->mm)!=NULL)
printk("total_vm:%ld;",(p->mm)->total_vm);
}
printk("进程的个数:%d\n",count);
return 0;
}
//内核模块退出函数
static void __exit pid_exit(void)
{
printk("exiting...\n");
}
module_init(print_pid);
module_exit(pid_exit);
task_struct.c对应Makefile文件:
#Makefile文件注意:假如前面的.c文件起名为first.c,那么这里的Makefile文件中的.o文
#件就要起名为first.o 只有root用户才能加载和卸载模块
obj-m:=task_struct.o #产生task_struct模块的目标文件
#目标文件 文件 要与模块名字相同
CURRENT_PATH:=$(shell pwd) #模块所在的当前路径
LINUX_KERNEL:=$(shell uname -r) #linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
#[Tab] 内核的路径 当前目录编译完放哪 表明编译的是内核模块
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理模块
mem.c:
#include<linux/module.h>
#include<linux/init.h>
#include<linux/interrupt.h>
#include<linux/sched.h>
static int pid;
module_param(pid,int,0644);
static int __init memtest_init(void){
struct task_struct *p;
//struct task_struct *p1;
struct vm_area_struct *temp;
printk("The virtual memory areas(VMA) are:\n");
p=pid_task(find_vpid(pid),PIDTYPE_PID);//该函数因内核版本而稍有不同
//printk("%p\n",p);
//p1=pid_task(find_get_pid(pid),PIDTYPE_PID);
//printk("%p\n",p1);
temp=p->mm->mmap;
while(temp){
//printk("start:%p\tend:%p\n",(unsigned long*)temp->vm_start,(unsigned long*)temp->vm_end);
//printk("start:%x\tend:%x\n",(unsigned long*)temp->vm_start,(unsigned long*)temp->vm_end);
printk("start:%lx\tend:%lx\n",(unsigned long*)temp->vm_start,(unsigned long*)temp->vm_end);
//printk("start:%pK\tend:%pK\n",(unsigned long*)temp->vm_start,(unsigned long*)temp->vm_end);
temp=temp->vm_next;
}
return 0;
}
static void __exit memtest_exit(void){
printk("Unloading my module.\n");
return;
}
module_init(memtest_init);
module_exit(memtest_exit);
MODULE_LICENSE("GPL");
mem.c对应Makefile文件:
#Makefile文件注意:假如前面的.c文件起名为first.c,那么这里的Makefile文件中的.o文
#件就要起名为first.o 只有root用户才能加载和卸载模块
obj-m:=mem.o #产生task_struct模块的目标文件
#目标文件 文件 要与模块名字相同
CURRENT_PATH:=$(shell pwd) #模块所在的当前路径
LINUX_KERNEL:=$(shell uname -r) #linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
#[Tab] 内核的路径 当前目录编译完放哪 表明编译的是内核模块
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理模块
exam.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(){
int i,fd;
char * buf;
printf("pid=%d\n",getpid());
sleep(500);
}