OS Experiment 4 of Harbin Institute of technology -- tracking and statistics of process running track

Tracking and statistics of process running track

The whole process from the creation of a process (call fork() under Linux) to the end is the life cycle of the process. The running track of the process in its life cycle is actually represented by multiple switching of the process state. For example, after the process is created, it will become ready state; When the process is scheduled, it will switch to the running state; If a file read-write operation is started during operation, the operating system will switch the process to the blocking state (waiting state) to give up the CPU; When the file is read and written, the operating system will switch it to thread state and wait for the process scheduling algorithm to schedule the execution of the process

process.c interpretation

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)
        {
                /* CPU Burst */
                times(&start_time);
                /* In fact, only t.tms_utime is the real CPU time. But we're simulating a
                 * Large CPU users that only run in user state, like "for(;);". So put t.tms_stime
                 * Plus, it's reasonable.*/
                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;

                /* IO Burst */
                /* Use sleep(1) to simulate I/O operation for 1 second */
                sleep_time=0;
                while (sleep_time < io_time)
                {
                        sleep(1);
                        sleep_time++;
                }
                last -= sleep_time;
        }
}


Use last to judge the exit. When the last time is less than 0, it means that the time has arrived.

Record a start time,

Time consumption assumes that a cpu time is used, minus one cpu time, which indicates the call

Use the sleep function to simulate one second until it is greater than or equal to an io time to exit, minus an io time, indicating the call

After calling, the last is still not less than 0. Continue to call.

proccess.c modification:

#include <stdio.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 p1,p2,p3,p4;
	
	p1=fork();
	if(p1<0){
		printf("file to fork");
	}else if(p1==0){
		printf("ppid:%4d pid:%4d\n",getppid(),getpid());
		cpuio_bound(10,1,0);
	}else{
		printf("ppid:%4d pid:%4d fpid:%4d\n",getppid(),getpid(),p1);
	}


	p2=fork();
	if(p2<0){
		printf("file to fork");
	}else if(p2==0){
		printf("ppid:%4d pid:%4d\n",getppid(),getpid());
		cpuio_bound(10,0,1);
	}else{
		printf("ppid:%4d pid:%4d fpid:%4d\n",getppid(),getpid(),p2);
	}

	p3=fork();
	if(p3<0){
		printf("file to fork");
	}else if(p3==0){
		printf("ppid:%4d pid:%4d\n",getppid(),getpid());
		cpuio_bound(10,1,1);
	}else{
		printf("ppid:%4d pid:%4d fpid:%4d\n",getppid(),getpid(),p3);
	}

	p4=fork();
	if(p4<0){
		printf("file to fork");
	}else if(p4==0){
		printf("ppid:%4d pid:%4d\n",getppid(),getpid());
		cpuio_bound(10,1,9);
	}else{
		printf("ppid:%4d pid:%4d fpid:%4d\n",getppid(),getpid(),p4);
	}
	wait(NULL);
	wait(NULL);
	wait(NULL);
	wait(NULL);	
	return 0;
}

/*
 * This function consumes CPU and I/O time according to parameters
 * last: The total time that the function actually consumes CPU and I/O, excluding the time in the ready queue, > = 0 is required
 * cpu_time: The time that the CPU is occupied continuously at one time, > = 0 is required
 * io_time: Time consumed by one I/O, > = 0 is required
 * If last > CPU_ time + io_ Time, the CPU and I/O are occupied repeatedly
 * All times are in seconds
 */
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)
	{
		/* CPU Burst */
		times(&start_time);
		/* In fact, only t.tms_utime is the real CPU time. But we're simulating a
		 * Large CPU users that only run in user state, like "for(;);". So put t.tms_stime
		 * Plus, it's reasonable.*/
		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;

		/* IO Burst */
		/* Use sleep(1) to simulate I/O operation for 1 second */
		sleep_time=0;
		while (sleep_time < io_time)
		{
			sleep(1);
			sleep_time++;
		}
		last -= sleep_time;
	}
}


