操作系统(概述+进程管理)

一、操作系统概述

基本特征

1. 并发

并发:一段时间内运行多个程序;
并行:同一时刻运行多个指令;
并行需要硬件支持,如多流水线、多核处理器、分布式计算机系统;
并发是操作系统引入进程和线程。

2. 共享

系统资源被多个并发进程共同使用;
互斥共享:临界资源,同一时刻仅允许一个进程访问;
同时共享。

3. 虚拟

将一个物理实体转为多个逻辑实体。
时分复用:多个进程在同一个处理上并发执行,每个进程轮流占用处理器,每次执行一个小时间片并快速切换。
空分复用:虚拟内存,将物理内存抽象成地址空间,每个进程都有自己的地址空间。地址空间的页被映射到物理内存,地址空间的页不需要全部在物理内存中,需要的时候执行页面置换算法,将页置换到内存中。

  1. 异步
    不是一次性执行完毕,而是走走停停。如时间片轮转。

基本功能

1. 进程管理

进程控制、进程同步、进程通信、死锁处理、处理机调度等。

2. 内存管理

内存分配、地址映射、内存保护与共享、虚拟内存。

3. 文件管理

文件存储空间管理、目录管理、文件读写管理与保护。

4. 设备管理

完成用户的IO请求。
缓冲管理、设备分配、设备处理、虚拟设备。

系统调用

用户态 vs 内核态
一个进程在用户态需要使用内核态的功能,进行系统调用陷入内核态,由操作系统完成。
Linux系统调用举例:

TaskCommands
进程控制fork(); exit(); wait()
进程通信pipe(); shmget(); mmap()
文件操作open(); read(); write()
设备操作ioctl(); read(); write()
信息维护getpid(); alarm(); sleep()
安全chmod(); umask(); chown()

大内核和微内核

1. 大内核

将操作系统功能作为一个紧密结合的整体放到内核。
由于各模块共享信息,因此有很高的性能。

2. 微内核

将部分功能移除内核,降低内核的复杂性。
需要频繁的在内核态和用户态之间切换,因此有一定的性能损失。

中断

1. 外中断

由CPU执行指令以外的事件引起;
I/O完成终端,表示设备输入/输出已经完成,可以进行下一个输入输出请求。
时钟中断、控制台中断。

2. 异常

由CPU执行指令的内部事件引起,非法操作码、地址越界、算术溢出等。

3. 陷入

用户程序中使用系统调用。

二、进程管理

进程与线程

1. 进程

进程是资源分配的基本单位;
进程控制块(Process Control Block, PCB)描述进程的基本信息和运行状态,进程创建撤销都是对PCB操作。

2. 线程

线程是独立调度的基本单位。

一个进程可以有多个线程,它们共享进程资源。

QQ和浏览器是两个进程,浏览器进程中有很多线程,例如HTTP请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新连接而发起HTTP请求时,浏览器还可以响应用户的其他事件。

引申阅读:为什么浏览器是多进程模型?

3. 区别

a) 拥有资源
进程是资源分配的基本单位,线程可以访问隶属的进程的资源。

b) 调度
线程是基本的调度单位,同一进程中线程切换不会引起进程切换,但是不同进程中的线程切换会引起进程切换。

c) 系统开销
进程切换:开销大,分配挥手资源,内存空间IO设备等。所付出的开销远大于创建或撤销线程时的开销。
线程切换:仅保存和设置少量寄存器内容,开销小。

d) 通信
线程:直接读写同一进程中的数据;
进程:借助IPC.

进程状态切换

进程状态

  • 就绪状态(ready) : 等待被调度;
  • 运行状态(running)
  • 阻塞状态(waiting) : 等待资源;

(这里需要补一张状态转移图)

阻塞状态时缺少需要的资源从运行状态转换来的,但是资源不包括CPU时间,缺少CPU时间从运行状态转为就绪状态。

进程调度算法

1. 批处理系统

1.1 先来先服务(FCFS)
非抢占式。
有利于长作业,不利于短作业,短作业需要等待很长时间。

1.2 短作业优先(SJF)
非抢占式。
有利于短作业,不利于长作业,长作业需要一直等待。

1.3 最短剩余时间(SRTN)
抢占式,按剩余时间的顺序进行调度。

2. 交互式系统

2.1 时间片轮转
将所有进程按FCFS顺序排列,依次按固定时间片调度。

时间片太小,导致进程切换频繁,消耗太大。
时间片过大,实时性得不到保证。

2.2 优先级调度
每个进程分配一个优先级。
随时间推移,等待的进程的优先级慢慢增加,防止优先级低的进程永远得不到调度。

2.3 多级反馈队列
图形说明。

3. 实时系统

实时系统要求在一个确定时间内得到响应。

  • 硬实时:军工等,必须满足绝对截至时间。
  • 软实时: 容忍一定的超时。

进程同步

1. 临界区

对临界资源访问的那段代码是临界区。
为了互斥访问临界资源,每个进程进入临界区之前需要进行检查。

2. 同步与互斥
  • 同步:多个进程因为合作而产生制约关系,使得进程偶一定的先后执行顺序。
  • 互斥:多个进程在同一时刻只有一个进程能进入临界区。
3. 信号量

