操作系统导论-第四章作业

一、进程

进程就是运行中的程序,程序本身是没有生命周期的,它只是存储在磁盘上的一些指令(或者一些静态数据),操作系统将这些指令和数据加载到内存中,使其运行起来。

1.1 虚拟化CPU技术

根据我们平时使用计算机的经验表明,计算机往往可以同时运行多个进程,实际上,一个正常的系统可能会有上百个进程同时在运行,但物理CPU是少量的,那么操作系统是如何提供几乎有无数个CPU的假象的呢——这就是虚拟化CPU技术。

如何实现虚拟化CPU?
操作系统通过让一个进程只运行一个时间片(毫秒级),然后切换到其他进程的做法,造成了有多个CPU的假象,这就是常说的时分共享CPU技术。
显然,这会造成性能损失,因为CPU必须被多个进程共享,每个进程的运行就会慢一点。

补充知识:
时分共享是操作系统共享资源的最基本的技术之一,允许资源由一个实体使用一小段时间,然后切换,这样循环下去,资源就可以被许多人共享。
空分共享是资源在空间上被划分给希望使用它的人,比如,磁盘空间是一个空分共享资源,一旦将块分配给文件,在用户删除文件之前,其他人不可能使用这个块空间。

1.2 进程的机器状态

进程还可以理解为操作系统为程序提供的一个抽象。对于一个进程,在任何时候,我们都可以知道它在运行过程中访问或影响的系统的不同部分,反过来说,被影响和访问的系统部分也可以用来概括该进程。

为了理解进程的构成,我们必须了解它的机器状态——程序在运行时可以读取或更新的内容。

在任意时刻,物理计算机的哪些部分对执行程序很重要?
进程的机器状态有个很重要的组成部分,就是进程的内存。指令存在在内存中,正在运行的程序读取和写入的数据也在内存中。
进程的机器状态的另一部分是寄存器。进程的许多指令明确地读取或更新寄存器。因此,他们对于执行该进程很重要。

有一些非常特殊的寄存器构成了该机器状态的一部分。例如,程序计数器(Program Counter)告诉我们程序当前正在执行哪个指令,栈指针(Stack Pointer)和相关的帧指针(Frame Pointer)用于管理函数参数栈、局部变量和返回地址。

1.3 进程API

操作系统所有的接口必须包含的内容有:

  • 创建 (Create):操作系统必须包含一些创建新进程的方法。比如,在Shell中输入命令或者双击应用图标时,所有现代操作系统都会以某种形式提供这些API。
  • 销毁 (Destory):由于存在创建进程的接口,因此操作系统还要提供强制销毁进程的接口。尽管很多进程会在运行完成后自己退出,但是,如果它们不退出,用户希望能够强制终止它们。因此停止失控进程的接口很有用。
  • 等待 (Wait):有时会有等待进程停止运行的需求,因此操作系统会提供相关接口。
  • 其他控制(Miscellaneous control):除了杀死或等待进程外,有时还可能有其他控制。比如,大多数操作系统提供某种方法来暂停进程(停止运行一段时间),然后恢复(继续运行)。
  • 状态(Statu):通常也有一些接口可以获得有关进程的状态信息,例如运行了多长时间,或者处于什么状态。

1.4 如何创建一个进程?

程序如何转化为一个进程?换个说法,操作系统是如何启动一个程序的?进程的创建实际上如何进行?

程序首先要被加载进内存

首先,程序必须被操作系统加载进内存,程序最初是以某个可执行格式驻留在磁盘上(disk,或者在某些现代系统中,在基于闪存的SSD上)。因此,将程序和静态数据加载到内存中的过程,需要操作系统从磁盘读取这些字节,并把它们放在内存的某处。

在早期的(或简单的)操作系统中,程序要全部加载进内存之后才会开始执行程序 。但是现代操作系统惰性(Lazily)执行该过程,即仅在程序执行期间需要加载的代码或数据片段,才会加载。这个惰性加载涉及到分页和交换机制,这块是内存虚拟化的内容。

