《信息安全系统设计与实现》第七周学习笔记
第三章Unix/Linux进程管理
多任务处理
- 多任务处理简单说,就是同一时间给多个程序运行处理数据。即使在系统中通常有许多其他的程序在运行,但进程也可以向每个程序提供一种假象,仿佛它在独占地使用处理器。但事实上进程是轮流使用处理器的
- 逻辑并行性称为“并发”
- 多任务处理是所有操作系统的基础,也是并行编程的基础
进程
- 定义: 进程是对映像的执行
- 操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的CPU时间。PROC结构体包含了某个进程的所有信息。
-
- next是指向下一个PROC结构体的指针
- ksp保存的堆栈指针
- pid是一个进程的进程编号
- status是当前状态
- priority是进程调度优先级
- kstack是进程执行时的堆栈
多任务处理系统
- type.h文件
- 文件定义了系统常熟和表示进程的简单PROC结构体
-
#define FREE 0 #define READY 1 #define SLEEP 2 #define ZOMBIE 3 typedef struct proc { struct proc *next; int *ksp; int pid; int ppid; int status; int priority; int kstack[SSIZE]; }
-
- 文件定义了系统常熟和表示进程的简单PROC结构体
- ts.s文件
- 在32位GCC汇编代码中可实现进程上下文切换
- queue.c文件
- 可实现队列和链表操作
- enqueue()函数按优先级将PROC输入队列中。在优先级队列中,具有相同优先级的进程按照 FIFO的顺序排序。
- dequeue()函数可返回从队列或链表中删除的第一个元素。
- printList()函数可打印链表元素。
- 可实现队列和链表操作
- t.c文
- 件定义MT系统数据结构、系统初始化代码和进程管理函数
进程同步
- 一个操作系统包含许多并发进程,这些进程可以彼此交互。进程同步是指控制和协调进程交互以确保其正确执行所需要的各项规则和机制。最简单的进程同步工具是休眠和唤醒。
- 睡眠模式:当某进程需要某些当前没有的东西时,例如申请独占一个存储区域、等待用户通过标准输入来输入字符等,它就会在某个事件值上进入休眠状态,该事件值表示休眠的原因
- 唤醒模式:多个进程可能会进入休眠状态等待同一个事件,这些进程可能都需要同一个资源,例如一台当前正处于繁忙状态的打印机。在这种情况下,所以这些进程都将休眠等待同一个事件值
进程终止
- 正常终止:进程调用exit(value),发出_exit(value)系统调用来执行在操作系统内核中的kexit(value)
- 异常终止:进程因某个信号而异常终止
MT系统中的进程管理
- 用二叉树的形式实现进程家庭树
- 实现ksleep()和kwakeup()进程同步函数
- 实现kexit()和kwait进程管理函数
- 添加“w”命令来测试和演示等待操作
Unix/Linux中的进程
- 进程来源
- 当操作系统启动时,操作系统内核的启动代码会强行创建一个PID=0初始进程
执行初始进程P0
- 当操作系统启动时,操作系统内核的启动代码会强行创建一个PID=0初始进程
- INIT和守护进程
- 当进程P1开始运行时,它将其执行映像更改为INIT程序。因此,P1通常被称为INIT进程 因为它的执行映像是init程序。P1 开始复刻出许多子进程
- P1的大部分子进程都是用来提供系统服务的。它们在后台运行,不与任何用户交互
- 登录进程
- P1复刻了许多LOGIN进程,每个终端上一个,用于用户登录
- sh进程
- 当用户成功登录时,LOGIN进程会获取用户的gid和uid,从而称为用户的进程。他将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序sh
- 进程的执行模式
- 在Unix/Linux中进程以两种不同的模式执行,即内核模式和用户模式,简称Kmode和Umode。在每种执行模式下,一个进程有一个执行映像
进程管理的系统调用
- fork()
- fork()创建子进程并返回子进程的pid,如果fork()失败则返回-1
- 进程执行顺序
- 在folk()完成后,子进程与父进程和系统中所有其他进程竞争CPU运行时间。接下来运行哪个进程取决于他们的调度优先级,优先级呈动态变化
- 进程终止
- 正常终止:当内核中的某个进程终止时,他会将_exit(value)系统调用中的值记录为进程PROC结构体中的退出状态。并通知他的二父进程并使该进程成为僵尸进程。父进程课通过系统调用找到僵尸子进程,获得其pid和退出状态
pid=wait(int *status)
- 异常终止:当某进程遇到异常时,他会陷入操作系统内核。内核的异常处理程序将陷阱错位类型转换为一个幻数,称为信号,将信号传递给进程,时进程终止
- 用户可以使用命令
kill -s signal_numeber
pid向通过pid识别的目标发送信号
- 用户可以使用命令
- 正常终止:当内核中的某个进程终止时,他会将_exit(value)系统调用中的值记录为进程PROC结构体中的退出状态。并通知他的二父进程并使该进程成为僵尸进程。父进程课通过系统调用找到僵尸子进程,获得其pid和退出状态
- 等待子进程终止
- 在任何时候,一个进程都可以使用
int pid = wait(int *status)
系统调用,等待僵尸子进程。如果成功,则wait()会返回僵尸子进程的PID,而且status包含僵尸子进程的exitCode。此外,wait()还会释放僵尸子进程,以供重新使用。
- 在任何时候,一个进程都可以使用
- exec():更改进程执行映像
- 进程可以使用exec()将Umode映像更改为不同的(可执行)文件
- exec()库函数有几个成员
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg,...); int execle(const char *path, const char *arg,..., char * const envp[]); int execv(const char *path, char *const argv[]);
- 进程可以使用exec()将Umode映像更改为不同的(可执行)文件
- 环境变量
- 环境变量是为当前sh定义的变量,由子sh或进程继承。他们定义了后续程序的执行环境。各环境变量定义为:关键字=字符串
- 在sh会话中,用户可使用env或printenv命令查看环境变量
-
SHELL=/bin/bash TERM=xterm USER=kcw PATH=/usr/1oca1/bin:/usr/bin:/bin:/usr/local/games:/usr/games:./ HOME= / home /kcw
- SHELL:指定将解释任何用户命令的sh
- TERM:指定运行sh时要模拟的终端类型
- USER:当前登录用户
- PATH:系统在查找命令时将检查的目录列表
- HOME:用户的主目录。在 Linux 中,所有用户主目录都在/home中
I/O重定向
- I/O重定向原则
- 标准输入
- 如果用一个新打开的文件来替换文件描述符0,那么输入将来自该文件而不是原始输入设备。
- 标准输出
- 更改文件描述符1,指向打开的文件名,然后stdout的输出将会转到该文件而不是屏幕;同样也可以将stderr重定向到一个文件
- 标准输入
管道
- 管道是Linux由Unix那里继承过来的进程间的通信机制,它是Unix早期的一个重要通信机制。其思想是,在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息。由于这种方式具有单向传递数据的特点,所以这个作为传递消息的共享文件就叫做“管道”
管道时用于进程交换数据的单向进程件通信通道。管道有一个读取端和一个写入端。
- 管道命令处理
cmd1 | cmd2
- sh将通过一个进程运行cmd1,并通过另一个进程运行cmd2,他们通过一个管道连接在一起,因此cmd1的输出变为cmd2的输入.
- 将管道写进程与管道读进程连接起来
- 当sh获取命令行cmd1|cmd2时,会复刻出一个子进程sh,并等待子进程sh照常终止
- 子进程sh:浏览命令行中是否有|符号。 在这种情况下
cmd|cmd
- 然后,子进程执行代码段
- 命令管道(命令管道又叫FIFO)
- 在sh中,通过mknod命令创建一个命令管道:
mknod mypipe p
- 或在c语言中发出mknod()系统调用:
int r = mknod("mypipe",S_IFIFO,0);
- 输入
ls -l mypipe
- 显示为
prw-r-r- 1 root root 0 time mypipe
- 进程可像访问普通文件一样发个文命名管道。
- 在sh中,通过mknod命令创建一个命令管道:
苏格拉底挑战:
遇到的问题及解决
问题
- 进程的调度是如何进行的?进程调度优先级和进程状态之间的关系吗?
解决
- 进程调度是操作系统中的一个关键组成部分,它负责确定哪个进程在给定时间段内执行,并以什么顺序执行。在多任务操作系统中,可能有多个进程竞争CPU运行时间。进程调度的目标是合理分配CPU时间,以满足各个进程的需求,并尽量提高系统的效率。
- 程状态和进程调度优先级之间存在紧密的关系。当一个进程创建时,它通常处于就绪状态,并被放置在适当的进程队列中,同时分配一个初始的调度优先级。随着时间的推移,进程可能会从就绪状态切换到运行状态,获得CPU时间执行,然后再切回就绪状态。如果一个进程在执行过程中发生I/O操作等等需要等待的事件,它可能会切换到阻塞状态。