Semaphore是一个整型变量,可以对其执行down和up操作,即常见的P和V操作。

  • down: 如果Semaphore>0,执行-1操作;如果Semaphore==0,进程睡眠,等待信号量大于0;
  • up: 对信号量执行+1操作,唤醒睡眠的进程让其完成down操作。

down和up是原子性的,不可拆分,通常做法是执行这些操作的时候屏蔽中断。

如果Semaphore只能取0/1,就是互斥量(Mutex),0表示加锁,1表示临界区解锁。

在执行临界区资源的时候进行加锁。

typedef int semaphore;
semaphore mutex = 1;
void P1() {
    down(&mutex);
    // 临界区
    up(&mutex);
}

void P2() {
    down(&mutex);
    // 临界区
    up(&mutex);
}

** 使用信号量实现生产者-消费者问题 **
问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。

因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。

为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。其中,empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才可以放入物品;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走物品。

注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,消费者就无法执行 up(empty) 操作,empty 永远都为 0,导致生产者永远等待下,不会释放锁,消费者因此也会永远等待下去。

#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;

void producer() {
    while(TRUE) {
        int item = produce_item();
        down(&empty);
        down(&mutex);
        insert_item(item);
        up(&mutex);
        up(&full);
    }
}

void consumer() {
    while(TRUE) {
        down(&full);
        down(&mutex);
        int item = remove_item();
        consume_item(item);
        up(&mutex);
        up(&empty);
    }
}
4. 管程

使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。

c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法,客户端代码通过调用这两个方法来解决生产者-消费者问题。

monitor ProducerConsumer
    integer i;
    condition c;

    procedure insert();
    begin
        // ...
    end;

    procedure remove();
    begin
        // ...
    end;
end monitor;

管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。

管程引入了 条件变量 以及相关的操作:wait() 和 signal() 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。

** 使用管程实现生产者-消费者问题 **

// 管程
monitor ProducerConsumer
    condition full, empty;
    integer count := 0;
    condition c;

    procedure insert(item: integer);
    begin
        if count = N then wait(full);
        insert_item(item);
        count := count + 1;
        if count = 1 then signal(empty);
    end;

    function remove: integer;
    begin
        if count = 0 then wait(empty);
        remove = remove_item;
        count := count - 1;
        if count = N -1 then signal(full);
    end;
end monitor;

// 生产者客户端
procedure producer
begin
    while true do
    begin
        item = produce_item;
        ProducerConsumer.insert(item);
    end
end;

// 消费者客户端
procedure consumer
begin
    while true do
    begin
        item = ProducerConsumer.remove;
        consume_item(item);
    end
end;

经典同步问题

1. 读者写者问题
2. 哲学家进餐问题

进程通信

  • 进程同步:控制多个进程按一定顺序执行;
  • 进程通信:进程间传输信息。
    进程通信是手段,进程同步是目的。为了实现进程同步,需要进程通信,传输一些进程同步所需要的信息。
1. 管道

管道是pipe函数创建的,fd[0]用于读,fd[1]用于写。

/*=======================================================================
#     FileName: pipe1.c
#         Desc: an example of pipe communication application
#      Version:  
#      History: 
 
========================================================================*/
#include <sys/wait.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{    
	pid_t pid;
	int fd[2];
	int read_count = 0;
	int i;
	char read_buffer[10] = {0};
	char write_buffer[10] = {0};
 
	/*create pipe*/
	if (pipe(fd) < 0)
	{
		printf("Create pipe failed\n");
		return -1;
	}
 
	/*create process*/
	if ((pid = fork()) < 0)
	{
		printf("Fork failed\n");
		return -1;
	}
 
	/* child */
	if (pid == 0)
	{
		printf("[child]Close read endpoint...\n");
		/* close read */
		close(fd[0]);   		
		for(i=0;i<10;i++)
		{
			write_buffer[i]=i;	
		}
		write(fd[1],write_buffer,i);
 
	}
	 /*father*/
	else
	{
		printf("[parent]Close write endpoint...\n");
		/* close write */
		close(fd[1]);   
		read_count = read(fd[0], read_buffer, 10);
		printf("father process read data\n");
		for(i=0; i<read_count; i++)
		{
			printf("read_buffer[%d] = %d\n",i,read_buffer[i]);
		}
	}
}

有以下限制:

  • 只支持半双工通信(单双交替传输);
  • 只能父子进程或者兄弟进程中使用。
2. FIFO

命名管道,去除了管道只能在父子进程中使用的限制。
FIFO常用于客户-服务器应用程序中,FIFO作为汇聚点,在客户进程和服务器进程之间传递数据。

插图

3. 消息队列

相比于FIFO,有以下优点:

  • 消息队列可以独立于读写进程存在,从而避免了FIFO中同步管道的打开和关闭时产生的困难。
  • 避免了FIFO的同步阻塞问题,不需要进程自己提供同步方法。
  • 读写进程可以根据消息类型有选择地接受消息。
4. 信号量

它是一个计数器,用于多个进程提供对共享数据对象的访问。

5. 共享内存

多个进程共享一个给定的存储区。因为数据不需要在进程间复制,因此这是最快的IPC。

需要使用信号量来同步对共享存储的访问。

多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用内存的匿名段。

6. 套接字(Socket)

与其它通信机制不同的是,它可用于不同机器间的进程通信。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值