HITOS_LAB5 进程运行轨迹的跟踪与统计

news2024/11/27 21:05:35

5. 进程运行轨迹的跟踪与统计

5.1. 实验目的

  • 掌握 Linux 下的多进程编程技术;
  • 通过对进程运行轨迹的跟踪来形象化进程的概念;
  • 在进程运行轨迹跟踪的基础上进行相应的数据统计,从而能对进程调度算法进行实际的量化评价, 更进一步加深对调度和调度算法的理解,获得能在实际操作系统上对调度算法进行实验数据对比的直接经验。

5.2. 实验内容

编写样本程序

process.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>

#define HZ	100

void cpuio_bound(int last, int cpu_time, int io_time);

int main(int argc, char * argv[])
{
	pid_t fd_1, fd_2;
	int fd;
	
	printf("The Parent Pid = [%d].\n", getpid());

	fd_1 = fork();
	if(fd_1 == 0)
	{
		printf("[%d] Is Running Now.\n", getpid());
		cpuio_bound(10, 1, 0);
		exit(0);
	}
	fd_2 = fork();
	if(fd_2 == 0)
	{
		printf("[%d] Is Running Now.\n", getpid());
		cpuio_bound(10, 1, 0);
		exit(0);
	}
	fd = wait(NULL);
	printf("[%d] Have Exited.\n",fd);

	fd = wait(NULL);
	printf("[%d] Have Exited.\n",fd);

	return 0;
}

void cpuio_bound(int last, int cpu_time, int io_time)
{
	struct tms start_time, current_time;
	clock_t utime, stime;
	int sleep_time;

	while (last > 0)
	{
		times(&start_time);
		do
		{
			times(&current_time);
			utime = current_time.tms_utime - start_time.tms_utime;
			stime = current_time.tms_stime - start_time.tms_stime;
		} while ( ( (utime + stime) / HZ )  < cpu_time );
		last -= cpu_time;

		if (last <= 0 )
			break;
		sleep_time=0;
		while (sleep_time < io_time)
		{
			sleep(1);
			sleep_time++;
		}
		last -= sleep_time;
	}
}

跟踪进程运行轨迹

首先修改init文件夹下的main.c中的main()函数,应当在内核启动时就打开log文件。

在这里插入图片描述

打开log文件的参数指定为只写模式,如果文件已经存在,则清空其现有内容。文件权限设置为所有人可读可写。
kernel/printk.c中添加fprintk函数。

#include <stdarg.h>
#include <stddef.h>
#include <linux/kernel.h>
#include "linux/sched.h"
#include "sys/stat.h"

static char logbuf[1024];
static char buf[1024];
extern int vsprintf(char * buf, const char * fmt, va_list args);

int printk(const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	i=vsprintf(buf,fmt,args);
	va_end(args);
	__asm__("push %%fs\n\t"
		"push %%ds\n\t"
		"pop %%fs\n\t"
		"pushl %0\n\t"
		"pushl $buf\n\t"
		"pushl $0\n\t"
		"call tty_write\n\t"
		"addl $8,%%esp\n\t"
		"popl %0\n\t"
		"pop %%fs"
		::"r" (i):"ax","cx","dx");
	return i;
}

int fprintk(int fd, const char *fmt, ...)
{
    va_list args;
    int count;
    struct file * file;
    struct m_inode * inode;
va_start(args, fmt);
    count=vsprintf(logbuf, fmt, args);
    va_end(args);
    if (fd < 3)
    {
        __asm__("push %%fs\n\t"
            "push %%ds\n\t"
            "pop %%fs\n\t"
            "pushl %0\n\t"
            "pushl $logbuf\n\t"
            "pushl %1\n\t"
            "call sys_write\n\t"
            "addl $8,%%esp\n\t"
            "popl %0\n\t"
            "pop %%fs"
            ::"r" (count),"r" (fd):"ax","cx","dx");
    }
    else
    {
        if (!(file=task[0]->filp[fd]))
            return 0;
        inode=file->f_inode;
__asm__("push %%fs\n\t"
            "push %%ds\n\t"
            "pop %%fs\n\t"
            "pushl %0\n\t"
            "pushl $logbuf\n\t"
            "pushl %1\n\t"
            "pushl %2\n\t"
            "call file_write\n\t"
            "addl $12,%%esp\n\t"
            "popl %0\n\t"
            "pop %%fs"
            ::"r" (count),"r" (file),"r" (inode):"ax","cx","dx");
    }
    return count;
}

