HDU操作系统课程设计实验二

这篇博客介绍了如何通过Linux内核模块编程获取系统内核线程信息以及构建进程家族树。实验内容包括设计一个模块列出所有内核线程的程序名、PID、状态、优先级和父PID,并实现类似pstree的功能,展示指定PID进程的家族信息。实验中涉及遍历进程链表、父子进程关系和进程状态的输出。


这是一个比较简单、容易的实验,前提条件是对Linux内核的父子进程链表遍历、查找、访问等有一定的了解。注意:实验时可能因为模块代码有问题导致模块无法正常加载,被阻塞在hello_init()函数中,以至于模块无法被rmmod命令卸载。重启虚拟机,模块自动卸载。

一、设计目的

Linux提供的模块机制能动态扩充Linux功能而无需重新编译内核,已经广泛应用在Linux内核的许多功能的实现中。在本实验中将学习模块的基本概念、原理及实现技术,然后利用内核模块编程访问进程的基本信息,加深对进程概念的理解,掌握基本的模块编程技术。

二、内容要求

(1)设计一个模块,要求列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID,输出按列对齐。
(2)设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号及进程状态,同时实现类似pstree的输出。
(3)请根据自身情况,进一步阅读分析程序中用到的相关内核函数的源码实现。

三、实验内容

实验思路

要求一思路图(图片出处不详)
要求二思路图(图片出处不详)

实验过程

  1. 编写模块代码和Makefile文件。
  2. 使用make命令编译模块代码。
  3. 使用insmodmodprobe命令将需要载入的模块以目标代码的形式加载到内核中,将自动调用init_module宏。
  4. 使用lsmod命令查看已载入系统的模块信息。
  5. 使用modinfo命令查看指定的模块信息。
  6. 使用rmmod命令删除指定的模块。

输出按列对齐输出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID

Linux系统中的每个进程都有一个父进程(init进程除外);每个进程还有0个或多个子进程。在进程描述符中parent指针指向其父进程,还有一个名为children的子进程链表(父进程task_struct中的children相当于链表的表头)。
通过for_each_process(task_struct)函数遍历内核进程对齐输出即可。
参考代码(不完整):

static int myallkt_init(void)
{
	struct task_struct *p;
	p = NULL;
	printk(KERN_ALERT"myallkt begin\n%*s程序名%*s进程号%*s进程状态%*s进程优先级%*s父进程进程号\n",14," ",0," ",0," ",0," ",0," ");
	for_each_process(p)
	{
		if(p->mm == NULL)
		{
			printk(KERN_ALERT"%20s %6d %8ld %10d %12d\n",p->comm,p->pid,p->state,p->prio,p->parent->pid);
		}
	}
	printk(KERN_ALERT"\n");
	return 0;
}

参数为某个进程的PID号,类似pstree的输出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号及进程状态

通过list_for_each(list_head*,task_struct*)函数遍历,task_struct中的children指针指向其某个子进程的进程描述符task_struct中children的地址而非直接指向某个子进程的地址,也就是说子进程链表中存放的仅仅是各个task_struct成员children的地址。
我们可以用list_entry(ptr,type,member)这个宏来获取task_struct成员的地址,ptr是指向该数据中list_head成员的指针,type是节点的类型,member是节点类型中list_head成员的变量名。
然后按照树状打印即可。
安装模块时传入参数:

module_param(pid, int ,0644); // module_param()宏来修饰要传入的参数变量
insmod 模块名.ko 变量名=值 // 加载模块时出入参数的命令

参考代码(不完整):

void show_it_children(struct task_struct *p,char fout1[100],int fl,int nps)
{
    struct task_struct *pchildren[500];
    struct list_head *L;
    int i = 0,npc = 0,ml = 0;
    char out[100];
    char fout2[100];

    list_for_each(L,&p->children){
        pchildren[npc++]=list_entry(L,struct task_struct,sibling);
    }
    
    //输出当前进程信息
    sprintf(out,"─%s(pid:%d,state:%ld)",p->comm,p->pid,p->state);
    ml = strlen(out) - 1;
    if(npc)
    {
        if(npc != 1)
            sprintf(fout2,"%s%s─┬─",fout1,out);
        else 
            sprintf(fout2,"%s%s───",fout1,out);
    }
    else
    {
        printk("%s%s\n",fout1,out);
        return ;
    }    
    //输出子进程信息
    if(nps - 1 > 0)
        sprintf(fout1,"%*s│%*s",fl,"",ml,"");
    else
	sprintf(fout1,"%*s",fl + ml + 2,"");
    for(i = 0;i < npc;i++)
    {
        sprintf(out,"%s(pid:%d,state:%ld)",pchildren[i]->comm,pchildren[i]->pid,pchildren[i]->state);
        if(i)
        {
            if(i != npc - 1)
                printk("%s├─%s\n",fout1,out);
            else
                printk("%s└─%s\n",fout1,out);
        }
        else
        {
            printk("%s%s\n",fout2,out);
        }
    }
}