为程序分配运行时内存
  • C程序用栈存放局部变量、函数参数和返回地址。操作系统也可能会用参数初始化栈,它会将参数填入main()函数,这个参数就是argc 和 argv数组。
  • 操作系统也可能为程序的堆(heap)分配一些内存,对于C程序来说,堆用于显示请求的动态分配数据。程序会通过调用malloc()来请求内存,并通过调用free()来明确地释放它。数据结构(如链表、散列表、树和其他数据结构)需要堆空间。一开始堆很小,但随着程序的运行,通过malloc()库请求更多内存,操作系统可能会参与分配更多的内存给进程。
IO设置相关工作

操作系统还要执行一些其他的初始化任务,尤其是输入输出相关的任务。比如,在UNIX系统中,默认情况下每个进程都有3个打开的文件描述符,用于标准输入、输出和错误。这些描述符让程序轻松读取来自终端的输入以及打印输出到屏幕。

启动程序

前面的工作完成之后,操作系统还有最后一项任务:启动程序,在入口处运行,即main()函数,通过跳转到main()例程,操作系统(OS)将CPU的控制权转移到新建的进程中,从而程序开始执行了。

1.5 进程状态

进程在任意时间可能会处于不同的状态,简单来讲,进程可以处于下面3种状态之一:

  • 运行 (running):在运行状态下,进程正在CPU上运行,这意味着它正在执行指令。
  • 就绪 (ready):在就绪状态下,进程已经准备好运行,但由于某种原因,操作系统选择不在此时运行
  • 阻塞 (blocked):在阻塞状态下,一个进程执行了某个操作,需要等待其它事件发生才能继续执行(比如此时程序发起IO请求,需要等待IO完成才能继续执行),这时它就会被阻塞,其它进程可以使用CPU。
状态转换

在这里插入图片描述

进程的上下文切换

操作系统是一个程序,和其他程序一样,它有一些关键的数据结构来跟踪各种相关的信息。
比如,为了跟踪每个进程的状态,操作系统可能会为所有就绪的进程保留某种进程列表,以及跟踪当前正在运行的进程的一些附加信息。操作系统还必须以某种方式跟踪被阻塞的进程。当IO事件完成时,操作系统应该确保唤醒正确的进程,让它准备好再次运行。

操作系统会追踪进程的一些重要信息。对于停止的进程,寄存器上下文将保存其寄存器的内容。当一个进程停止时,它的寄存器将被保存在这个内存位置。通过恢复这些寄存器(将它们的值放回实际的物理寄存器中),操作系统可以恢复运行该进程。这被称为上下文切换(Context Switch)。

进程除了运行、就绪和阻塞之外,还有其他一些进程可以处于的状态。有时候系统会有一个初始状态,表示进程在创建时处于的状态——新建态
另外,一个进程可以处于已退出但尚未清理的最终状态(在基于UNIX的系统中,这称为僵尸状态)。这个最终状态非常有用,因为它允许其他进程(通常是创建该进程的父进程)检查进程的返回代码,并查看感刚刚结束的进程是否成功执行(在UNIX系统中,程序成功执行返回0,否则返回非0)。完成后,父进程将进行最后一次调用(例如,wait()),以等待子进程的完成,并告诉操作系统它可以清理这个正在结束的进程的所有相关数据结构。

二、章节作业

模拟作业:模拟作业以模拟器的形式出现,你运行它以确保理解上述内容。模拟器通常是Python程序,它们让你能够生成不同的问题(使用不同的随机种子),也让程序为你解决问题,以便你检查答案。使用-h或-help参数运行任何模拟器,将提供有关模拟器所有选项的更多信息。

程序process-run.py 让你能够查看程序运行时进程状态如何改变,是在使用CPU(例如,执行相加指令)还是执行I/O(例如,向磁盘发送请求并等待它完成)。

