杭电操作系统实验二---linux内核模块编译(完整实验报告)

一 题目介绍

题目内容:

1)当模块被载入内核时会向系统日志文件中写入“hello,world”;当被卸载时,会向系统写入“goodbye”。

2)设计一个模块,要求列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID

3)设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号及进程状态。

二 实验思路

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第1张图片杭电操作系统实验二---linux内核模块编译(完整实验报告)_第2张图片

三 遇到问题及解决方法

一开始使用for_each_process(p)来找到与用户输入pid相匹配的进程,并且通过判断state是否为空来判断pid是否存在,虽然最后结果正确,但是make的时候对p->state==NULL中的”==”报错

使用实验一中pid_task(find_get_pid(pid),PIDTYPE_PID);来找到task_struct,然后通过判断p是否是否指向空来判断pid是否存在。

四 核心代码及实验结果展示

  1. 实验一

在home文件夹中创建hello文件夹,新建myhello.c和Makefile

myhello.c

#include

#include

#include

static int  hello_init(void)

{

        printk(KERN_ALERT"hello,word\n");

        return 0;

}

static void  hello_exit(void)

{

        printk(KERN_ALERT"goodbye\n");

}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");

 

Makefile

obj-m :=hello.o

hello-objs:=myhello.o

KDIR :=/lib/modules/$(shell uname -r)/build

PWD :=$(shell pwd)

default:

 /*-C指定内核源码目录,M指定模块源码所在目录*/

make -C $(KDIR) M=$(PWD) modules

clean:

        make -C $(KDIR) M=$(PWD) clean

进入hello文件夹,输入make

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第3张图片

输入sudo insmod ./hello.ko加载模块命令

输入lsmod查看模块是否被编译

输入dmesg查看是否在日志输出

输入sudo rmmod hello卸载模块

输入dmesg查看结果

  1. 实验二

在home文件中新建process文件夹,新建module1.c和Makefile文件

module1.c

#include 

#include 

#include 

#include 

#include 

static int hello_init(void)

{

    struct task_struct *p;

/*进程描述符指针p*/

p=&init_task;

/*p指向init_task

内核中init_task变量就是是进程0(调度程序)使用的进程描述符,也是Linux系统中第一个进程描述符,贯穿于整个Linux系统的初始化过程,所以不可能不存在*/

    printk(KERN_ALERT"名称\t进程号\t状态\t优先级\t父进程号\t");

/*

#define KERN_EMERG KERN_SOH "0"     /*系统无法使用*/

#define KERN_ALERT   KERN_SOH "1"     /*必须马上输出*/

#define KERN_CRIT      KERN_SOH "2"     /*临界*/

#define KERN_ERR KERN_SOH "3"     /*错误*/

#define KERN_WARNING    KERN_SOH "4"     /*警告*/

#define KERN_NOTICE KERN_SOH "5"     /*正常但重要*/

#define KERN_INFO     KERN_SOH "6"     /*信息*/

#define KERN_DEBUG  KERN_SOH "7"     /*调试级别信息*/

*/

    for_each_process(p)

/* for_each_process是宏循环控制语句,内核开发者可它扫描整个进程链表。

#define for_each_process(p) \

       for (p = &init_task ; (p = next_task(p)) != &init_task ; )

一直循环下去,直到再次碰上init_task为止,因为循环链表*/

    {

        if(p->mm == NULL){

/* mm是指向被映射的用户地址空间的内存管理结构的指针

       struct mm_struct *mm, *active_mm;

#ifdef CONFIG_COMPAT_BRK

       unsigned brk_randomized:1;

#endif

#if defined(SPLIT_RSS_COUNTING)

       struct task_rss_stat rss_stat;

#endif

内核进程始终在内核空间运行,从来不切换到用户空间去,所以没有用户态地址空间,所以它们的mm成员总是为NULL*/

            printk(KERN_ALERT"%s\t%d\t%ld\t%d\n",p->comm,p->pid, p->state,p->normal_prio,p->parent->pid);

/*

struct task_struct {

volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */

int prio, static_prio, normal_prio;

pid_t pid;

char comm[TASK_COMM_LEN]; /* executable name excluding path */

…};

相应的程序名char common[TASK_COMM_LEN]

进程标识符pid_t pid

进程状态volatile long state

常规静态优先级int normal_prio

*/

        }

    }

    return 0;

}

// 清理函数

static void hello_exit(void)

{

    printk(KERN_ALERT"goodbye~\n");

}



// 函数注册

module_init(hello_init); 

module_exit(hello_exit); 



// 模块许可申明

MODULE_LICENSE("GPL");

 

Makefile

obj-m:=module1.o

KDIR:= /lib/modules/$(shell uname -r)/build

PWD:= $(shell pwd)

all:default test

default:

        $(MAKE) -C $(KDIR) M=$(PWD) modules 

clean:

        @sudo rmmod module1

        $(MAKE) -C $(KDIR) M=$(PWD) clean

        @dmesg | tail -10

test:

/*清除原有信息*/

@sudo dmesg -C

        @sudo insmod module1.ko

            @dmesg

在cd process进入process文件夹

输入make

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第4张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第5张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第6张图片

 

 

检验:输入ps -u root查看root用户的进程

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第7张图片

经检查与结果一致

输入make clean

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第8张图片

  1. 实验三
