The Long Walk1.4.2 操作系统概念Operating System Concept

程序代码成为文本段(代码段)。进程除了代码段之外还包括当前2活动,通过程序计数器的值和处理器寄存器的内容来表示,进程还包括堆栈段和数据段,以及堆(heap)是进程运行期间动态分配的内存
程序本身不是进程:程序只是被动实体,而进程是活动实体,有一个程序计数器来表示下一个要执行的命令和相关资源集合。装载可执行文件通常有两种方式:1. 双击可执行文件的图标或是在内存中输入该文件的文件名

进程状态:

  1. 新的:进程正在被创建
  2. 运行:指令正在被执行
  3. 等待:进程等待某个事件的发生(如IO完成或收到信号)
  4. 就绪:进程等待分配处理器
  5. 终止:进程完成执行

进程控制块:
每个进程在操作系统内使用进程控制块(Process Control Block,PCB)来表示。
进程状态:状态包括…
程序计数器:计数器表示进程要执行的下个指令的地址
CPU寄存器:啊哈
CPU调度信息:包括优先级、调度队列的指针和其他调度参数
内存管理信息:基址和界限寄存器的值、页表和段表
记账信息:包括CPU时间、实际使用时间、时间界限、记账数据、作业或进程数量
I/O状态信息:包括分配给进程的I/O设备列表、打开的文件列表
因此PCB是这些信息的仓库,进程间和进程间是不同的

进程调度
进程进入系统时,就会加入作业队列中,该队列中包括系统中的所有进程。驻留在内存中就绪的、等待的进程保存在就绪队列中。该队列通常用链表实现,其头节点指向链表的第一个和最后一个PCB块的指针。每个PCB包括一个指向就绪队列的下一个PCB的指针域。
Linux中的进程表示,是通过C结构的task_struct来表示的:
pid_t pid; //process identifier
long state; //state of the process
unsigned int time; //scheduling information
struct files_sturct *files; //list of open files
struct mm_struct *mm; //address space of this process
这些都是双向链表,内核位当前正在运行的进程保留一个成为current的指针。
如何修改一个task_struct字段,假如操作系统想把当前进程的状态值修改:
current -> state = new_state;

等待特定I/O设备的进程列表称为设备列表,每个设备都有自己的设备列表。当进程被分配到CPU并执行时,可能发生以下情况:

调度程序:
进程在其生命周期中会在各种调度队列之间进行迁移,为了调度,操作系统要按某种方式从队列中选择进程,进程选择是由相应的调度程序决定的(scheduler)
对于批处理系统,进程更多是被提交,而不是马上执行。这种进程被放到大量存储设备的缓冲池中,长期调度程序或作业调度程序从中选择进程,并装入内存进行执行。短期调度程序或CPU调度程序从准备执行的进程中选择进程,并为之分配CPU
这两种调度程序的区别在于执行的频率。短期调度必须频繁的选择新进程。长期调度控制多道程序设计的程度(内存中进程数量)。如果数量稳定,那创建进程的平均速度就等于离开进程进入系统的平均速度。因此只有当进程离开系统后,才会需要调度长期调度程序。
长期调度应该选择一个I/O为主和CPU为主的组合进程,达到最佳性能。
中期调度程序:将进程从内存中移除,降低多道程序设计的程度。之后进程重载内存,从中断处开始执行。成为交换(swapping)

上下文切换
通常执行一个状态保存来保存CUP当前状态,之后执行一个状态恢复。将CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态,这一任务称为上下文切换(context swithc)

进程操作:
进程创建,进程在执行中,能通过创建进程系统调用(create-process system call)创建多个新进程。创建进程称为父进程,新进程称为子进程。新进程可以创建其他进程,从而形成进程树。
操作系统根据唯一的进程标示符(Process identifier,pid)来识别进程
C shell或者Csh是一个用户调用不同子进程的命令行接口,如ls或cat命令

通过fork()系统调用,可以创建新进程,新进程通过复制原来进程的地址空间而成。这种机制允许父进程和子进程方便的进行通信,两个进程都会继续执行位于fork()后的指令。但对于新进程,forx返回值为0,而对于父进程,返回值为子进程的进程标示符。
系统调用fork后,一个进程会使用系统调用exec(),以用新程序取代进程的内存空间。系统调用exec()将二进制文件装入内存,并开始执行。如果子进程运行时没事可做,就采用系统调用wait将其移出就绪队列。

进程通信
如果一个进程不能影响其他进程或被其他进程影响,那此进程是独立的
协作进程需要一种进程间通信机制(interprocess communication,IPC)来允许进程相互交换数据和信息。进程间通信的有两种基本模式:

  1. 共享内存.:在共享内存模式中,建立起一块协作进程共享的内存区域,进程通过向共享内存读或写入数据来交换信息。时间很短
  2. 消息传递:通过在协作进程间交换信息来实现通信。对交换数量少的数据很有用,因为不需要避免冲突。时间更长,因为需要系统调用实现,需要内核的介入

