第六周学习笔记

发布时间 2023-10-27 16:51:38作者: 最好的护盾电池使用者

Linux进程管理

多任务处理

在编程中,多任务处理是指同时执行多个任务或进程的能力。这种能力可以通过并发编程来实现,其中任务可以是同时执行的线程、进程或协程。

进程的概念

进程:进程是对映像的执行
在操作系统内核中,每个进程用一个独特的数据结构表示,叫作进程控制块(PCB)或任务控制块(TCB)等。

多任务处理系统

多任务处理系统简称MT

多任务处理系统的组成部分

type.h文件
/*********** type.h file ************/
#define NPROC   9 //number of PROCs
#define SSIZE 1024 //stack size 4KB
// PROC status
#define FREE    0
#define READY   1
#define SLEEP   2
#define ZOMBIE  3
typedef struct proc{
    struct proc *next;
    int *ksp;
    int pid;
    int status;
    int priority;
    int  kstack [SSIZE];
}PROC;

ts.s文件

ts.s 在32位 GCC 汇编代码中可实现进程上下文切换。

/*********** ts.s file ************/
.globl running, scheduler, tswitch
tswitch:
SAVE: pushl %eax
pushl %ebx
pushl %ecx
pushl %edx
pushl %ebp
pushl %esi
pushl %edi
pushfl
movl running,%ebx # ebx -> PROC
movl %esp,4(%ebx) # PORC.save_sp = esp
FIND: call scheduler
RESUME: rnovl running,%ebx # ebx -> PROC
movl 4(%ebx) ,%esp #esp= PROC.saved_sp
popfl
popl %edi
popl %esi
popl %ebp
popl %edx
popl %ecx
popl %ebx
popl %eax
ret

queue.c文件

queue.c 文件可实现队列和链表操作函数。enqueue()函数按优先级将 PROC 输入队列中。在优先级队列中,具有相同优先级的进程按先进先出(FIFO)的顺序排序。dequeue()函数可返回从队列或链表中删除的第一个元素。printList()函数可打印链表元素。

/*********** queue.c file ************/
int enqueue(PROC **queue,PROC *p)
{
    PROC *q = *queue;
    if(q == 0 || p->priority> q->priority){
        *queue = p;
        p->next = q;
    }
    else{
        while(g->next && p->priority <= q->next->priority)
            q = q->next;
        p->next = q->next;
        q->next = p;
    }
}
PROC *dequeue (PROC **queue)
{
    PROC *p = *queue;
    if (p)
        *queue =(*queue)->next;
    return p;
}
int printList(char *name,PROC *p)
{
    printf("%s = ",name);
    while(p){
        printf("[8d %d]->",p->pid,p->priority);
        p = p->next;
    }
    printf("NULL\n");
}

t.c文件

t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数。

进程同步

睡眠模式

当某进程需要某些当前没有的东西时,它就会在某个事件值上进入休眠状态,该事件值表示休眠的原因。为实现休眠操作,我们可在PROC结构体中添加一个event字段,并实现 ksleep(int event)函数,使进程进入休眠状态。

唤醒操作

当某个等待时间发生时,另一个执行实体(可能是某个进程或中断处理程序)将会调用 wakeup(event)。唤醒正处于休眠状态等待该事件值的所有程序。如果没有任何程序休眠等待该程序,kwakeup()就不工作,即不执行任何操作。

进程终止

正常终止:进程调用 exit(value),发出_exit(value)系统调用来执行在操作系统内核中的 kexit(value)。
异常终止:进程因某个信号而异常终止。当进程终止时,最终都会在操作系统内核中调用 kexit()。

MT系统中的进程管理

实现过程:

  • 用二叉树的形式实现进程家族树。
  • 实现 ksleepO()和kwakeup()进程同步函数。
  • 实现kexit()和kwait()进程管理函数。
  • 添加“w”命令来测试和演示等待操作。

Unix/Linux中的进程

进程来源

当操作系统启动时,操作系统内核的启动代码会强行创建一个PID=0的初始进程。然后系统执行它。在初始化系统后,P0复刻一个子进程P1,并把进程切换为以用户模式运行P1。

INIT和守护进程

P1的大部分子进程都是用来提供系统服务的。它们在后台运行,不与任何用户交互。它们被称为守护进程。

登录进程

除了守护进程,P1复刻了许多登录进程,每个终端上一个,用于用户登录。每个登录进程打开三个与自己的终端相关联的文件流(stdin, stdout, stderr).

sh进程

当用户成功登录时,LOGIN 进程会获取用户的 gid 和 uid,从而成为用户的进程。它将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序 sh。现在,用户进程执行sh,因此用户进程通常称为 sh 进程。它提示用户执行命令。一些特殊命令,如cd(更改目录)、退出、注销等,由sh 自己直接执行。其他大多数命令是各种 bin 目录(如/bin、/sbin、/usr/bin、/usr/local/bin 等)中的可执行文件。对于每个(可执行文件)命令,sh 会复刻一个子进程,并等待子进程终止。子进程将其执行映像更改为命令文件并执行命令程序。子进程在终止时会唤醒父进程 sh,父进程会收集子进程终止状态、释放子进程 PROC 结构体并提示执行另一个命令等。除简单的命令之外,sh 还支持 I/O 重定向和通过管道连接的多个命令。

sh模拟器

带有I/O重定向的单命令

  • 提示用户输入命令行,形式为:
    cmd arg1 arg2 arg3 ··· argn
  • 处理简单命令:
    cmd = "cd" :chdir(arg1) OR chdir(HOME) if no arg1;
    cmd = "exit" : exit(0) to terminate;
  • 对于所有其他命令:
    创建子进程;
    等待子进程终止;
    打印子进程的退出状态码;
    继续执行步骤1;
  • 子进程:首先,假设命令行中没有管道。
    1.处理I/O重定向:
cmd arg1 arg2 ... < infile  // take inputs from infile  
cmd arg1 arg2 ...> outfile  // send outputs to outfile  
cmd arg1 arg2... >> outfile  // APPEND outputs to outfile  

2.通过execve()执行cmd,传递参数
char *myargv[ ], char *env[ ]
至cmd文件,其中myargv[]是一个字符数组*;

myargv[0]->cmd,  
myargv[1]->arg1,  
End with a NULL pointer  

sh可以在PATH环境变量目录中搜索可执行命令,因此sh模拟器也必须这样做。为此,模拟器程序必须将PATH变量标记到用于定位命令文件的各个目录中。

带有管道的命令

  • 在验证了mysh适用于简单命令之后,将其扩展到处理管道。如果命令行有一个符号 |,把它分为头部和尾部,如:
    cmd1 < infile | cmd 2 > outfile
    head= "cmd < infile";tail = "cmd 2 > outfile"
    然后通过以下步骤实现管道。
    1.创建管道
    2.复刻出一个子进程来共享管道
    3.安排一个进程作为管道的写进程,另一个进程作为管道的读进程。
    然后,让各进程execve()其命令(可能带有I/O重定向)。
    多管道:如果命令行包含多个管道符号,则通过递归实现管道。