#include 

#include 

#include 

#include 

#include 

#include 

/*在使用宏之前,先声明无符号变量*/

static unsigned int pid;

/*module_param(name,type,perm)

name既是用户看到的参数名,有事模块内接收参数的变量

type表示数据的类型,例如:int,double,bool,unit...

perm对于全局可读的变量为0444,对于根目录可写为0644。

*/

module_param(pid, uint, 0644);

static int list_init(void)

{

    /*parent 指向其父进程

    children 表示当前进程的子进程

    sibling 表示当前进程的链表*/

    /*定义类型为结构体的指针*/

    struct task_struct *p;

    struct task_struct *parent;

    struct task_struct *children;

    struct task_struct *sibling;

    struct list_head *list;

    p=NULL;

/*根据进程号pid得到进程描述符struct pid

struct pid *find_get_pid(pid_t nr)

{

struct pid *pid;

rcu_read_lock();

/*读端临界区,阻塞写者*/

pid = get_pid(find_vpid(nr));

/*find_vpid(nr)根据nr也就是namespace下的局部pid找到对应的struct pid结构体

get_pid 将struct pid结构体中的count字段(记录被使用的次数)加1 后,返回struct pid*/

rcu_read_unlock();

/*写者可以进行下一步操作*/

return pid;

}

pid_task(struct pid *pid, enum pid_type type) PIDTYPE_PID是enum pid_type type中进程的进程号,返回进程的task_struct */

p=pid_task(find_get_pid(pid),PIDTYPE_PID);

if(p==NULL)

printk(KERN_ALERT"Pid does not exit\n");

 else{

parent=p->parent;

printk(KERN_ALERT"This is parent:\n");

printk(KERN_ALERT"程序名\t\tPID号\t进程状态\t优先级\n");

printk(KERN_ALERT"%-10s\t%5d\t%ld\t\t%d\n",parent->comm, parent->pid, parent->state, parent->prio);

/*

sibling.next指向进程的下一个兄弟进程的进程描述符sibling成员,若其后没有其他兄弟进程,则指向父进程

sibling.prev指向进程的上一个兄弟进程,若其之前没有兄弟进程,则指向父进程

children.next指向父进程的第一个子进程的sibling成员(而不是children成员!)

children.prev指向父进程的最后一个子进程的sibling成员。





*/

printk(KERN_ALERT"This is sibling:");

printk(KERN_ALERT"程序名\t\tPID号\t进程状态\t优先级\n");

/*遍历兄弟进程*/

/*#define list_for_each(pos,head)\

for(pos=(head)->next;pos!=(head);pos=pos->next)

pos是指向list_head的指针,用来具体地对每一个list_head进行操作*/

list_for_each(list,&parent->children)

{

/*

#define list_entry(ptr,type,member)\

container_of(ptr,type,member)

#define container_of(ptr,type,member)({\

const typeof( ((type *)0)->member)*_mptr=(ptr);

(type *)( (char *)_mptr - offsetof(type,member));}

offsetof获得该成员变量基于其包含体地址的偏移量

ptr指向该数据存储在链表中的地址值

type是数据项的类型

member是数据项类型定义中list_head成员的变量名

计算tpye结构体中成员member在结构体中的偏移量,然后用ptr的值减去这个偏移量,就得出type数据结构的首地址*/

sibling=list_entry(list,struct task_struct,sibling);

printk(KERN_ALERT"%-10s\t%5d\t%ld\t\t%d\n", sibling->comm, sibling->pid, sibling->state, sibling->prio);

}

printk(KERN_ALERT"This is children:\n");

printk(KERN_ALERT"程序名\t\tPID号\t进程状态\t优先级\n");

/*遍历子进程*/

list_for_each(list,&p->children)

{

children=list_entry(list,struct task_struct,sibling);

     printk(KERN_ALERT"%-10s\t%5d\t%ld\t\t%d\n", children->comm, children->pid, children->state, children->prio);

}

}

return 0;

}

static void list_exit(void)

{

    printk(KERN_ALERT"goodbye~\n");

}

module_init(list_init);

module_exit(list_exit);

MODULE_LICENSE("GPL");

 

 

 

Makefile

obj-m := list.o

KDIR := /lib/modules/`uname -r`/build

PWD := $(shell pwd)

default:

              sudo make -C $(KDIR) M=$(PWD) modules

@sudo dmesg -C

clean:

              - sudo rmmod list

              sudo make -C $(KDIR) M=$(PWD) clean

 

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第9张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第10张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第11张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第12张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第13张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第14张图片

 

pid不存在

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第15张图片

五 个人实验改进与总结

5.1 个人实验改进

通过遍历所有进程for_each_process(p)检查每个进程的pid是否和用户输入的pid相同,若找到pid相同的进程则输出此进程的父进程、兄弟进程、子进程,然后退出遍历。

5.2 个人实验总结

所有进程都是pid为1的init进程的后代。

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第16张图片

杭电操作系统实验二---linux内核模块编译(完整实验报告)_第17张图片

结构体list_head包含两个指针成员:next,prev。这两个指针成员都是list_head类型,以此构成链表。实际应用中,list_head结构体往往实例化为其他结构体的成员,例如task_struct中的children,sibling。

你可能感兴趣的:(杭电操作系统实验二---linux内核模块编译(完整实验报告))