基于LKM的进程性能分析论文阅读并实现

一、知识点摘录

基于linux内核模块(LKM)的方法:

​ 它可以被用于实时跟踪任何进程特定的数据。在这种方法中,用户不需要任何单独的工具,只需要内核模块来收集所有基本信息。此外,这种基于模块的技术的能力可以通过加入额外的功能来增强,从而使其成为一个成熟的多进程抽象层,以满足任何高性能嵌入式平台的需求。

Linux性能分析命令:

在这里插入图片描述

Linux实时进程监控实现:

​ 我们的进程监视大致分为两个部分,用户空间数据收集和内核空间模块。字符设备节点(/dev/myNode)用于在这两个单元之间通信。用户空间应用程序打开字符节点并传递适当的请求字符串,以获得进程所需的信息。

二、代码实现

用户空间数据收集系统ucode.c代码如下:

#include <stdio.h>
#include <unistd.h> 
#include <fcntl.h> 
#include <string.h> 

/*可以检索的进程统计信息列表:*/
void showopt ( ) {		
printf ( "  1. process name\n"
		 "  2. group id\n"
		 "  3. parent process\n"
		 "  4. process group leader\n"
		 "  5. child processes created\n"
		 "  6. process memory segments\n"
		 "  7. virtual memory mapping\n"
		 "  8. process priority\n"
		 "  9. process state\n"
		 "  10. cpu used by process\n"
		 "  11. total fault count of process\n"
		 "  12. process start time\n"
		 "  13. process link count \n" 
		 "  14. exit \n");
}

/*如前所述,C源代码的主体请求LKM通过字符设备节点/dev/mynode获取数据。我们将进程id (PID)和请求令牌传递给模块。这个模块检查传递的令牌,并通过传递的相同缓冲区buf将结果传递给用户空间应用程序。在这里,我们没有在写之后使用任何单独的读系统调用,以使其简单和更快。*/
#define MAX 1024
int main () {
	char buf[MAX],opt[3],pid[10];
	long int rt;
	//open device node of LKM
	int fd=open("/dev/myNode",O_RDWR);
	printf("Enter Process PID: ");
	scanf("%s",pid);
	showopt();
	//get option
	while(1){
       		printf(" Enter Option : " );
        	scanf("%s",opt);
		if(strcmp(opt, "14") == 0)       break;
       		 //pass message string to LKM
       		 sprintf( buf, "%s %s",pid,opt);	//sprintf的作用是将一个格式化的字符串输出到一个目的字符串中
		write(fd,buf,strlen(buf));		//write函数把buf中strlen(buf)个字节写入文件描述符fd所指的文档,成功时返回写的字节数,错误时返回-1.
		//print result
		if (read(fd, buf, MAX) >= 0){
			printf ("%s",buf) ;
		}
	}
	close(fd);
	return 0;
}

内核模块代码kcode.c代码如下:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/semaphore.h>
#include <linux/fs.h> 
#include <linux/uaccess.h>
#include <linux/sched/signal.h>

/*在内核代码中,我们首先注册一个动态字符设备驱动程序,并在用户空间中创建设备节点/dev/myNode,用户空间应用程序通过该节点与模块进行通信。*/
static struct class *cl;
static struct cdev *cdev;
dev_t dev;
struct semaphore mysem;
int rpid, opt;
#define MAX 1024
static char mybuff[MAX];

int myopen(struct inode *inode,struct file *file)
{
    //阻止同时访问
    down_interruptible(&mysem);	//这个函数的功能就是获得信号量,如果得不到信号量就睡眠,此时没有信号打断,那么进入睡眠。但是在睡眠过程中可能被信号打断,打断之后返回-EINTR,主要用来进程间的互斥同步。
    return 0;
}

int myrelease(struct inode *inode,struct file *file)
{
    //释放信号量
    up(&mysem);	//up()函数的功能是释放信号量sem,释放信号量后,sem的计数器的值将加1。
    return 0;
}