process-run.py 代码
#! /usr/bin/python2

from __future__ import print_function
import sys
from optparse import OptionParser
import random

# to make Python2 and Python3 act the same -- how dumb
def random_seed(seed):
    try:
        random.seed(seed, version=1)
    except:
        random.seed(seed)
    return

# process switch behavior
SCHED_SWITCH_ON_IO = 'SWITCH_ON_IO'
SCHED_SWITCH_ON_END = 'SWITCH_ON_END'

# io finished behavior
IO_RUN_LATER = 'IO_RUN_LATER'
IO_RUN_IMMEDIATE = 'IO_RUN_IMMEDIATE'

# process states
STATE_RUNNING = 'RUNNING'
STATE_READY = 'READY'
STATE_DONE = 'DONE'
STATE_WAIT = 'WAITING'

# members of process structure
PROC_CODE = 'code_'
PROC_PC = 'pc_'
PROC_ID = 'pid_'
PROC_STATE = 'proc_state_'

# things a process can do
DO_COMPUTE = 'cpu'
DO_IO = 'io'
DO_IO_DONE = 'io_done'


class scheduler:
    def __init__(self, process_switch_behavior, io_done_behavior, io_length):
        # keep set of instructions for each of the processes
        self.proc_info = {
    
    }
        self.process_switch_behavior = process_switch_behavior
        self.io_done_behavior = io_done_behavior
        self.io_length = io_length
        return

    def new_process(self):
        proc_id = len(self.proc_info)
        self.proc_info[proc_id] = {
    
    }
        self.proc_info[proc_id][PROC_PC] = 0
        self.proc_info[proc_id][PROC_ID] = proc_id
        self.proc_info[proc_id][PROC_CODE] = []
        self.proc_info[proc_id][PROC_STATE] = STATE_READY
        return proc_id

    # program looks like this:
    #   c7,i,c1,i
    # which means
    #   compute for 7, then i/o, then compute for 1, then i/o
    def load_program(self, program):
        proc_id = self.new_process()
        for line in program.split(','):
            opcode = line[0]
            if opcode == 'c': # compute
                num = int(line[1:])
                for i in range(num):
                    self.proc_info[proc_id][PROC_CODE].append(DO_COMPUTE)
            elif opcode == 'i':
                self.proc_info[proc_id][PROC_CODE].append(DO_IO)
                # add one compute to HANDLE the I/O completion
                self.proc_info[proc_id][PROC_CODE].append(DO_IO_DONE)
            else:
                print('bad opcode %s (should be c or i)' % opcode)
                exit(1)
        return

    def load(self, program_description):
        proc_id = self.new_process()
        tmp = program_description.split(':')
        if len(tmp) != 2:
            print('Bad description (%s): Must be number <x:y>' % program_description)
            print('  where X is the number of instructions')
            print('  and Y is the percent change that an instruction is CPU not IO')
            exit(1)

        num_instructions, chance_cpu = int(tmp[0]), float(tmp[1])/100.0
        for i in range(num_instructions):
            if random.random() < chance_cpu:
                self.proc_info[proc_id][PROC_CODE].append(DO_COMPUTE)
            else:
                self.proc_info[proc_id][PROC_CODE].append(DO_IO)
                # add one compute to HANDLE the I/O completion
                self.proc_info[proc_id][PROC_CODE].append(DO_IO_DONE)
        return

    def move_to_ready(self, expected, pid=-1):
        if pid == -1:
            pid = self.curr_proc
        assert(self.proc_info[pid][PROC_STATE] == expected)
        self.proc_info[pid][PROC_STATE] = STATE_READY
        return

    def move_to_wait(self, expected):
        assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
        self.proc_info[self.curr_proc][PROC_STATE] = STATE_WAIT
        return

    def move_to_running(self, expected):
        assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
        self.proc_info[self.curr_proc][PROC_STATE] = STATE_RUNNING
        return

    def move_to_done(self, expected):
        assert(self.proc_info[self.curr_proc][PROC_STATE] == expected)
        self.proc_info[self.curr_proc][PROC_STATE] = STATE_DONE
        return

    def next_proc(self, pid=-1):
        if pid != -1:
            self.curr_proc = pid
            self.move_to_running(STATE_READY)
            return
        for pid in range(self.curr_proc + 1, len(self.proc_info)):
            if self.proc_info[pid][PROC_STATE] == STATE_READY:
                self.curr_proc = pid
                self.move_to_running(STATE_READY)
                return
        for pid in range(0, self.curr_proc + 1):
            if self.proc_info[pid][PROC_STATE] == STATE_READY:
                self.curr_proc = pid
                self.move_to_running(STATE_READY)
                return
        return

    def get_num_processes(self):
        return len(self.proc_info)

    def get_num_instructions(self, pid):
        return len(self.proc_info[pid][PROC_CODE])

    def get_instruction(self, pid, index):
        return self.proc_info[pid][PROC_CODE][index]

    def get_num_active(self):
        num_active = 0
        for pid in range(len(self.proc_info)):
            if self.proc_info[pid][PROC_STATE] != STATE_DONE:
                num_active += 1
        return num_active

    def get_num_runnable(self):
        num_active = 0
        for pid in range(len(self.proc_info)):
            if self.proc_info[pid][PROC_STATE] == STATE_READY or \
                   self.proc_info[pid][PROC_STATE] == STATE_RUNNING:
                num_active += 1
        return num_active

    def get_ios_in_flight(self, current_time):
        num_in_flight = 0
        for pid in range(len(self.proc_info)):
            for t in self.io_finish_times[pid]:
                if t > current_time:
                    num_in_flight += 1
        return num_in_flight

    def check_for_switch(self):
        return

    def space(self, num_columns):
        for i in range(num_columns):
            print('%10s' % ' ', end='')

    def check_if_done(self):
        if len(self.proc_info[self.curr_proc][PROC_CODE]) == 0:
            if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING:
                self.move_to_done(STATE_RUNNING)
                self.next_proc()
        return

    def run(self):
        clock_tick = 0

        if len(self.proc_info) == 0:
            return

        # track outstanding IOs, per process
        self.io_finish_times = {
    
    }
        for pid in range(len(self.proc_info)):
            self.io_finish_times[pid] = []

        # make first one active
        self.curr_proc = 0
        self.move_to_running(STATE_READY)

        # OUTPUT: headers for each column
        print('%s' % 'Time', end='') 
        for pid in range(len(self.proc_info)):
            print('%14s' % ('PID:%2d' % (pid)), end='')
        print('%14s' % 'CPU', end='')
        print('%14s' % 'IOs', end='')
        print('')

        # init statistics
        io_busy = 0
        cpu_busy = 0

        while self.get_num_active() > 0:
            clock_tick += 1

            # check for io finish
            io_done = False
            for pid in range(len(self.proc_info)):
                if clock_tick in self.io_finish_times[pid]:
                    io_done = True
                    self.move_to_ready(STATE_WAIT, pid)
                    if self.io_done_behavior == IO_RUN_IMMEDIATE:
                        # IO_RUN_IMMEDIATE
                        if self.curr_proc != pid:
                            if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING:
                                self.move_to_ready(STATE_RUNNING)
                        self.next_proc(pid)
                    else:
                        # IO_RUN_LATER
                        if self.process_switch_behavior == SCHED_SWITCH_ON_END and self.get_num_runnable() > 1:
                            # this means the process that issued the io should be run
                            self.next_proc(pid)
                        if self.get_num_runnable() == 1:
                            # this is the only thing to run: so run it
                            self.next_proc(pid)
                    self.check_if_done()
            
            # if current proc is RUNNING and has an instruction, execute it
            instruction_to_execute = ''
            if self.proc_info[self.curr_proc][PROC_STATE] == STATE_RUNNING and \
                   len(self.proc_info[self.curr_proc][PROC_CODE]) > 0:
                instruction_to_execute = self.proc_info[self.curr_proc][PROC_CODE].pop(0)
                cpu_busy += 1

            # OUTPUT: print what everyone is up to
            if io_done:
                print('%3d*' % clock_tick, end='')
            else:
                print('%3d ' % clock_tick, end='')
            for pid in range(len(self.proc_info)):
                if pid == self.curr_proc and instruction_to_execute != '':
                    print('%14s' % ('RUN:'+instruction_to_execute), end='')
                else:
                    print('%14s' % (self.proc_info[pid][PROC_STATE]), end='')

            # CPU output here: if no instruction executes, output a space, otherwise a 1 
            if instruction_to_execute == '':
                print('%14s' % ' ', end='')
            else:
                print('%14s' % '1', end='')

            # IO output here:
            num_outstanding = self.get_ios_in_flight(clock_tick)
            if num_outstanding > 0:
                print('%14s' % str(num_outstanding), end='')
                io_busy += 1
            else:
                print('%10s' % ' ', end='')
            print('')

            # if this is an IO start instruction, switch to waiting state
            # and add an io completion in the future
            if instruction_to_execute == DO_IO:
                self.move_to_wait(STATE_RUNNING)
                self.io_finish_times[self.curr_proc].append(clock_tick + self.io_length + 1)
                if self.process_switch_behavior == SCHED_SWITCH_ON_IO:
                    self.next_proc()

            # ENDCASE: check if currently running thing is out of instructions
            self.check_if_done()
        return (cpu_busy, io_busy, clock_tick)
        