jiffies

jiffieskernel/sched.c文件中定义为一个全局变量:

long volatile jiffies=0;

其记录了从系统开机到当前时间的时钟中断发生次数,也被称为“滴答数”。

kernel/sched.c文件中的sched_init()函数中,时钟中断处理函数被设置为:

set_intr_gate(0x20,&timer_interrupt);

这表明jiffies表示了从系统开机时到目前为止的时钟中断次数,即“滴答数”。

此外,在sched_init()函数中,以下代码用于设置每次时钟中断的间隔,即LATCH

outb_p(0x36, 0x43);
outb_p(LATCH&0xff, 0x40);
outb_p(LATCH>>8, 0x40);

三条语句的目的是配置8253定时芯片的工作模式,并设置时钟中断的触发频率。其中,LATCH是在kernel/sched.c文件中定义的一个宏:

kernel/sched.c

#define LATCH  (1193180/HZ)

include/linux/sched.h

#define HZ 100

寻找状态切换点

需要在所有发生进程状态切换的代码点添加适当的代码,以记录进程状态变化的情况并输出到log文件中。

总体而言,Linux 0.11支持四种主要的进程状态转移:从就绪到运行、从运行到就绪、从运行到睡眠以及从睡眠到就绪。此外,还存在新建和退出两种情况。其中,就绪到运行的状态转移通过schedule()函数实现(该函数同时涵盖调度算法);运行到睡眠的转移则依赖于sleep_on()interruptible_sleep_on(),以及进程主动休眠的系统调用如sys_pause()sys_waitpid();而从睡眠到就绪的转移则依赖于wake_up()。通过在这些函数的适当位置插入处理语句,可以实现对进程运行轨迹的全面跟踪。

kernel/fork.c文件中的copy_process()函数中,修改如下,以在新建态N时输出新建进程信息到文件:

	p->start_time = jiffies;
	fprintk(3, "%ld\t%c\t%ld\n", p->pid, 'N', jiffies);

kernel/sched.c

schedule()函数:

if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
	(*p)->state==TASK_INTERRUPTIBLE)
	{
		(*p)->state=TASK_RUNNING;
		fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);	
	}

由于中断信号影响,由可中断的阻塞态W变为就绪态J

	if(task[next]->pid != current->pid)
	{
		if(current->state==TASK_RUNNING)
			fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'J', jiffies);

		fprintk(3, "%ld\t%c\t%ld\n", task[next]->pid, 'R', jiffies);
	}
	switch_to(next);

sys_pause()函数:

int sys_pause(void)
{
	if(current->state!=TASK_INTERRUPTIBLE )
		fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
	current->state = TASK_INTERRUPTIBLE;
	schedule();
	return 0;
}

sleep_on()函数:

void sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	tmp = *p;
	*p = current;
	current->state = TASK_UNINTERRUPTIBLE;
	fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);		

	schedule();
	if (tmp)
		tmp->state=0;
	fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies);		
}

interruptible_sleep_on()函数:

void interruptible_sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	tmp=*p;
	*p=current;
repeat:	current->state = TASK_INTERRUPTIBLE;
		fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
	schedule();
	if (*p && *p != current) {
		(**p).state=0;
		fprintk(3, "%ld\t%c\t%ld\n", (**p).pid, 'J', jiffies);
		goto repeat;
	}
	*p=NULL;
	if (tmp)
		tmp->state=0;
	fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies);
}

wake_up()函数:

void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;
		fprintk(3, "%ld\t%c\t%ld\n", (**p).pid, 'J', jiffies);	
		*p=NULL;
	}
}

修改kernel/exit.c

do_exit()函数:

fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'E', jiffies);

sys_waitpid()函数:

fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
schedule();

Linux-0.11

make all
sudo ./mount-hdc
sudo umount hdc
./run

在这里插入图片描述
日志文件process.log

在这里插入图片描述

统计进程时间