ssize_t mywrite(struct file *file,const char *buff,size_t count,loff_t *pos)
{
    char *to,*val;
    //接收到复制缓冲区
    copy_from_user(mybuff,buff,count);	//将用户空间的数据拷贝到内核空间,失败返回没有被拷贝的字节数,成功返回0
    //get PID and OPTION passed
    to= &mybuff[0];
    val=strsep(&to," ");	//该函数是用来分解字符串为一组字符串。第一个参数为要分解的字符串,第二个字符串为分隔符字符串。
    rpid=simple_strtol(val,NULL,10);	//将一个字符串转换成unsigend long long型数据。
    val=strsep(&to," ");
    opt= simple_strtol(val,NULL,10);
    return count;
}

/*在内核处理程序mywrite中,我们找到进程的任务结构,它的PID被传递。然后根据从用户空间接收到的选项值查找所需的信息。计算各种进程信息的内核逻辑如下所示。*/
ssize_t myread(struct file *file,char *buff, size_t count, loff_t *pos) {
	struct task_struct *task, *t;
	struct list_head *trav;
	struct vm_area_struct *vma;
	char *c;
    //find the task_struct
    for_each_process(task) {
        //compare process pid
        if(rpid==task->pid)
            break;
    }
    if(rpid == task->pid) {
        switch(opt) {
        case 1: // Finding process name
            count = snprintf(mybuff, MAX, "%s\n", task->comm);
            break;
        case 2: // Finding group id
            count = snprintf(mybuff, MAX, "%d\n", task->tgid);
            break;
        case 3: // Finding parent process
            count = snprintf(mybuff, MAX, "Process:%s with Pid=%d\n",task->parent->comm, task->parent->pid);
            break;
        case 4: // Finding group leader
            count = snprintf(mybuff, MAX, "Process:%s with Pid=%d\n",
                task->group_leader->comm, task->group_leader->pid);
            break;
        case 5: // Finding child processes created
            count = 0;
            list_for_each (trav, &(task->children)) {
                t = list_entry(trav, struct task_struct, sibling);
                count += snprintf(mybuff + count, MAX - count,"%d ", t->pid);
            }
                count += snprintf(mybuff + count, MAX - count, "\n");
                break;
        case 6: // Finding process memory segments
            count = snprintf(mybuff,MAX,
                "Code Segment: 0x%lx - 0x%lx \n"
                "Data Segment: 0x%lx - 0x%lx \n"
                "Heap Segment: 0x%lx \n"
                "Stack Segment: 0x%lx \n",
                task->mm->start_code,
                task->mm->end_code,
                task->mm->start_data,
                task->mm->end_data,
                task->mm->start_brk,
                task->mm->start_stack
            );
            break;
        case 7: // Finding process Virtual memory mapping
            count = snprintf(mybuff, MAX, "Total no of vmas=%d\n",task->mm->map_count);
            for(vma = task->mm->mmap; vma; vma = vma->vm_next) {
                count += snprintf(mybuff + count,MAX - count, "0x%lx - 0x%lx\n",vma->vm_start, vma->vm_end);
            }
            break;
        case 8: // Finding process priority
            count = snprintf(mybuff, MAX,
                "Static Priority(nice)=%d\n"
                "Dynamic Priority=%d\n"
                "Normal Priority=%d\n",
                task->prio, task->static_prio, task->normal_prio
            );
            break;
        case 9: // Finding process scheduling policy
            switch(task->policy) {
                case SCHED_NORMAL:
                    c = "SCHED_NORMAL\n";
                    break;
                case SCHED_FIFO:
                    c = "SCHED_FIFO\n";
                    break;
                case SCHED_RR:
                    c = "SCHED_RR\n";
                    break;
                case SCHED_BATCH:
                    c = "SCHED_BATCH\n";
                    break;
                case SCHED_IDLE:
                    c = "SCHED_IDLE\n";
                    break;
                case SCHED_DEADLINE:
                    c = "SCHED_DEADLINE\n";
                    break;
            }
            count = snprintf(mybuff, MAX, "%s", c);
            break;
        case 10: // Finding cpu used
            count = snprintf(mybuff, MAX, "CPU used=%d\n",task_cpu(task));
            break;
        case 11: // Finding major and minor faults
            count = snprintf(mybuff, MAX,
                "No of Major faults=%ld\n"
                "No of Minor faults=%ld\n",
                task->maj_flt, task->min_flt
            );
            break;
        case 12: // Finding process start time
		/*经查询man手册,start_time的单位为时钟滴答即毫秒,经以下代码计算算出来的时间和开始时间正好吻合*/
			count = snprintf(mybuff, MAX,
                "TIME: %.2lld:%.2lld:%.2lld \r\n",
                task->start_time/1000/3600%24,
                task->start_time/1000/60%60,
                task->start_time/1000%60
            );
            break;
        case 13: // Finding process link count
            count = snprintf(mybuff, MAX, "Link count=%d\n",
                refcount_read(&(task->thread_pid->count)));
            break;
        default:
            count = 20;
            strncpy(mybuff, "option not exist\n", count);
            printk("%s\n", mybuff);
            break;
        }
    } else {
        count = 20;
        strncpy(mybuff, "PID not exist\n", count);
        printk("%s\n", mybuff);
    }
    copy_to_user(buff, mybuff, count+1);//将内核空间中的数据拷贝到用户空间
    return count;
}