Implementation results
It can be seen that the 281 process is implemented first, and four processes 282283284285 are created,

282 created 288289290 three processes

283 created 286287 two processes

284 created 291

288 created 292293

292 created 296

289 created 295

Custom file descriptor

Take the log file as a file opened by default by the process. It can be input and output when necessary. It can be regarded as a basic input and output file. Here we need to supplement the knowledge. That is, the log file is used as the same device as the screen.

We can see the main function of the source code, which defines the basic input and output. In init / main C medium

Here, after all the settings are initialized, a fork() is performed to branch, and the child process is initialized by calling the init() function, and the parent process is blocked. Look at the init function

The previous setup function is used to load the file system,
The open function is used to open the terminal as the file descriptor of the process, and dup is used to copy the file descriptor. Because the process has not opened any files before, the open file description sequence number is 0, and then the file descriptor 0 is copied twice as the file descriptors 1 and 2. These three file descriptors are standard input, standard output and standard error output, At the same time, because the fork function is used to create the process, the child process will inherit the file descriptor of the parent process, so the child process protects the file descriptor 0,1,2 by default

We can put these three lines before the fork function, so that each process will be monitored.
At the same time, open another file, so that every subsequent process starts to create a file descriptor 3, pointing to / var / process Log file.
What we need to know is that this creation process is completed in the kernel, so we certainly can't use the write function in user mode in the kernel, so we need a function to write files in the kernel. Here we directly give the fprintk(int fd,const char *fmt,...) function and give the file descriptor. The usage is the same as that of fprintf. When the process state changes, we just need to perform fprintk and output it to / var / process Log.

#include "linux/sched.h"
#include "sys/stat.h"

static char logbuf[1024];
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 the output is to stdout or stderr, call sys directly_ Just write */
    if (fd < 3)
    {
        __asm__("push %%fs\n\t"
            "push %%ds\n\t"
            "pop %%fs\n\t"
            "pushl %0\n\t"
        /* Note that for Windows environments, yes_ logbuf, the same below */
            "pushl $logbuf\n\t"
            "pushl %1\n\t"
        /* Note that for Windows environments, yes_ sys_write, the same below */
            "call sys_write\n\t"
            "addl $8,%%esp\n\t"
            "popl %0\n\t"
            "pop %%fs"
            ::"r" (count),"r" (fd):"ax","cx","dx");
    }
    else
/* It is assumed that descriptors with > = 3 are associated with files. In fact, there are many other situations that are not considered here.*/
    {
    /* Get file handle from file descriptor table of process 0 */
        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;
}

Write this function to kernel / printk C medium

Implementation of clock interrupt

Sched. In kernal C is a file for clock interrupt, in which sched C is init / main C call, used to initialize the clock.
Which defines a

long volatile jiffies=0;//This records the number of clock interrupts that have occurred since the computer was turned on

The following timer_ The interrupt function is mainly used to call a do when a process is interrupted by the clock_ Timer function, this do_timer then calls shschedule to select and execute the process in ready state, and finally exits from ring 0 to ring 3.

timer_interrupt:
!    ......
! increase jiffies Count value
    incl jiffies
!    ......
	call do_timer		# 'do_timer(long CPL)' does everything from
	addl $4,%esp		# task switching to accounting ...
	jmp ret_from_sys_call

Then, in sched_init() also defines

// Setup mode 8253
outb_p(0x36, 0x43);
outb_p(LATCH&0xff, 0x40);
outb_p(LATCH>>8, 0x40);
// In kernel / sched C medium
#define LATCH  (1193180/HZ)

// In include / Linux / sched H medium
#define HZ 100