chmod +x stat_log.py
./stat_log.py process.log 12 13 14 15 -g | more

在这里插入图片描述

修改linux-0.11进程调度的时间片

Linux 0.11所采用的调度算法是一种综合考虑进程优先级并具有动态反馈机制,能够调整时间片的轮转调度算法。该算法为每个进程分配一个称为时间片的时间段,即该进程被允许运行的时间。如果在时间片结束时进程仍在运行,CPU将会被剥夺,并分配给另一个进程;而如果进程在时间片结束前阻塞或结束,则CPU会立即进行切换。调度程序的主要任务是维护一个就绪进程列表,当进程用完它的时间片后,它将被移到队列的末尾。那么综合考虑进程优先级又是什么呢?这意味着一个进程在阻塞队列中停留的时间越长,其优先级就越高,因此下次将被分配更大的时间片。

进程之间的切换是需要时间的。如果时间片设定得太小,将导致频繁的进程切换,从而浪费大量时间在进程切换上,影响系统效率;相反,如果时间片设定得足够大,虽然不会浪费时间在进程切换上,但会降低系统的利用率,且可能对用户交互性产生不良影响。因此,时间片的大小需要在保持CPU利用率和用户交互性之间取得平衡。

为了调整每个进程的时间片,可以通过修改INIT_TASK宏中的counter来实现。在这里,counter代表进程的时间片。通过增加它,可以延长进程被分配CPU的时间。下图中,平均等待时间以及平均完成时间随着时间片的切片数增多而减少 。

在这里插入图片描述

5.3. 实验报告

  1. 结合自己的体会,谈谈从程序设计者的角度看,单进程编程和多进程编程最大的区别是什么?

在单进程编程中,程序是按照顺序执行的,一个任务完成后才会执行下一个任务。这种方式不涉及到多个任务之间的切换和数据保存,但是在等待I/O时,CPU可能处于空闲状态,导致CPU利用率相对较低。

相比之下,多进程编程允许多个进程交替执行。当一个进程等待I/O时,系统可以切换到另一个就绪的进程执行,从而提高了CPU的利用率。然而,多进程编程涉及到进程切换时的数据保存和复杂的调度机制,这可能导致一些额外的开销和复杂性。

执行方式

  • 单进程编程: 顺序执行,程序从上到下逐行执行。一个任务的执行完成后,才会执行下一个任务。
  • 多进程编程: 并发执行,多个进程可以同时运行。进程之间可以交替执行,提高系统的响应速度和资源利用率。

数据是否同步

  • 单进程编程: 数据是同步的,因为在单进程中修改数据会直接影响整个进程的状态。
  • 多进程编程: 数据是异步的,因为每个进程有自己的内存空间,进程之间的数据修改不会直接影响其他进程的数据。

CPU利用率

  • 单进程编程: CPU利用率相对较低。当进程在等待I/O操作时,CPU处于空闲状态。
  • 多进程编程: CPU利用率相对较高。当一个进程在等待I/O时,其他进程可以继续执行,充分利用了CPU资源。

适用场景

  • 单进程编程: 适用于简单的、线性的任务,不涉及多任务协同工作的场景。
  • 多进程编程: 适用于需要同时处理多个任务、有并发需求的场景,例如服务器程序、并行计算等。

2.你是如何修改时间片的?仅针对样本程序建立的进程,在修改时间片前后, log 文件的统计结果(不包括Graphic)都是什么样?结合你的修改分析一下为什么会这样变化,或者为什么没变化?

通过在sched.h文件中的INIT_TASK宏进行时间片的修改。观察到在样本程序建立的进程中,随着时间片的较大幅度修改,并没有引起log文件统计结果的显著变化。

  • 系统处理能力不变: 关键在于系统处理的进程数量并没有发生改变。即使时间片较大,系统在单位时间内仍能处理相同数量的进程。这导致了吞吐量在一定范围内没有明显的变化。

  • 平均等待时间和平均完成时间的变化: 在一定范围内,随着时间片的增大,平均等待时间和平均完成时间呈下降趋势。这是因为在较小的时间片情况下,CPU花费更多时间在调度切换上,导致平均等待时间增加。随着时间片的增大,这种等待时间减小。然而,当时间片足够大时,进程调度可能变得类似于先来先服务(FCFS),平均等待时间和平均完成时间趋于稳定。

  • 调度策略影响: 随着时间片的修改,RR轮转调度逐渐转变为FCFS先来先服务。这导致了吞吐量的相对稳定性,因为在单位时间内完成的进程数量受到系统处理能力的限制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1286815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Transformer的U型医学图像分割网络综述