#
# PARSE ARGUMENTS
#

parser = OptionParser()
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
parser.add_option('-P', '--program', default='', help='more specific controls over programs', action='store', type='string', dest='program')
parser.add_option('-l', '--processlist', default='', help='a comma-separated list of processes to run, in the form X1:Y1,X2:Y2,... where X is the number of instructions that process should run, and Y the chances (from 0 to 100) that an instruction will use the CPU or issue an IO', action='store', type='string', dest='process_list')
parser.add_option('-L', '--iolength', default=5, help='how long an IO takes', action='store', type='int', dest='io_length')
parser.add_option('-S', '--switch', default='SWITCH_ON_IO', help='when to switch between processes: SWITCH_ON_IO, SWITCH_ON_END', action='store', type='string', dest='process_switch_behavior')
parser.add_option('-I', '--iodone', default='IO_RUN_LATER', help='type of behavior when IO ends: IO_RUN_LATER, IO_RUN_IMMEDIATE', action='store', type='string', dest='io_done_behavior')
parser.add_option('-c', help='compute answers for me', action='store_true', default=False, dest='solve')
parser.add_option('-p', '--printstats', help='print statistics at end; only useful with -c flag (otherwise stats are not printed)', action='store_true', default=False, dest='print_stats')
(options, args) = parser.parse_args()

