Linux实践二:模块
一、基本模块的实现:
1.进程遍历打印输出
2.简单地编写一个新的系统调用(替换空的系统调用号)
基本模块学到的知识点:
1.相关指令
make oldconfig 配置内核
make 编译内核
make modules_instal 编译安装内核模块
make install 引导新编译的内核
uname –a 查看内核版本
lsmod 查看加载的模块
insmod 加载模块
rmmod 卸载模块
dmesg 显示开机信息
基础模块一:系统调用(替换空的233)
文件Makefile
obj-m :=syscall.o PWD := $(shell pwd) KDIR:=/lib/modules/4.4.0-21-generic/build all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean
uname -r 显示当前使用的内核信息,确定自己当前linux的内核版本是多少
测试代码:
#include <stdio.h> #include <stdlib.h> int main() { unsigned long x = 0; x = syscall(223); //测试223号系统调用 printf("Hello, %ld\n", x); return 0; }
make -C $(LINUX_KERNEL_PATH) 指明跳转到内核源码目录下读取那里的Makefile
M=$(CURRENT_PATH) 表明返回到当前目录继续执行当前的Makefile。
头文件module.h,必须包含此文件;
头文件kernel.h,包含常用的内核函数;
头文件init.h包含宏_init和_exit,允许释放内核占用的内存。
基础模块二:进程遍历
二、深入
学习一:页表模块
基本思路:
1.研究学习学姐的实践指导书,由于新版本的Ubuntu需要的是四级页表,根据百度的一些知识,将学姐的代码由二级页表修改成四级页表
(重点是理解页表结构)
2.进一步修改页表,修改权限,使得用户态的可以变成内核态(研究中)。
对于页的保护通常设置一个存取控制字段。当这个字段占一位时,用于规定该页中的内 容允许写还是读;如果存取控制字段占两位,那么它可以表示存取控制为读写、只读和 只运行三种。当进程写一个只读页时,系统就会通过中断来报错。
模块参数以module_param(name,type,perm)的形式定义,其中name为参数名,type为参数的数据类型,perm是一个权限值,控制谁可以存取模块参数在sysfs中的表示。
页全局目录(Page Global Directory)
• 页上级目录(Page Upper Directory)
• 页中间目录(Page Middle Directory)
• 页表(Page Table)
页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每一个页表项指向一个页框。线性地址因此被分成五个部分。图中没有显示位数,因为每一部分的大小与具体的计算机体系结构有关。
#include <linux/module.h> #include <asm/pgtable.h> #include <linux/version.h> #include <asm/page.h> #include <linux/gfp.h> #include <linux/page-flags.h> #include <linux/sched.h>//find_task_by_vpid #include <linux/mm.h>//find_vma MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("CONVERT USER VIRTUAL ADDRESS TO PHYADDRESS"); static int pid; static unsigned long va; module_param(pid,int,0644); module_param(va,ulong,0644); static int find_pgd_init(void) { unsigned long pa=0; struct task_struct *pcb_tmp=NULL; pgd_t *pgd_tmp=NULL; pud_t *pud_tmp=NULL; pmd_t *pmd_tmp=NULL; pte_t *pte_tmp=NULL; printk(KERN_ALERT "test:va=0x%lx,pid=%d.\n",va,pid); rcu_read_lock(); if( !( pcb_tmp = pid_task(find_vpid(pid), PIDTYPE_PID) ) ) { rcu_read_unlock(); printk(KERN_ALERT "Can't find the task %d.\n",pid); return 0; } rcu_read_unlock(); printk("The page index_table address = 0x%p\n\n",pcb_tmp->mm->pgd); printk(KERN_ALERT "pgd=0x%p\n",pcb_tmp->mm->pgd); if(!find_vma(pcb_tmp->mm,va)) { printk(KERN_ALERT "virt_addr 0x%lx not available.\n",va); return 0; } pgd_tmp=pgd_offset(pcb_tmp->mm,va); printk(KERN_ALERT "pgd_tmp=0x%p\n",pgd_tmp); printk(KERN_ALERT "pgd_val(*pgd_tmp)=0x%lx\n\n",pgd_val(*pgd_tmp)); if(pgd_none(*pgd_tmp)) { printk(KERN_ALERT "Not mapped in pgd.\n"); return 0; } pud_tmp=pud_offset(pgd_tmp,va); pmd_tmp=pmd_offset(pud_tmp,va); pte_tmp=pte_offset_kernel(pmd_tmp,va); if(pte_none(*pte_tmp)) { printk(KERN_ALERT "Not mapped in pte.\n"); return 0; } if(!pte_present(*pte_tmp)) { printk(KERN_ALERT "pte not in RAM,maybe swaped.\n"); return 0; } pa=(pte_val(*pte_tmp)&PAGE_MASK)|(va&~PAGE_MASK); printk(KERN_ALERT "Virtual address: 0x%lx in RAM is 0x%lx.\n",va,pa); printk(KERN_ALERT "Part content in 0x%lx is 0x%lx.\n",pa,*(unsigned long*)((char *)pa+PAGE_OFFSET)); int i; printk("some content:\n"); for(i=0;i<40;i=i+4) { printk("%lx\n",*(unsigned long*)((char*)pa+PAGE_OFFSET+i)); } return 0; } static void find_pgd_exit(void) { printk(KERN_ALERT "Goodbye.\n"); } module_init(find_pgd_init); module_exit(find_pgd_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("weiwei"); MODULE_DESCRIPTION("GET WESSAGE");
- 页全局目录(Page Global Directory)
- 页上级目录(Page Upper Directory)
- 页中间目录(Page Middle Directory)
- 页表(Page Table)
页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每一个页表项指向一个页框。线性地址因此被分成五个部分。
修改权限的思路,查看页表的读写,u/s的值,然后通过相关的设置函数进行修改
函数名称 |
说明 |
pte_user( ) |
读 User/Supervisor 标志。 |
pte_read( ) |
读 User/Supervisor 标志(表示 80x86 处理器上的页不受读的保护)。 |
pte_write( ) |
读 Read/Write 标志。 |
pte_exec( ) |
读 User/Supervisor 标志( 80x86 处理器上的页不受代码执行的保护)。 |
pte_dirty( ) |
读 Dirty 标志。 |
pte_young( ) |
读 Accessed 标志。 |
pte_file( ) |
读 Dirty 标志(当 Present 标志被清除而 Dirty 标志被设置时,页属于一个非线性磁盘文件映射)。 |
设置页表项中各标志的值的函数:
函数名称 |
说明 |
mk_pte_huge( ) |
设置页表项中的 Page Size 和 Present 标志。 |
pte_wrprotect( ) |
清除 Read/Write 标志。 |
pte_rdprotect( ) |
清除 User/Supervisor 标志。 |
pte_exprotect( ) |
清除 User/Supervisor 标志。 |
pte_mkwrite( ) |
设置 Read/Write 标志。 |
pte_mkread( ) |
设置 User/Supervisor 标志。 |
pte_mkexec( ) |
设置 User/Supervisor 标志。 |
pte_mkclean( ) |
清除 Dirty 标志。 |
pte_mkdirty( ) |
设置 Dirty 标志。 |
pte_mkold( ) |
清除 Accessed 标志(把此页标记为未访问)。 |
pte_mkyoung( ) |
设置 Accessed 标志(把此页标记为访问过)。 |
pte_modify(p,v) |
把页表项 p 的所有访问权限设置为指定的值 v 。 |
ptep_set_wrprotect() |
与 pte_wrprotect( ) 类似,但作用于指向页表项的指针。 |
ptep_set_access_flags( ) |
如果 Dirty 标志被设置为 1 则将页的访问权设置为指定的值,并调用flush_tlb_page() 函数。 |
ptep_mkdirty( ) |
与 pte_mkdirty( ) 类似,但作用于指向页表项的指针。 |
ptep_test_and_clear_dirty( ) |
与 pte_mkclean( ) 类似,但作用于指向页表项的指针并返回 Dirty 标志的旧值。 |
ptep_test_and_clear_young( ) |
与 pte_mkold( ) 类似,但作用于指向页表项的指针并返回 Accessed标志的旧值。 |