学习笔记6

第3章学习笔记

Unix/Linux进程管理

3.1 多任务处理

多任务处理指的是同时进行几项独立活动的能力。在单处理器(单CPU)系统中,一次只能执行一个任务。多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。

只要切换得够快,就会有一种同时执行所有任务的错觉。这种逻辑性行为被称为“并发”。

3.2 进程的概念

操作系统中,任务也被称为进程。实际应用中可以互换使用。

在第2章中,我们把执行映像定义为包含执行代码、数据和堆栈的存储区。进程的正式定义:

*** 进程是对映像的执行 ***

操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的CPU时间。在操作系统内核中,每个进程用一个独特的数据结构表示,叫做进程控制块(PCB)。本书中直接称它为PROC结构体,PROC结构体包含了某个进程的所有信息。
PROC

next指向下一个PROC结构体的指针,用于在各种动态数据结构(链表和队列)中维护PROC结构体;

ksp字段是保存的堆栈指针。当某进程放弃使用CPU时,他会将执行上下文保存在堆栈中,并将指针保存在PROC.ksp中,便于恢复;

pid是一个进程ID编号;ppid是父进程ID编号;

status是进程当前状态;

priority是进程调度优先级;

kstack是进程执行时的堆栈。

*** 内核通常会定义有限数量的结构体 ***定义为

'''
PROC procNPROC; //NPROC a constant, e.g. 64
'''

3.3 多任务处理系统MT

3.3.1 type.h文件

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

3.3.2 ts.s文件

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

3.3.3 queue.c文件

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

3.3.4 t.c 文件

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

t.c1

t.c2

t.c3

t.c4

t.c5

t.c6

3.3.5 多任务处理系统代码介绍

3.4 进程同步

一个操作系统包含许多并发进程,这些进程可以彼此交互。进程同步是指控制和协调进程交互以确保其正确执行所需的各项规则和机制。最简单的进程同步工具就是休眠和唤醒操作。

3.4.1 睡眠模式

当某进程需要某些当前没有的东西时,例如申请独占一个存储区域、等待用户通过标准输入来输入字符等,它就会在某个事件值上进入休眠状态,该事件值表示休眠的原因。为实现休眠操作,我们可在PROC结构体中添加一个event字段,并实现ksleep(int event)函数,使进程进入休眠状态。修改如下:

*** 加粗显示的添加字段 ***

sleep1

sleep2

由于休眠进程不在readyQueue中,所以它在被另一个进程唤醒之前不可运行。因此,在让自己进入斜面状态之后,进程调用tswich()来放弃使用CPU。

3.4.2 唤醒操作

当某个等待时间发生时,另一个执行实体将会调用kwakeup(event),唤醒正处于休眠状态等待该事件值的所有程序。如果没有任何程序休眠等待该程序,kwakeup()就不工作。kwakeup()算法:

kwakeup

js:被唤醒的进程可能不会立即运行,它只是被放入readyQueue中排队。

ksleep()和kwakeup()函数一般用于进程同步,但在特殊情况下也用于同步父进程和子进程,下文具体论述。

3.5 进程终止

俗称:进程终止或进程死亡。

进程能以两种方式终止:

*** 正常终止 ***:进程调用exit(value),发出_exit(value)系统调用来执行在操作系统内核中的kexit(value),这就是我们本节要讨论的情况。

*** 异常终止 ***:进程因某个信号而异常终止。信号和信号处理将在第六章讨论。

两种情况最终都会在操作系统内核中调用kexit()。

3.5.1 kexit()的算法

kexit

在所有类Unix系统中,进程P1(又叫INIT进程)将所有其他的孤儿进程,不论死亡还是活跃都被送到P1中,成为P1的子进程。

3.5.2 进程家族树

通常,进程家族树通过个PROC结构中的一对子进程和兄弟进程指针以二叉树的形式实现,如:

'''
PROC *child, *slibling, *parent;
'''

二叉树

每个垂直链接都是child指针,每一个水平链接都是slibling指针。

3.5.3 等待子进程终止

在任何时候,进程都可以调用内核函数