random_seed(options.seed)

assert(options.process_switch_behavior == SCHED_SWITCH_ON_IO or options.process_switch_behavior == SCHED_SWITCH_ON_END)
assert(options.io_done_behavior == IO_RUN_IMMEDIATE or options.io_done_behavior == IO_RUN_LATER)

s = scheduler(options.process_switch_behavior, options.io_done_behavior, options.io_length)

if options.program != '':
    for p in options.program.split(':'):
        s.load_program(p)
else:
    # example process description (10:100,10:100)
    for p in options.process_list.split(','):
        s.load(p)

assert(options.io_length >= 0)

if options.solve == False:
    print('Produce a trace of what would happen when you run these processes:')
    for pid in range(s.get_num_processes()):
        print('Process %d' % pid)
        for inst in range(s.get_num_instructions(pid)):
            print('  %s' % s.get_instruction(pid, inst))
        print('')
    print('Important behaviors:')
    print('  System will switch when ', end='')
    if options.process_switch_behavior == SCHED_SWITCH_ON_IO:
        print('the current process is FINISHED or ISSUES AN IO')
    else:
        print('the current process is FINISHED')
    print('  After IOs, the process issuing the IO will ', end='')
    if options.io_done_behavior == IO_RUN_IMMEDIATE:
        print('run IMMEDIATELY')
    else:
        print('run LATER (when it is its turn)')
    print('')
    exit(0)

