zoukankan      html  css  js  c++  java
  • linux实践——内核编程 基础模块

    一、内核模块的概念

    Linux模块(module)是一些可以作为独立程序来编译的函数和数据类型的集合。内核模块给我们带来的便利是模块本身并不被编译进内核文件,可在内核运行期间动态的安装或卸载。因为如果将模块编译进内核的话,一是生产的内核文件过大,二是如果要添加或删除某个组件要重新编译整个内核。
    
    Linux模块可以通过静态或动态地加载到内核空间,静态加载是指在内核启动过程中加载;动态加载是指在内核运行的过程中随时加载。我采用的是动态加载的方法。
    
    一个模块被加载到内核中时,它就成为内核代码的一部分,与其他内核代码地位是一样的。模块加载如系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号加到内核符号表中,这样使模块间可进行通信。
    

    二、内核模块的基本结构

    linux内核模块的程序结构有:模块加载函数(必须),模块卸载函数(必须),模块许可证声明(必须),模块参数(可选),模块导出符号(可选),模块作者的等信息声明(可选)。
    
    一个内核模块应该至少包含两个函数。一个“开始”(初始化)的函数被称为init_module(),当内核模块被insmod 加载时被执行,还有一个“结束”(要完成与模块加载函数相反的功能)的函数被称为cleanup_module() ,当内核模块被rmmod 卸载时被执行。实际上,从内核版本2.3.13 开始我们就可以为开始和结束函数起任意的名字了。这可以通过宏module_init()和module_exit()实现,需要注意的地方是函数必须在宏的使用前定义,否则会有编译错误。
    
    模块许可证声明描述内核模块的许可权限,格式为MODULE_LICENSE("Dual BSD/GPL"),Linux可接受的 LICENSE 包括”GPL","GPL v2","GPL and additional rights","Dual BSD/GPL","Dual MPL/GPL","Proprietary"。可以不加,则系统默认。如果不声明 LICENSE ,模块被加载时,将收到内核的警告。
    
    模块参数是“模块被加载的时候可以被传递给模块的值”,它本身对应模块内部的全部变量。 可以使用module_param(参数名,参数类型,读/写权限)为模块定义一个参数。
    
    内核模块可以导出符号(symbol,对应与函数或变量),这样其他模块可以使用本模块中的变量和函数。/proc/kallsyms文件对应这内核符号表,它记录了符号以及符号符号所在的内存地址。 
    
    模块可以使用如下宏导出符号到内核符号表: 
    
        EXPORT_SYMBOL(符号名); 
        EXPORT_SYMBOL_GPL(符合名);    //只是用于GPL许可权模块。 
    
    导出的符合将可以被其他模块使用,使用前声明以下既可以。
    
    模块作者的等信息声明:
    
    	MODULE_AUTOR("作者信息"); 
    	MODULE_DESCRIPTION("模块描述信息"); 
    	MODULE_VERSION("版本信息"); 
    	MODULE_ALIAS("别名信息"); 
    	MODULE_DEVICE_TABLE("设备表信息"); 
    
    对于USB,PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,表示驱动所支持的设备列表。
    

    三、编写内核模块的基本步骤

    1、根据自己的需求编写内核模块源代码

    2、将源代码进行编译,生成.ko文件

    在编译内核模块时需要用到Makefile,
    	obj-m :=*.o
    	PWD := $(shell pwd)
    	KDIR:=/usr/src/linux-headers-4.4.0-21-generic
    	all:
    		make -C $(KDIR) M=$(PWD) modules
    	clean:
    		make -C $(KDIR) M=$(PWD) clean
    
    obj-m:这个变量是指定要编译的模块
    KDIR:这是我们正在运行的操作系统内核编译目录,也就是编译模块需要的环境
    PWD:这是当前工作路径,$(shell )是make的一个内置函数,用来执行shell命令
    注意:要将Makefile文件与四个内核模块源代码放在同一个文件夹中。
    

    3、用insmod命令加载模块

    4、测试内核模块功能

    5、用rmmod命令卸载模块

    四、内核模块编程

    proc模块

    代码

    proc.c代码:

    #include <linux/module.h>  
    #include <linux/init.h>  
    #include <linux/kernel.h>  
    
    #include <linux/fs.h>     // for basic filesystem  
    #include <linux/proc_fs.h>    // for the proc filesystem  
    #include <linux/seq_file.h>   // for sequence files  
    #include <linux/jiffies.h>    // for jiffies  
    #include <linux/slab.h>       // for kzalloc, kfree  
    #include <linux/uaccess.h>    // for copy_from_user  
    
    //static struct task_struct *pcurrent;
    int print_current_task_info(void);
    
    // global var  
    static char *str = NULL;  
      
    // seq_operations -> show  
    static int jif_show(struct seq_file *m, void *v)  
    {
        //seq_printf(m, "current kernel time is %llu
    ", (unsigned long long) get_jiffies_64());  
     
    seq_printf(m, "str is %s
    ", str); 
        return 0; //!! must be 0, or will show nothing T.T  
    }  
      
    // file_operations -> write  
    static ssize_t jif_write(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos)  
    {  
    
        char *tmp = kzalloc((count+1), GFP_KERNEL);  
        if (!tmp)  
            return -ENOMEM;  
      
        //copy_to|from_user(to,from,cnt)  
        if (copy_from_user(tmp, buffer, count)) {  
            kfree(tmp);  
            return -EFAULT;  
        }  
    
        kfree(str);  
        str = tmp;  
      
        return count;  
    }  
      
    // seq_operations -> open  
    static int jif_open(struct inode *inode, struct file *file)  
    {  
        return single_open(file, jif_show, NULL);  
    }  
      
    static const struct file_operations jif_fops =   
    {  
        .owner      = THIS_MODULE,  
        .open       = jif_open,  
        .read       = seq_read,  
        .write      = jif_write,  
        .llseek     = seq_lseek,  
        .release    = single_release,  
    };  
      
    // module init  
    static int __init jif_init(void)  
    {  
        struct proc_dir_entry* jif_file;  
      
        jif_file = proc_create("exp2", 0, NULL, &jif_fops);  
        if (NULL == jif_file)  
        {  
            return -ENOMEM;  
        }  
      
        return 0;  
    }  
      
    // module exit  
    static void __exit jif_exit(void)  
    { 
    printk("******************************************
    ");
        remove_proc_entry("exp2", NULL);  
        kfree(str);
    
    }  
      
    module_init(jif_init);  
    module_exit(jif_exit);  
      
    MODULE_AUTHOR("why");
    MODULE_LICENSE("GPL");  
    

    测试过程及结果:

    1.	make
    

    2.	sudo insmod proc.ko
    
        若要查看模块是否插入成功可以使用"lsmod | grep proc"查看
    

    3.	sudo -i
    	cat /proc/exp2
    
    切换到root用户下,打印/proc/exp2文件中的信息,如图所示:
    

    4.	echo 5312 > /proc/exp2
    
    输入"5312"到/proc/exp2文件中,若输入的信息有空格则需要在信息两侧加双引号
    

    5.	cat /proc/exp2
    
    打印/proc/exp2中信息验证模块编程是否成功
    

    syscall模块(系统调用)

    代码

    syscall代码:

    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/unistd.h>
    #include <linux/sched.h>
    
    MODULE_LICENSE("GPL");
    
    #define SYS_CALL_TABLE_ADDRESS 0xc17ab180	
    #define NUM 23	
    int orig_cr0;  *sys_call_table_my = 0;
    static int (*anything_saved)(void);  
    static int clear_cr0(void)  
    {
    	unsigned int cr0 = 0;
    	unsigned int ret;
    	asm volatile ("movl %%cr0, %%eax":"=a"(cr0));  
    	ret = cr0;
    	cr0 &= 0xfffeffff;  
    	asm volatile ("movl %%eax, %%cr0": :"a"(cr0)); 
    	return ret;
    }
    
    static void setback_cr0(int val)  
    {
    	asm volatile ("movl %%eax, %%cr0": : "a"(val));
    }
    
    asmlinkage long sys_mycall(void)  
    {
    	printk("pid:%d, comm:%s
    ", current->pid, current->comm);
    	return current->pid;
    }
    
    static int __init call_init(void)
    {
    	sys_call_table_my = (unsigned long*)(SYS_CALL_TABLE_ADDRESS);
    	printk("call_init.......
    ");
    	anything_saved = (int (*)(void))(sys_call_table_my[NUM]);  
    	orig_cr0 = clear_cr0();  
    	sys_call_table_my[NUM] =(unsigned long) &sys_mycall;
    	setback_cr0(orig_cr0); 
    	return 0;
    }
    
    static void __exit call_exit(void)
    {
    	printk("call_exit..........
    ");
    	orig_cr0 = clear_cr0();
    	sys_call_table_my[NUM] = (unsigned long)anything_saved; 
    	setback_cr0(orig_cr0);
    }
    
    module_init(call_init);
    module_exit(call_exit);
    
    MODULE_AUTHOR("Why");
    
    MODULE_VERSION("v1.0"); 
    
    MODULE_DESCRIPTION("A module for replace a syscall"); 
    

    syscalltest代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/syscall.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <signal.h>
    int main()
    {
    	unsigned long x=0;
    	x=syscall(23);
    	printf("hello!why!%ld
    ",x);
    	return 0;
    }
    

    测试过程及结果:

    1.	sudo cat /proc/kallsyms | grep sys_call_table
    
    通过得到的本机系统调用表地址修改代码
    

    2.	make
    	gcc syscalltest.c -o syscalltest
    

    3.	sudo insmod syscall.ko
    

    4.	./syscalltest
    

    page模块(内存页表)

    相关知识

    当今,Linux采用了一种同时适用于32位和64位系统的普通分页模型。前面我们看到,两级页表对32位系统来说已经足够了,但64位系统需要更多数量的分页级别。直到2.6.10版本,Linux采用三级分页的模型。从2.6.11版本开始,采用了四级分页模型。
    
    四级分页模型中的4种页表分别被称作:
    	• 页全局目录(Page Global Directory)
    	• 页上级目录(Page Upper Directory)
    	• 页中间目录(Page Middle Directory)
    	• 页表(Page Table)
    
    页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每一个页表项指向一个页框。线性地址因此被分成五个部分。图中没有显示位数,因为每一部分的大小与具体的计算机体系结构有关。
    
    对于没有启用物理地址扩展的32位系统,两级页表已经足够了。从本质上说Linux通过使“页上级目录”位和“页中间目录”位全为0,彻底取消了页上级目录和页中间目录字段。	不过,页上级目录和页中间目录在指针序列中的位置被保留,以便同样的代码在32位系统和64位系统下都能使用。内核为页上级目录和页中间目录保留了一个位置,这是通过把它们的页目录项数设置为1,并把这两个目录项映射到页全局目录的一个合适的目录项而实现的。
    
    启用了物理地址扩展的32 位系统使用了三级页表。Linux的页全局目录对应80x86 的页目录指针表(PDPT),取消了页上级目录,页中间目录对应80x86的页目录,Linux的页表对应80x86的页表。最终,64位系统使用三级还是四级分页取决于硬件对线性地址的位的划分。
    
    pte_t、pmd_t、pud_t和 pgd_t分别描述页表项、页中间目录项、页上级目录和页全局目录项的类型格式。当PAE被激活时它们都是64位的数据类型,否则都是32位数据类型。 pgprot_t是另一个64位(PAE激活时)或32位(PAE禁用时)的数据类型,它表示与一个单独表项相关的保护标志。
    
    五个类型转换宏(__ pte、__ pmd、__ pud、__ pgd和__ pgprot)把一个无符号整数转换成所需的类型。另外的五个类型转换宏(pte_val,pmd_val, pud_val, pgd_val和pgprot_val)执行相反的转换,即把上面提到的四种特殊的类型转换成一个无符号整数。
    

    代码

    page代码:

    #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.
    ",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.
    ",pid);
    	return 0;
    	}
    	rcu_read_unlock();
    
    	printk("The page index_table address = 0x%p
    
    ",pcb_tmp->mm->pgd);
    
    	printk(KERN_ALERT "pgd=0x%p
    ",pcb_tmp->mm->pgd);
    	if(!find_vma(pcb_tmp->mm,va))
    	{
    	printk(KERN_ALERT "virt_addr 0x%lx not available.
    ",va);
    	return 0;
    	}
    	pgd_tmp=pgd_offset(pcb_tmp->mm,va);
    	printk(KERN_ALERT "pgd_tmp=0x%p
    ",pgd_tmp);
    	printk(KERN_ALERT "pgd_val(*pgd_tmp)=0x%lx
    
    ",pgd_val(*pgd_tmp));
       	 if(pgd_none(*pgd_tmp))
    	{
     	   printk(KERN_ALERT "Not mapped in pgd.
    ");
     	   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.
    ");
    	return 0;
    	}
    	if(!pte_present(*pte_tmp))
    	{
       	 	printk(KERN_ALERT "pte not in RAM,maybe swaped.
    ");
    		return 0;
    	}
    	pa=(pte_val(*pte_tmp)&PAGE_MASK)|(va&~PAGE_MASK);
    	printk(KERN_ALERT "Virtual address: 0x%lx in RAM is 0x%lx.
    ",va,pa);
    	printk(KERN_ALERT "Part content in 0x%lx is 0x%lx.
    ",pa,*(unsigned long*)((char *)pa+PAGE_OFFSET));
    	int i;
    	printk("some content:
    ");
    	for(i=0;i<40;i=i+4)
    	{
    		printk("%lx
    ",*(unsigned long*)((char*)pa+PAGE_OFFSET+i));
    	}	
    return 0;
    }
    
    static void find_pgd_exit(void)
    {
    	printk(KERN_ALERT "Goodbye.
    ");
    }
    
    module_init(find_pgd_init);
    module_exit(find_pgd_exit);
    
    MODULE_LICENSE("GPL");
    
    MODULE_AUTHOR("Why");
    
    MODULE_DESCRIPTION("GET WESSAGE");
    

    测试过程及结果:

    1.	make
    

    2.	gedit page.c
    	ps -ef | grep gedit
    	objdump -d /usr/bin/gedit | more
    
    使用一个我们想要查看的软件,查看pid,查看进程的入口地址。
    

        使用计算机计算出地址的十进制值
    

    3.	sudo insmod page.ko pid=1111 va=134514700
    

    4.	dmesg | tail -n 20
    

  • 相关阅读:
    SQL的高级属性-
    查询
    SQL语句
    CSS的创建样式
    表单部分属性值
    HTML的语法和基本标签
    网页制作与HTML基本结构
    小程序button 去边框
    关于axios跨域带cookie
    Uncaught Error: code length overflow. (1604>1056)
  • 原文地址:https://www.cnblogs.com/whyfarrell/p/5521620.html
Copyright © 2011-2022 走看看