'''
pid = kwait(int *status)
'''

来等待僵尸子进程,如果成功,则返回的pid是僵尸子进程的pid,而status包含僵尸子进程等待僵尸子进程。 此外,kwait()还会将僵尸子进程释放回freeList以便重用。

在kwait算法中,如果没有子进程,则进程会返回-1,表示错误。否则,他将搜索僵尸子进程。如果他找到僵尸子进程,就会收集僵尸子进程的pid和退出代码。相应的,当进程终止时,他必须发出:

'''
kwakeup(running->parent)
'''

唤醒父进程

3.6 MT系统中的进程管理

3.6.1 进程来源

强行创建PID=0初始进程—>系统执行初始进程P0—>挂载一个跟文件系统—>P0复刻出一个子进程P1

3.6.2 INIT和守护进程

3.6.3 登陆进程

3.6.4 sh进程

当用户成功登录时,LOGIN进程会获取用户的gid和uid,从而称为用户的进程。他将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序sh。

3.6.5 进程的执行模式

在Unix/Linux中进程以两种不同的模式执行,即内核模式和用户模式

进程管理

一旦进入Umode就不能随意更改CPU的状态。

3.7 进程管理的系统调用

3.7.1 fork()

int pid = fork()
fork()创建子进程并返回子进程的pid。

3.7.2 进程终止

(1)正常终止:当内核中的某个进程终止时,他会将_exit(value)系统调用中的值记录为进程PROC结构体中的退出状态。并通知他的二父进程并使该进程成为僵尸进程。父进程课通过系统调用找到僵尸子进程,获得其pid和退出状态

'''
pid=wait(int *status)
'''

(2)异常终止:当某进程遇到异常时,他会陷入操作系统内核。内核的异常处理程序将陷阱错位类型转换为一个幻数,称为信号,将信号传递给进程,时进程终止。用户可以使用命令

'''
kill -s signal_numeber pid
'''

向通过pid识别的目标发送信号。

3.7.3 等待子进程终止

在任何时候,一个进程都可以使用

'''
int pid = wait(int *status);
'''

系统调用,等待僵尸子进程。

3.6.4 环境变量

各环境变量定义为:

关键字=字符串

重要环境变量:

'''
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中。

在sh会话中,可以将环境变量设置为新的(字符串)值,如:

'''
HOME= / home / newhome
'''

可通过EXPORT命令传递给后代sh,如

'''
expoert HOME
'''

3.8 I/O重定向

sh进程有三个用于终端IO的文件流: stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。每个流都是指向执行映像堆区中FILE结构体的一个指针,如下文所示。

image.png

3.9 管道

管道时用于进程交换数据的单向进程件通信通道。管道有一个读取端和一个写入端。

3.9.1 管道命令处理

在Unix/Linux中,命令行

'''
cmd1 | cmd2
'''

sh将通过一个进程运行cmd1,并通过另一个进程运行cmd2,他们通过一个管道连接在一起,因此cmd1的输出变为cmd2的输入

3.9.2、命令管道

命令管道又叫FIFO

(1)在sh中,通过mknod命令创建一个命令管道:

'''
mknod mypipe p
'''

(2)或在c语言中发出mknod()系统调用

'''
int r = mknod("mypipe",s_IFIFP,0);
'''

(3)进程可像访问普通文件一样发个文命名管道。

编程练习

1.grep练习

1

2. fork练习

参考https://blog.csdn.net/Always2015/article/details/45008785
fork()函数又叫计算机程序设计中的分叉函数,fork是一个很有意思的函数,它可以建立一个新进程,把当前的进程分为父进程和子进程,新进程称为子进程,而原进程称为父进程。fork调用一次,返回两次,这两个返回分别带回它们各自的返回值,其中在父进程中的返回值是子进程的PID,而子进程中的返回值则返回 0。因此,可以通过返回值来判定该进程是父进程还是子进程。还有一个很奇妙的是:fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。

2

3

posted @ 2021-10-24 21:43  氧气2019  阅读(43)  评论(0编辑  收藏  举报