基于Transformer的U型医学图像分割网络综述 论文来自 计算机应用 2023 摘 要&#xff1a;目前&#xff0c;医学图像分割模型广泛采用基于全卷积网络&#xff08;FCN&#xff09;的U型网络&#xff08;U-Net&#xff09;作为骨干网&#xff0c;但卷积神经网络&#xff08;CNN&am…

十三、FreeRTOS之FreeRTOS时间管理

本节主要介绍以下内容&#xff1a; 1&#xff0c;延时函数介绍&#xff08;了解&#xff09; 2&#xff0c;延时函数解析&#xff08;熟悉&#xff09; 3&#xff0c;延时函数演示实验&#xff08;掌握&#xff09; 4&#xff0c;课堂总结&#xff08;掌握&#xff09; 一、…

【Transformer论文精读系列】(一)如何理解Transformer里的注意力机制?

论文&#xff1a;Attention Is All You Need 参考李沐老师的讲解视频&#xff1a; Transformer论文逐段精读【论文精读】_哔哩哔哩_bilibili 其他参考&#xff1a; 超强动画&#xff0c;一步一步深入浅出解释Transformer原理&#xff01;_哔哩哔哩_bilibili Transformer论文逐段…

2023年【A特种设备相关管理(锅炉压力容器压力管道)】考试内容及A特种设备相关管理(锅炉压力容器压力管道)复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 A特种设备相关管理&#xff08;锅炉压力容器压力管道&#xff09;考试内容根据新A特种设备相关管理&#xff08;锅炉压力容器压力管道&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将A特种设备相关管理…

1970-2022年中国省级国家级开发区数据集

1970-2022年Z国省级国家级开发区数据集 1、时间&#xff1a;1970-2022年 2、指标&#xff1a;单位名称、所属区域、所属省份、所属级别、开发区类型、关注热度、成立时间、核准面积、主导产业、地址、联系电话、经纬度 3、范围&#xff1a;2781个开发区 4、来源&#xff1a…

机器学习---朴素贝叶斯分类器的实现(对文本进行侮辱性言论和非侮辱性言论的分类)

1. loadDataSet函数 import numpy as np# 构造loadDataSet函数用于生成实验样本 def loadDataSet(): postingList[[my, dog, has, flea, problems, help, please],[maybe, not, take, him, to, dog, park, stupid],[my, dalmation, is, so, cute, I, love, him],[stop, postin…

2005-2021年地级市绿色发展注意力数据(根据政府报告文本词频统计)

2005-2021年地级市绿色发展注意力数据&#xff08;根据政府报告文本词频统计&#xff09; 1、时间&#xff1a;2005-2021年 2、指标&#xff1a;省、市、年份、一级指标、关键词、关键词词频、总词频 3、范围&#xff1a;270个地级市 4、来源&#xff1a;地级市政府工作报告…

卡通渲染总结《二》

关于技术的方面&#xff0c;一方面就是其轮廓边缘检测&#xff1a; 主要的方法可以被分为基于图片空间和对象空间&#xff0c;对象空间比图片空间会多一些立体坐标位置的信息。 轮廓类型分类 首先我们顶一下轮廓是什么&#xff0c;从一个视角看去如果一条边相邻的两个面其恰…

python自动化测试之破解滑动验证码

在Web自动化测试的过程中&#xff0c;经常会被登录的验证码给卡住&#xff0c;不知道如何去通过验证码的验证。 一般的情况下遇到验证码我们可以都可以找开发去帮忙解决&#xff0c;关闭验证码&#xff0c;或者给一个万能的验证码&#xff01; 那么如果开发不提供帮助的话&…

金蝶云星空单据编辑界面,不允许批量填充操作