In addition, the input clock frequency of PC 8253 timing chip is 1.193180MHz, i.e. 1193180 / s, LATCH=1193180/100, and the clock interrupt is generated every 11931.8, i.e. every 1 / 100 second (10ms). Therefore, jiffies actually records how many 10ms have passed since the startup.
The clock beats 1193180 times per second, then LATCH=1193180/100, that is, the jiffies are increased every 11931.8 times, which means 1 / 100 of a second.

Find the state switching point:

To display the process status, the files to be modified are mainly

fork.c, which contains the code that generates the process

sched.c, which contains scheduling related codes, only needs to be modified State is modified, but it should be noted that the ready state and running state are represented by a flag of 0. This requires us to make a detailed analysis of the code of the schedule() function to find out when a process needs to be set to run and when it is ready

exit.c is mainly the code of process exit. It only needs. Output where the state has been modified, and when we need to add it in the schedule() function

fork()

A process starts with fork (), so we can see the source code of fork () function. The source code of fork function is in sys_fork(), in kernel/system_call.s

.align 2
sys_fork:
	call find_empty_process
	testl %eax,%ea
	
	js 1f
	push %gs
	pushl %esi
	pushl %edi
	pushl %ebp
	pushl %eax
	call copy_process
	addl $20,%esp
1:	ret

copy_process is the creation of a new process in kernel / fork C, the code is

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
		long ebx,long ecx,long edx,
		long fs,long es,long ds,
		long eip,long cs,long eflags,long esp,long ss)
{
	struct task_struct *p;
	int i;
	struct file *f;

	p = (struct task_struct *) get_free_page();
	if (!p)
		return -EAGAIN;
	task[nr] = p;
	*p = *current;	/* NOTE! this doesn't copy the supervisor stack */
	p->state = TASK_UNINTERRUPTIBLE;
	p->pid = last_pid;
	//*********************************new code
	fprintk(3,"%d\tN\t%d\n",p->pid,jiffies);	
	//*********************************
	p->father = current->pid;
	p->counter = p->priority;
	p->signal = 0;
	p->alarm = 0;
	p->leader = 0;		/* process leadership doesn't inherit */
	p->utime = p->stime = 0;
	p->cutime = p->cstime = 0;
	p->start_time = jiffies;
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;
	p->tss.eip = eip;
	p->tss.eflags = eflags;
	p->tss.eax = 0;
	p->tss.ecx = ecx;
	p->tss.edx = edx;
	p->tss.ebx = ebx;
	p->tss.esp = esp;
	p->tss.ebp = ebp;
	p->tss.esi = esi;
	p->tss.edi = edi;
	p->tss.es = es & 0xffff;
	p->tss.cs = cs & 0xffff;
	p->tss.ss = ss & 0xffff;
	p->tss.ds = ds & 0xffff;
	p->tss.fs = fs & 0xffff;
	p->tss.gs = gs & 0xffff;
	p->tss.ldt = _LDT(nr);
	p->tss.trace_bitmap = 0x80000000;
	if (last_task_used_math == current)
		__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
	if (copy_mem(nr,p)) {
		task[nr] = NULL;
		free_page((long) p);
		return -EAGAIN;
	}
	for (i=0; i<NR_OPEN;i++)
		if ((f=p->filp[i]))
			f->f_count++;
	if (current->pwd)
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
	set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
	set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
	p->state = TASK_RUNNING;	/* do this last, just in case  Set the status of the process to ready status*/
	//********************************new code
	fprintk(3,"%d\tJ\t%d\n",p->pid,jiffies);
	//*******************************
	return last_pid;
}

sche.c:

Record the time of joining the sleep state.