/*所有对驱动模块的访问,如打开、写入、关闭/dev/myNode,都是通过内核接口结构文件操作进行的。信号量mysem用于单例访问模块。*/
struct file_operations fops ={
    .owner=THIS_MODULE,
    .open=myopen,
    .release=myrelease,
    .write= mywrite,
    .read = myread,
};

static int start(void)
{
    printk(KERN_ALERT "Initializing Module");	//KERN_ALERT为消息打印级别
    //get major no
    alloc_chrdev_region(&dev,0,1,"myNode") ;	//该函数是用来向内核申请主设备号时用的,第一个参数:输出型参数,获得一个分配到的设备号
    // regisiter device
    cdev =cdev_alloc();	//申请一个字符设备
    cdev->ops=&fops;	//文件操作
    cdev->owner=THIS_MODULE;
    cdev_add(cdev,dev,1);	//添加一个字符设备到操作系统
    // 创建设备节点
    cl=class_create(THIS_MODULE,"myNode");	//此函数的执行效果就是创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的
    device_create(cl,NULL,cdev->dev,NULL,"myNode");	//该函数进行设备文件创建
    //initialize semaphore
    sema_init(&mysem,1);		//初始化信号量,将信号量计数器值设置1。
    return 0;
}

/*这里使用信号量(mysem)实现从同步访问到模块的同步。根据设计,该模块一次服务单个请求,而其他请求必须等待才能获得访问。*/
static void leave(void)
{
	printk(KERN_ALERT "Leaving Module\n") ;
 	device_destroy(cl,dev);
	class_destroy(cl);
	cdev_del(cdev);
	unregister_chrdev_region(cdev->dev,1);
}
module_init(start);
module_exit(leave);
MODULE_LICENSE("GPL");

Makefile代码如下:

obj-m += kcode.o

CURRENT_PATH:=$(shell pwd)	#模块所在的当前所在路径
LINUX_KERNEL:=$(shell uname -r)	#linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)	#linux内核的当前版本源码路径

all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules	#编译模块
#				内核的路径		  当前目录编译完放哪   表明编译的是内核模块

clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean	#清理模块

运行结果:

在这里插入图片描述

在这里插入图片描述

程序的理解:

​ 该程序中涉及到了很多有关设备操作的函数,仅能对该函数的功能进行理解,具体实现有点不太明白,对于信号量方面也很是陌生,但极大的锻炼了我解决报错和修改代码的能力。

​ 在我看来,这个程序的具体实现过程为:在内核中创建了一个字符设备,该设备可以写入用户端程序传入到/dev/myNode中的数据,然后读出相应的数据到/dev/myNode,最后由用户端程序进行输出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值