第三章学习笔记

发布时间 2023-12-17 21:10:20作者: 20211112周子凯

@

一、学习笔记

1.多任务处理

多任务处理指同时执行几个独立的任务。在单CPU系统中一次只能执行一个任务。多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务,这种切换机制称为上下文切换。如果切换速度足够快,就给人一种同时执行所有任务的错觉,这种逻辑并行性成为并发。多个CPU的多处理系统中,可在不同的CPU上实时、并行执行多项任务。

2.进程

在操作系统中,任务也成为进程。进程是对映像的执行。每个进程用一个独特的数据结构表示,叫做进程控制块(PCB)或任务控制块(TCB),可直接称为PROC结构体,它包含某个进程的所有信息。

3.多任务处理系统

3.1.type.h 文件
type.h 文件定义了系统常数和表示进程的简单 PROC 结构体。

/*********** type.h file ************/
#define NPROC   9
#define SSIZE 1024
// 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;

3.2.ts.s 文件
ts.s 在32位 GCC 汇编代码中可实现进程上下文切换。
3.3.queue.c 文件
queue.c 文件可实现队列和链表操作函数。enqueue()函数按优先级将 PROC 输入队列中。在优先级队列中,具有相同优先级的进程按先进先出(FIFO)的顺序排序。dequeue()函数可返回从队列或链表中删除的第一个元素。printList()函数可打印链表元素。
3.4.tc.c 文件
tc.c 文件定义 MT 系统数据结构、系统初始化代码和进程管理函数。
3.5.多任务处理系统代码介绍
(1)虚拟 CPU:MT 系统在 Linux 下编译链接为
gcc -m32 t.c ts.s
然后运行 a.out。
(2)init():当 MT系统启动时,main()函数调用init()以初始化系统。init()初始化 PROC 结构体,并将它们输入 freeList()。它还将 readyQueue()初始化为空。然后使用 proc[0]创建 P0,作为初始运行进程。P0的优先级最低,为0。所有其他任务的优先级都是1,因此它们轮流从 readyQueue 运行。
(3)P0调用fkork()来创建优先级为1的子进程P1,并将其输入就绪队列中。然后P0调用tswitch(),将会切换任务以运行P1.
(4)tswitch():tswitch()函数实现进程上下文切换。它由三个独立的步骤组成
(4.1)SAVE函数:当正在执行的某个任务调用tswitch()时,他会把返回地址保存在堆栈上,并在汇编代码中进入tswitch()。
(4.2)scheduler():在执行了tswitch()中的SAVE函数之后,任务调用scheduler()来选择下一个正在运行的任务。
(4.3)RESUME函数:当执行从scheduler()返回时,“运行”可能已经转而指向另一个任务的PROC。运行指向的那个PROC,就是当前正在运行的任务。tswitch()中的RESUME函数将CPU的堆栈指针设置为当前运行任务的已保存堆栈指针。
(5)kfork():kfork()函数创建一个子任务并将其输入readyQuene中。
(6)body()
(7)空闲任务 P0:P0的特殊之处在于他在所有任务中具有最低的优先级。
(8)运行多任务处理(MT)系统:在Linux下输入
gcc -m32 t.c s.s
编译链接MT系统并运行所得到的a.out。

4.进程同步

4.1.睡眠模式:ksleep(int event)
当某进程需要某些当前没有的东西时,它就会在某个事件值上进入休眠状态。为实现休眠操作,我们可以在 PROC结构体中添加一个event字段,并实现ksleep(int event)函数,使进程进入休眠状态。接下来,我们将假设对 PROC结构体进行修改以包含加粗显示的添加字段。

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

5.进程终止

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

6.MT系统中的进程管理

完善基础MT系统,实现MT系统的进程管理函数:
(1)用二叉树的形式实现进程家族树。
(2)实现 ksleepO()和kwakeup()进程同步函数。
(3)实现kexit()和kwait()进程管理函数。
(4)添加“w”命令来测试和演示等待操作。

修改后的MT系统的输出示例:
在这里插入图片描述

7.Unix/Linux中的进程

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

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

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

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

7.5.进程的执行模式
(1)中断:中断是外部设备发送给 CPU的信号,请求 CPU 服务。当在 Umode 下执行时,CPU 中断是启用的,因此它将响应任何中断。在中断发生时,CPU 将进入 Kmode 来处理中断,这将导致进程进人 Kmode。
(2)陷阱:陷阱是错误条件,例如无效地址、非法指令、除以0等,这些错误条件被 CPU识别为异常,使得 CPU 进入 Kmode 来处理错误。在 Unix/Linux 中,内核陷阱处理程序将陷阱原因转换为信号编号,并将信号传递给进程。对于大多数信号,进程的默认操作是终止。
(3)系统调用:系统调用(简称syscall)是一种允许 Umode 进程进入 Kmode 以执行内核函数的机制。当某进程执行完内核函数后,它将期望结果和一个返回值返回到 Umode,该值通常为0(表示成功)或-1(表示错误)。如果发生错误,外部全局变量errno(在 errno.h中)会包含一个 ERROR 代码,用于标识错误。

8.进程管理的系统调用

8.1fork()
int pid = fork();
fork()创建子进程并返回子进程的pid,如果失败则返回-1。
8.2进程执行顺序
fork()完成后,运行哪个进程取决于它们的优先级。
8.3进程终止
正常终止:
pid = wait(int *status);
异常终止:
kill -s signal_number pid
8.4等待子进程终止
int pid = wait(int *status);
int pid = waitpid(int pid,int *status,int options);
8.5subreaper进程
prct1(PR_SET_CHILD_SUBREAPER);
这样,init进程P1将不再是孤儿进程的父进程。
8.6exec()
更改进程执行映像:
int execve(const char *filename,char *const argv[],char *const envp[]);
8.7环境变量
SHELL:指定将解释任何用户命令的sh
TERM:指定运行sh时要模拟的终端类型
USER:当前登录用户
PATH:系统在查找命令时将检查的目录列表
HOME:用户的主目录

9.I/O重定向

stdin:标准输入
重定向:
int fd = open("filename",O_RDOMLY);
close(0);
dup(fd);
stdout:标准输出
重定向:
close(1);
open("filename",O_WRONLY|O_CREAT,0644);
stderr:标准错误

10.管道

管道是用于进程交换数据的单向进程间通信通道,有一个读取端和一个写入端。
10.1管道编程
int pd[2];
int r = pipe(pd);
10.2管道命令处理
ps x |grep "httpd"
cat filename | more
10.3命名管道—FIFO
sh中:
mknod mypipe p
C程序中:
int r = mknod("mypipe",S_IFIFO,0);

二、苏格拉底挑战

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

三、实践过程及截图

代码:

include <stdio.h>

include <stdlib.h>

include <unistd.h>

int main(int argc, char * argv[])
{
int pid;
/* fork another process /
pid = fork();
if (pid < 0)
{
/
error occurred /
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid == 0)
{
/
child process /
printf("This is Child Process!\n");
}
else
{
/
parent process /
printf("This is Parent Process!\n");
/
parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!\n");
}
}

过程截图
在这里插入图片描述
在这里插入图片描述

四、问题及解决方法

在这里插入图片描述