文章目录 金蝶云星空单据编辑界面&#xff0c;不允许批量填充操作案例演示开发设计测试 金蝶云星空单据编辑界面&#xff0c;不允许批量填充操作 案例演示 售后单&#xff0c;明细信息单据体&#xff0c;物料编码字段禁止批量填充。 开发设计 编写表单插件&#xff0c;在Be…

【C++】POCO学习总结(九):网络

【C】郭老二博文之&#xff1a;C目录 1、Poco::Net::IPAddress IP地址 Poco::Net::IPAddress类存储IPv4或IPv6主机地址。 Poco::Net::IPAddress可以从字符串解析&#xff0c;也可以格式化为字符串。支持IPv4格式(d.d.d.d)和IPv6格式(x: x: x: x: x: x: x: x)。 常用函数&…

行业内卷严重到什么程度了?

一.内卷现状 最近大家都吐槽找工作难&#xff0c;确实很难。 不得不说&#xff0c;现在找工作的难度是以前的很多倍。甚至可以说地狱级都不为过。 以前只要简历一挂到网上&#xff0c;就有很多电话打过来。特别是在一线城市&#xff0c;各种类型企业的HR都来找&#xff0c;希…

《opencv实用探索·十》opencv双边滤波的简单理解

1、引言 OpenCV中的双边滤波&#xff08;Bilateral Filtering&#xff09;是一种保持边缘清晰的滤波方法&#xff0c;它考虑像素的空间关系和像素值之间的差异。双边滤波对于去除噪声的同时保持图像的边缘非常有效&#xff0c;它也是一种非线性滤波。 双边滤波采用了两个高斯滤…

获取网络ppt资源

背景&#xff1a; ​ 某度上有很多优质的PPT资源和文档资源&#xff0c;但是大多数需要付费才能获取。对于一些经济有限的用户来说&#xff0c;这无疑是个遗憾&#xff0c;因为我们更倾向于以免费的方式获取所需资源。 解决方案&#xff1a; ​ 然而&#xff0c;幸运的是&am…

vivado如何进行增量编译

情况1 如果是根据 placement 或者 routing 进行增量编译&#xff0c;直接右键点击要跑的 implement,&#xff0c;选择set incremental implement &#xff0c; 然后选第二项&#xff0c;指定 要参考的 routing.dcp 或者 placement.dcp 的路径即可 情况2 如果只参考 synthesis …

西工大计算机学院计算机系统基础实验一(环境配置)

首先&#xff0c;不要焦虑&#xff0c;稳住心态慢慢来&#xff0c;一点一点做&#xff0c;跟着作者把基础打好&#xff0c;比什么都重要。作者曾经经历过这份痛苦&#xff0c;知道它有多么不好受。当初的作者高中之前甚至都没有自己的一台笔记本&#xff0c;上了大学以后学C语言…

设计模式:装饰者模式

目录 一、定义 二、场景 三、例子 四、优缺点 优点&#xff1a; 缺点&#xff1a; 一、定义 在不改变已有对象结构的情况下&#xff0c;动态添加新的功能到对象上&#xff0c;是继承的一种替代方案。属于结构型模式。 二、场景 1.扩展一个类的功能&#xff0c;添加附加职责…

Python如何传递任意数量的实参及什么是返回值

Python如何传递任意数量的实参 传递任意数量的实参 形参前加一个 * &#xff0c;Python会创建一个已形参为名的空元组&#xff0c;将所有收到的值都放到这个元组中&#xff1a; def make_pizza(*toppings):print("\nMaking a pizza with the following toppings: "…

【部署】预处理和后处理加速方案:CVCuda

预处理 和 后处理加速方案大概可以包括以下几种 &#xff08;1&#xff09;nvidia开源的CVCuda &#xff08;2&#xff09;使用opencv4的cuda加速模块 &#xff08;3&#xff09;手写cuda算子 这一章我们先从CVCuda开始 一.基本要求 1.1 何时可以使用CVcuda库 在模型的 预…

SpringBoot_02

Web后端开发_07 SpringBoot_02 SpringBoot原理 1.配置优先级 1.1配置 SpringBoot中支持三种格式的配置文件&#xff1a; application.propertiesapplication.ymlapplication.yaml properties、yaml、yml三种配置文件&#xff0c;优先级最高的是properties 配置文件优先级…