sleep_on() and interruptible_sleep_on() puts the current process into sleep, which can be found in kernel / sched Source code found in C.

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;
	//****************************************new code
	fprintk(3,"%d\tW\t%d\n",p->pid,jiffies);
	//****************************************
	schedule();
	if (tmp){
		tmp->state=0;
		fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
	}
}
//Function function, one parameter is the queue of pcb. The main functions are:
//Add the currently running process to the blocking queue and the process in the blocking queue to the ready queue,
int sys_pause(void)
{
	current->state = TASK_INTERRUPTIBLE;
	
	fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
	
	schedule();
	return 0;
}
//Add process to blocking queue
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;

	//Add a blocking state, from execution to blocking***************************************************************** 
	fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
	//Add a blocking state, from execution to blocking***************************************************************** 
	
	schedule();
	if (*p && *p != current) {
		(**p).state=0;
		//Add a ready state, from blocking to ready***************************************************************** 
		fprintk(3,"%d\tJ\t%d\n",(**p)->pid,jiffies);
		//Add a ready state, from blocking to ready***************************************************************** 	
		
		goto repeat;
	}
	*p=NULL;
	if (tmp){
		tmp->state=0;

		//Add a ready state, from blocking to ready***************************************************************** 
		fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
		//Add a ready state, from blocking to ready***************************************************************** 
			
	}
}
void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;
		
		//Add a ready state, from blocking to ready***************************************************************** 
		fprintk(3,"%d\tJ\t%d\n",(**p)->pid,jiffies);
		//Add a ready state, from blocking to ready***************************************************************** 
		
		*p=NULL;
	}
}
void schedule(void)
{
	int i,next,c;
	struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
		if (*p) {
			if ((*p)->alarm && (*p)->alarm < jiffies) {
					(*p)->signal |= (1<<(SIGALRM-1));
					(*p)->alarm = 0;
				}
			if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
			(*p)->state==TASK_INTERRUPTIBLE){
				(*p)->state=TASK_RUNNING;
				//Add a ready state******************************************************************* 
				fprintk(3,"%d\tJ\t%d\n",p->pid,jiffies);
			}
		}

/* this is the scheduler proper: */

	while (1) {
		c = -1;
		next = 0;
		i = NR_TASKS;
		p = &task[NR_TASKS];
		while (--i) {
			if (!*--p)
				continue;
			if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
				c = (*p)->counter, next = i;
		}
		if (c) break;
		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
			if (*p)
				(*p)->counter = ((*p)->counter >> 1) +
						(*p)->priority;
	}
	
	//************************************
	//The next is the subscript in the queue of the process to be switched. When the next is not the same as the current process, print the current J state in the file. If it is the same, add an R state
	if(task[next]->pid != current->pid){
		if(current->state == TASK_RUNNING){
			fprintk(3,"%d\tJ\t%d\n",current->pid,jiffies);		
		}
		fprintk(3,"%d\tR\t%d\n",task[next]->pid,jiffies);
	}
	
	switch_to(next);
}

These are the main Modification codes, which only need to be modified where the state has changed, and when scheduling switching, judge whether the scheduled process is the same as the current process.

exit.c:

There are also two places in this file where the state is switched. I won't list them here. Just follow the example and do fprintk

Test:

Enter the modified system and run process C code

Found / var / success Log file. It is recommended to take it to the local machine to check, because the card is compared in the experimental environment, and because of sys in the main() function_ The sentence pause () causes process 0 to generate a wait state, not to mention Linux 0 11 hit, directly stuck

There are some problems with the experimental data here: all processes have no N state, and it is found that fork The fprintk statement in C writes descriptor 3 as "3", resulting in the N state not written to success Log
The figure above shows running process C, the operating system writes to process The character of the log. I feel a little wrong here, because I have executed process many times C, but the characters are not written into the file. I guess it's because you need to wait, because you need to read the disk every time you switch a process, so when you finish the process After C, you need to wait for a long time and wait for the CPU to finish writing to the disk. I don't know whether this conclusion is correct for the time being.
After waiting for a long time, I switched 4 million times, of which 99% were main For (;) in C pause(); Caused

Think about it later. You can remove the fprintk function in the pause() function to ensure the completion speed. At that time, you didn't realize

Keywords: C Linux Operating System

Added by gsaldutti on Fri, 18 Feb 2022 12:40:46 +0200