static int mypetree_init(void)
{
    struct task_struct *p;
    struct task_struct *psibling[100];
    struct list_head *L;
    int i = 0,nps = 0,fl = 0,tps = 0;
    char out[100];
    char fout1[100];

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

    list_for_each(L,&p->parent->children){
        psibling[nps++]=list_entry(L,struct task_struct,sibling);
    }

    //输出父进程信息
    if(p->parent==NULL)
        sprintf(out,"无父进程─");
    else
        sprintf(out,"%s(pid:%d,state:%ld)─",p->parent->comm,p->parent->pid,p->parent->state);
    fl = strlen(out) - 2;
    if(nps)
        sprintf(fout1,"%s┬",out);
    else
        sprintf(fout1,"%s─",out);

    show_it_children(p,fout1,fl,nps);

    tps = nps - 1;
    //输出兄弟进程信息
    for(i = 0;i < nps;i++)
    {
        if(psibling[i] != p)
        {
            --tps;
            if(tps)
                sprintf(out,"%*s├─",fl,"");
            else
                sprintf(out,"%*s└─",fl,"");
            show_it_children(psibling[i],out,fl,tps);
        }
    }
    return 0;
}

四、实验核心代码

allkt.c:按列对齐输出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID。
Makefile1:allkt.c的Makefile文件。
pstree.c:设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是类似pstree的输出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号及进程状态。
Makefile2:pstree.c的Makefile文件。
完整实验代码详见:HDU-operation-system-course-design-code/实验二/