(cpu_busy, io_busy, clock_tick) = s.run()

if options.print_stats:
    print('')
    print('Stats: Total Time %d' % clock_tick)
    print('Stats: CPU Busy %d (%.2f%%)' % (cpu_busy, 100.0 * float(cpu_busy)/clock_tick))
    print('Stats: IO Busy  %d (%.2f%%)' % (io_busy, 100.0 * float(io_busy)/clock_tick))
    print('')

tips:第一行的代码是表示python程序所在的绝对路径,这个需要根据自己python程序所在的位置做修改。

问题
  • 1.用以下标志运行程序: ./process-run.py -l 5:100,5:100。CPU利用率(CPU使用时间的百分比)应该是多少?为什么你知道这一点?利用-c参数查看你的答案是否正确。

    运行结果截图
    在这里插入图片描述

先解释一下参数:

  • -l 表示显示进程列表,展示出所有的进程
  • 5:100 表示指定进程是"5:100",这意味着该进程由5条指令组成,并且每条指令是CPU指令的可能性是100%
    这条命令有两个5:100,表示同时运行两个进程。

答案:CPU利用率应该为100%,因为两个进程所有指令都是CPU指令。

检查答案是否正确
在这里插入图片描述

    1. 现在用这些参数运行: ./process-run.py -l 4:100,1:0。这些参数指定了一个包含4条指令的进程(都要使用CPU),并且只是简单地发出IO请求并等待它完成。完成这两个进程需要多长时间?(默认每个CPU指令用1个时钟时间,一个IO请求5个时钟时间)用-c检查你的答案是否正确。

      运行结果截图
      在这里插入图片描述
      答案:11个时钟时间,第一个进程花费4个时钟时间,进程结束后,第二个进程发起IO请求花费一个时钟时间,然后处理IO请求花费5个时钟时间,最后IO结束返回花费1个时钟时间。