共享内存系统:一块共享内存区域驻留在生成内存段进程的地址空间,其他希望使用此共享内存段段进程必须将其放在地址空间上。
共享内存系统类似于生产者消费者系统,当消费者使用其中一项时,生产者就能生产另一项。他们必须同步,以免消费者消费一个没有生产出来的项
一个web服务器生产(提供)HTML图像和文件,被请求的客户Web浏览器所消费(读取)
无限缓冲对缓冲大小没有限制,消费者不得不等待新的项,但生产者总是生产新的项
有限缓冲,如果缓冲为空,消费者必须等待,如果为满,生产者必须等待
共享缓存是由循环数组和两个逻辑指针实现的:in和out,in指向缓冲中下一个空位,out指向缓冲中第一个满位。当in == out时候,缓冲为空;当(in + 1) % BUFFER_SIZE == out时,缓冲为满

消息传递系统
消息传递工具提供两种操作:发送和接受消息。如果进程P和Q要进行通信,他们之间必须有通信线路。
对于直接通信,需要通信的每个进程必须明确的命名通信的接受者和发送者,send()和receive()定义如下:
send(P, message):发送消息到进程P
receive(Q, message):接受来自进程Q的消息
这种通信线路具有如下属性:

  1. 在需要的每对进程之间建立线路,进程仅需要知道相互通信的标识符
  2. 一个线路只与两个进程相关
  3. 每对进程之间只有一个线路
    以上方案展示了对称寻址,即发送和接受进程必须命名对方以便通信

非对称通信定义如下:
send(P, message):同上
receive(id, message):接受任何进程的消息,变量id设置成与其通信的进程

在间接通信中,通过邮箱或端口来发送和接受消息,每个邮箱都有唯一的标识符。其原语定义如下:
send(A, message):发送一个消息到邮箱A
receive(A, message):接受来自邮箱A的消息

进程或操作系统可以拥有邮箱,如果邮箱为进程所有(是进程地址的一部分),需要区分拥有者和使用者(只能发送和接受)。当拥有邮箱的进程终止,邮箱将消失

同步:消息传递可以是阻塞和非阻塞——同步和异步
阻塞send:发送进程阻塞,直到消息被接受进程或邮箱所接受
非阻塞send:发送进程发送消息并再继续操作
阻塞receive:接受者阻塞,直到有消息可用
非阻塞receive:接收者收到一个有效消息或空消息
当send和receive都阻塞时,则发送者之间就有一个集合点(rendezvous)。当使用阻塞send和receive时,如何解决生产者-消费者问题就不重要了。生产者仅需要调用阻塞send()调用并等待,直到消息被送到接受者或邮箱,当消费者调用阻塞send()调用并等待,当消费者调用receive时,发送阻塞直到有一个消息可用

IPC系统实例:
POSIX共享内存
系统先使用shmget()创建共享内存段
segment_id = shmget(IPC_PRIVATE, size, S_IRUSR | S_IWUSR)
第一个参数指的是共享内存段的关键字
第二个参数是共享内存段的大小(字节数)
第三个参数标示模式,指出读写权限
想访问共享内存段必须采用shmat() (SHard Memory Attach),此调用的三个参数:
第一个是希望加入共享内存段的整数标识符
第二个是内存中的一个指针位置,标示要加入到的共享内存所在
第三个参数是一个标志,标示加入到共享内存区域是读还是写
share_memory = (char*) shmat(id, NULL, 0);
返回一个指向附属的共享内存区域的内存中初始位置的指针
sprintf(share_memory, “Writing to shared memory”);

线程
线程是CPU使用的基本单元,由线程ID、程序计数器、寄存器集合和栈组成。与属于同一进程的其他线程共享代码段、数据段和其他操作系统资源

多线程模型:两种不同方法提供线程支持,用户层用户线程和内核层内核线程
用户线程受内核支持,而无需内核管理
内核线程由操作系统直接支持和管理
多对一模型:将许多用户级线程映射到一个内核线程。线程管理是由线程库在用户空间进行的,因而效率比较高。如果一个线程执行力阻塞系统调用,那整个进程就会阻塞,而且同一时刻只有一个线程可以访问内核,多个线程不能运行在多个处理器上

一对一模型:将每个用户线程映射到一个内核线程,一个线程执行力阻塞系统调用,允许另一个线程继续运行。但每创建一个用户线程就要创建一个内核线程

多对多模型:多路复用了许多用户线程到同样数量或更小数量的内核线程上,但内核一次只能调用一次线程,没有增加并发性

线程库(Thread Library)为程序猿创建和管理线程的API
实现线程库的方法有两种:

  1. 提供一个没有内核支持的库,库内的所有代码和数据结构都在用户空间里。调用库中的一个函数只是导致了用户空间的一个本地函数调用,而不是系统调用
  2. 执行了一个由操作系统直接支持的内核级的库

多线程问题
在多线程中,系统调用fork()和exec()的语句有所改变
如果程序中的一个线程调用fork(),那么新进程会复制所有线程

线程取消是在线程完成之前终止线程的任务,取消线程的方式有两种:
异步取消:一个线程立即终止目标线程
延迟取消:目标线程不断检查它是否应终止,允许线程以有序方式结束自己

发布了47 篇原创文章 · 获赞 10 · 访问量 1724

猜你喜欢

转载自blog.csdn.net/Antonio_Salieri/article/details/102298150