实验一 linux 内核编译及添加系统调用 设计目的 Linux 是开源操作系统,用户可以根据自身系统需要裁剪、修改内核,定制出功能更加 合适、运行效率更高的系统,因此,编译 linux 内核是进行内核开发的必要基本功。 在系统中根据需要添加的系统调用是修改内核的一种常用手段,通过本次实验,读 者应理解 linux 系统处理系统调用的流程以及增加系统调用的方法。 内容要求 (1) 添加一个系统调用,实现对指定进程的 nice 值的修改或读取功能,并返回进程最 的 nice 值及优先级 prio。建议调用型为: int mysetnice(pid_t pid, int flag, int nicevalue, void __user * prio, void __user * nice); 参数含义: pid:进程 ID。 flag:若值为 0,表示读取 nice 值;若值为 1,表示修改 nice 值。 Prio、nice:进程当前优先级及 nice 值。 返回值:系统调用成功时返回 0,失败时返回错误码 EFAULT。 (2) 写一个简单的应用程序测试(1)中添加的系统调用。 (3) 若程序中调用了 linux 的内核函数,要求深入阅读相关函数源码。 实验 linux 内核模块编程 设计目的 Linux 提供的模块机制能动态扩充 linux 功能而无需重编译内核,已经广泛应用在 linux 内核的许多功能的实现中。在本实验中将学习模块的基本概念、理及实现技术,然后利 用内核模块编程访问进程的基本信息,从而加深对进程概念的理解、对模块编程技术的掌 握。 内容要求 (1) 设计一个模块,要求列出系统中所有内核线程的程序名、PID 号、进程状态及 进程优先级。 (2) 设计一个带参数模块,其参数为某个进程的 PID 号,该模块功能是列出该 进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID 号。 (3) 请根据自身情况,进一步阅读分析程序中用到的相关内核函数的源码实现。 实验四 linux 进程管理 设计目的 (1) 熟悉 linux 的命令接口。 (2) 通过对 linux 进程控制的相关系统调用的编程应用,进一步加深对进程概念的理解, 明确进程和程序的联系和区别,理解进程并发执行的具体含义。 (3) 通过 Linux 管道通信机制、消息队列通信机制、共享内存通信机制的使用,加深 对不同类型的进程通信方式的理解。 (4) 通过对 linux 的 Posix 信号量的应用,加深对信号量同步机制的理解。 (5)请根据自身情况,进一步阅读分析相关系统调用的内核源码实现。 设计内容 (1)熟悉 linux 常用命令:pwd,useradd,passwd, who, ps, pstree, kill, top, ls, cd, mkdir, rmdir, cp, rm, mv, cat, more, grep 等。 (2) 实现一个模拟的 shell: 编写三个不同的程序 cmd1.c,cmd2.c,cmd3.c,每个程序的功能自定,分别编译成可执 行文件 cmd1,cmd2,cmd3。然后再编写一个程序,模拟 shell 程序的功能,能根据用户输 入的字符串(表示相应的命令名),去为相应的命令创建子进程并让它去执行相应的程序,而父进程则等待子进程结束,然后再等待接收下一条命令。如果接收到的命令为 exit,则父 进程结束;如果接收到的命令是无效命令,则显示“Command not found”,继续等待。 (3) 实现一个管道通信程序: 由父进程创建一个管道,然后再创建 3 个子进程,并由这三个子进程利用管道与父进程 之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息。通信的 具体内容可根据自己的需要随意设计,要求能试验阻塞型读写过程中的各种情况,测试管道 的默认大小,并且要求利用 Posix 信号量机制实现进程间对管道的互斥访问。运行程序,观 察各种情况下,进程实际读写的字节数以及进程阻塞唤醒的情况。 (4) 利用 linux 的消息队列通信机制实现两个线程间的通信: 编写程序创建两个线程:sender 线程和 receive 线程,其中 sender 线程运行函数 sender(), 它创建一个消息队列,然后,循环等待用户通过终端输入一串字符,将这串字符通过消息队 列发送给 receiver 线程,直到用户输入“exit”为止;最后,它向 receiver 线程发送消息“end”, 并且等待 receiver 的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,删除 相关消息队列,结束程序的运行。Receiver 线程运行 rece
### 杭州电子科技大学操作系统课程设计实验三的相关资料 关于杭州电子科技大学(HDU)的操作系统课程设计实验三,可以从以下几个方面获取相关资源和教程: #### 1. **官方实验文档** 官方发布的实验指导书是最权威的参考资料。通常情况下,学校会提供详细的实验说明、目标以及实现方法[^1]。如果无法直接获得纸质版或PDF版本的实验指导书,可以尝试联系授课教师或者查阅学校的在线教学平台。 #### 2. **GitHub开源项目** 许多学生会在完成实验后将其代码上传至GitHub或其他代码托管网站上分享。通过搜索关键词如`HDU-operation-system-course-design-code 实验三`,可以找到一些公开的实验代码实例。需要注意的是,在借鉴他人代码时应确保理解其实现逻辑并遵循学术诚信则。 #### 3. **博客与技术文章** 部分同学可能会撰写个人博客来总结自己的学习经验和技术难点解析。例如,“咸鱼的自留地”中提到了一系列针对HDU操作系统实验室实践内容[^3],虽然具体到某个特定实验可能未完全覆盖,但仍具有一定的参考价值。 #### 4. **论坛讨论区** 像优快云、知乎这样的中文社区经常会有高校学生的提问交流帖。输入类似于“HDU 操作系统 实验三”的查询词即可发现不少前辈们的经验贴士。这些帖子不仅包含了最终解决方案还涉及调试过程中遇到的各种问题及其解决办法。 以下是基于上述提到的信息整理出来的一个简单示例程序片段用于演示Linux进程间通信之管道机制的部分功能: ```c #include <stdio.h> #include <unistd.h> int main() { int fd[2]; pid_t p; char write_message[] = "Hello from parent!"; char read_message[100]; pipe(fd); p = fork(); if (p > 0) { // Parent process writes to the pipe. close(fd[0]); write(fd[1], write_message, sizeof(write_message)); } else { // Child process reads from the pipe. close(fd[1]); read(fd[0], read_message, sizeof(read_message)); printf("%s\n", read_message); } return 0; } ``` 此段代码展示了父子进程中如何利用匿名管道进行数据交换的过程.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值