检查答案
在这里插入图片描述

    1. 现在交换进程的顺序: ./process-run.py -l 1:0,4:100.现在发生了什么?交换顺序是否重要?为什么?同样,用-c看看你的答案是否正确。

      运行截图:
      在这里插入图片描述
      答案:第一个进程先运行,发起IO请求进入阻塞状态,CPU此时会调度第二个进程运行。交换顺序很重要,因为如果先运行的进程进入阻塞之后,可以调度后面的程序占用CPU,CPU的利用率变高,时间效率也会变高。

      检查答案:
      在这里插入图片描述
      对比两个命令的CPU使用率(-p 参数):
      在这里插入图片描述
      在这里插入图片描述
    1. 现在探索另一些参数。一个重要的参数是 -S,它决定了当进程发出I/O请求时系统如何反应。将参数设置为SWITCH_ON_END,表示进程进程I/O操作时,系统不会切换到另一个进程,而是等待进程完成。当你运行以下两个进程时,会发生什么情况?一个执行I/O,另一个执行CPU工作。(-l 1:0,4:100 -c -S SWITCH_ON_END)

      运行截图 :
      在这里插入图片描述
      答案:第二个程序会等待IO完成返回之后才会执行,此时CPU处于空闲状态,利用率低。
      在这里插入图片描述
    1. 现在,运行相同的进程,但切换行为设置,在等待IO时切换到另一个线进程(-l 1:0,4:100 -c S SWITCH_ON_IO),现在会发生什么?利用-c来确认你的答案是否正确。

      运行截图 :
      在这里插入图片描述
      答案:进程0请求IO,操作系统切换进程1执行,此时CPU利用率大大提高了。
      在这里插入图片描述
    1. 另一个重要的行为是IO完成时要做什么。利用-I IO_RUN_LATER,当IO完成时,发出它的进程不一定马上运行,相反,此时运行的程序会一直运行,当你运行这个进程组合时会发生什么?(./process-run.py -l 3:0,5:100,5:100 -S SWITCH_ON_IO -I IO_RUN_LATER -c -p)系统资源是否被有效利用?

      运行截图 :
      在这里插入图片描述
      在这里插入图片描述
      答案:此时0进程的第一个IO请求完毕之后,也不能立即运行下一个IO请求,必须等待当前进程执行完之后才会被调度。此时IO空闲,但是当前进程完成后,只剩下0进程的IO请求,此时CPU又空闲下来了,资源利用率很低。
    1. 现在运行相同的进程,但使用 -I IO_RUN_IMMEDIATE设置,该设置立即发出IO的进程。这种行为有何不同?为什么运行一个刚刚完成IO的进程会是一个好主意?

      运行截图
      在这里插入图片描述
      答案:这种行为会在IO完成后,进程通知操作系统,操作系统会切换上下文,调度完成IO的进程继续执行,此时操作系统发现该进程又是一个IO请求,在通知IO设备处理之后,就切换另一个进程使用CPU,这样IO和CPU同时进行,利用率变高了。其实我们可以注意到一个细节,IO操作也是会占用CPU的,但是占用时间不长。
      对于为什么说运行一个刚刚完成的IO进程会是一个好主意,我的想法的,IO请求占用CPU时间短,就像进程调度的抢占式短作业优先算法一样,完成时间早的进程会被优先调度一样,这个算法是经过证明的,会提高运行效率。
    1. 现在运行一些随机生成的进程,例如 -s 1 -l 3:50,3:50,-s 2 -l 3:50,3:50, -s 3 -l 3:50,3:50。看看你是否能预测追踪记录会如何变化?当你使用-I IO_RUN_IMMEDIATE设置与-I IO_RUN_LATER时会发生什么?当你使用-S SWITCH_ON_IO与-S SWITCH_ON_END时会发生什么?

      运行截图 :
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解释一下参数:
-s 表示随机数种子seed
3:50 表示3个操作,50%概率使用CPU

答案:随机数种子为1的时候,进程0有三个操作:一个CPU、两个IO,进程1有三个操作:三个都是CPU操作。进程1的3个CPU操作在进程0阻塞的返回之前就完成了。因此花费的时钟时间是进程0执行的时间。随机数种子为2的时候,进程0有三个操作:一个CPU、两个IO,进程1有三个操作:一个CPU、两个IO。两个进程的CPU操作都是在对方IO返回之前完成的。随机数种子为3的时候,进程0有三个操作:两个CPU、一个IO,进程1有三个操作:两个IO、一个CPU。

加上-I参数之后,前两个命令没有变化,因为IO返回时,CPU都是空闲的,因此不存在调度问题。但第三个命令在加上-I IMMEDIATE之后,IO返回时,CPU在执行其他进程,但此时进程通知操作系统它IO完成了,操作系统会马上切换进程,让完成IO的进程继续运行。如果参数是-I LATER,表明即使此时IO返回了,CPU仍然会继续执行正在执行的进程,不会发生调度,对于命令3来说,IO完成的进程必须等待正在运行的进程完成,才能被分配CPU。此时会多花费一个时钟时间等待CPU。

加上-S参数之后,-S SWITCH_ON_IO,就是运行的进程请求IO时放弃CPU,操作系统切换到其他进程继续执行,-S SWITCH_ON_END,就是运行的进程请求IO时,不放弃CPU使用权(即使这时它不使用),相当于串行执行多个进程。显然,第二种做法使得CPU使用率非常低。

猜你喜欢

转载自blog.csdn.net/qq_56919740/article/details/133839972
今日推荐