操作系统(第四周 第二堂)

简介: 操作系统(第四周 第二堂)



回顾

上文的重点就两个内容:一、进程调度的理解;二、队列图以及调度程序

其中进程调度的理解有:一、进程本身角度调度理解;二、计算机整体角度理解进程管理和调度

队列图共有五个队列:I/O队列、中断队列、就绪队列、时间片过期队列、创建子进程队列

调度程序:长期调度程序、短期调度程序

进程运行

研究完进程调度,现在我们来研究进程的运行,研究进程运行前要先看进程是如何创建、删除的

一个进程的一生包括:创建、工作、被调度、删除

进程的创建

所有进程(除了Pid=0的初始进程)都是由父进程创建产生

定义:创建进程称为父进程,被创建进程称为子进程

每一个进程都采用进程标识符来唯一确定进程,子进程和父进程得pid不同

进程创建必备的两个指令:fork()、exec()

fork():

一、创建子进程,子进程和父进程完全相同(虚拟地址也相同,但是物理空间实际地址不同)。完全相同也意味着父进程fork后的程序计数器为fork函数结束位置,而子进程的程序计数器也为fork函数结束位置

二、在父进程中返回子进程的pid,在子进程中返回0

三、创建子进程也意味着要在内核中新建PCB(进程控制块)

exec():

一、以新程序来取代原进程的内存空间,包括程序、栈等进程所有的成分

二、exec后子进程就有了新的虚拟地址空间,可以认为“脱离”父进程的限制

三、exec后子进程的PID保持不变

进程的工作

进程是并行工作的,即子进程被父进程创建后和父进程一起工作(父进程利用wait(NULL)除外,该方法让父进程在子进程结束后才工作)

举例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
// 测试创建子进程函数 pid_t fork();
int main()
{
    pid_t pid = fork();
    if(-1 == pid)
    {
        //创建子进程失败
        return -1;
    }
    if(0 == pid)
    {
        //子进程
        printf("I am child, my fork:%d\n", pid);
        printf("I am child, my pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(5);
        printf("I am child, I have finished\n");  
    }
    else
    {
        //父进程 
        printf("I am father, my fork:%d\n", pid);      
        printf("I am father, my pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(5);
        printf("I am father, I have finished\n");   
    }
    return 0;
}

执行结果为:

程序运行的关键点:

1、父进程先运行:由于子进程由fork函数创建,所以需要耗费一些时间。

2、父子进程并行运行:父进程利用sleep函数休息时,可以看到子进程也输出了其值,说明父子进程目前共同在运行

3、fork返回值:父进程的fork返回值为子进程的pid,子进程的fork返回值为0

4、pid是进程标识符:子进程和父进程的pid的值不同

进程的删除

进程的删除主要有两种:

1、进程走到return 0 自己结束

2、进程调用exit(1)结束

举例1(走到return 0结束)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
// 测试创建子进程函数 pid_t fork();
int main()
{
    pid_t pid = fork();
    if(-1 == pid)
    {
        //创建子进程失败
        return -1;
    }
    if(0 == pid)
    {
        //子进程
        printf("I am child, my fork:%d\n", pid);
        printf("I am child, my pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(5);
        printf("I am child, I have finished\n");  
    }
    else
    {
        //父进程 
        wait(NULL) 
        printf("I am father, my fork:%d\n", pid);      
        printf("I am father, my pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(5);
        printf("I am father, I have finished\n");   
    }
    printf("%d 号进程已结束\n",pid);
    return 0;
}

结果为:

关键点:在父进程中调用wait(NULL)函数让父进程在子进程结束后才开始运行

举例2(利用exit(1)结束)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
// 测试创建子进程函数 pid_t fork();
int main()
{
    pid_t pid = fork();
    if(-1 == pid)
    {
        //创建子进程失败
        return -1;
    }
    if(0 == pid)
    {
        //子进程
        printf("I am child, my fork:%d\n", pid);
        printf("I am child, my pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(5);
        printf("I am child, I have finished\n");
        exit(1);  
    }
    else
    {
        //父进程 
        wait(NULL) 
        printf("I am father, my fork:%d\n", pid);      
        printf("I am father, my pid:%d, ppid:%d\n",getpid(),getppid());
        sleep(5);
        printf("I am father, I have finished\n");   
    }
    printf("%d 号进程已结束\n",pid);
    return 0;
}

执行结果为:

关键点:子进程运行到exit函数后就自己删除自己,父进程开始运行

进程通信

操作系统内并发执行的进程可以是独立的也可以是协作的,协作完成的进程就涉及到进程间通信

进程通信主要有两种方式:1、共享内存  2、消息传递

图中关键点:

1、共享内存没有内核参与,消息传递需要内核参与

2、共享内存速度快于消息传递,但是共享内存要避免内存冲突。在多处理器系统上,共享内存有高速缓存一致性的问题

共享内存

共享内存实现通信一定要面临一种情况:其中一个进程是消息的发送者,另一个进程是消息的接收者。

这样我们必须要设计算法保证:消息接受者知道消息发送者何时发送

生产者算法
while(true){
    while(((in+1)%BUFFER_SIZE)==out)
        ; //共享内存满了,就不能生产等待消费者进程拿
    buffer[in]=next_produced;
    in=(in+1))%BUFFER_SIZE
}
消费者算法
item next_consumed;
while(true){
    while(in==out)
        ;//此时共享内存是空的,无法拿东西
    next_comsumed=buffer[out];
    out=(out+1)%BUFFER_SIZE;

算法关键点:

1、缓冲区最大值为BUFFER_SIZE-1(人为设定)

2、in+1==out设定为满,in==out设定为空

3、如果想要缓冲区最大值为BUFFER_SIZE,需要另设一个标志数用来记录缓冲区是满的还是空的。否则满的和空的都是in==out成立则无法区分

4、本质就是双指针实现,如上图所示

消息传递

定义

关键点:

1、消息传递对于分布式环境(硬件不同)的两个进程的通信特别有用

2、消息传递必须通过系统调用完成,所以必须陷入内核

3、消息传递一共有两种通信方式,第一种是直接通信方式,第二种是间接通信方式

首先要知道一个消息是由消息头和消息体组成的,消息头里包括:发送进程ID、接收进程ID、消息类型、消息长度等格式化的信息(计算机网络中发送的“报文”其实就是一种格式化的消息)。

直接通信方式,如下:

发送进程把它想要发送的消息,通过发送原语发送给接收进程,然后这些消息会被放到接收进程的消息队列里面,接着接收进程通过接收原语一个个读取消息队列中的消息,这样发送进程和接收进程就实现了线程通信。

间接通信方式,如下:

对于间接通信方式,进程1会先通过发送原语把消息放到一个信箱中,然后进程2会通过接收原语从信箱中读取这些消息。

算法实现

总结

本文到这里就结束啦~~这堂课的内容较为杂乱、复杂,但是学一学拓展一下知识是非常好的呀~~

如果觉得对你有帮助,辛苦友友点个赞哦~

知识来源:操作系统概念(黑宝书)、山东大学高晓程老师PPT及课上讲解。不要私下外传

相关文章
|
数据采集 搜索推荐 算法
【数据挖掘实战】——航空公司客户价值分析(K-Means聚类案例)
项目地址:Datamining_project: 数据挖掘实战项目代码
5949 1
|
算法 调度 Unix
现代操作系统部分章节笔记
二、进程与线程 1.进程 1.进程模型: 1.有自己的程序地址空间,程序计数器、寄存器和变量当前值。 2.切换进程的时候程序计数器、寄存器会装载到真正的相应物理硬件上。
960 0
|
1天前
|
数据采集 人工智能 安全
|
10天前
|
云安全 监控 安全
|
2天前
|
自然语言处理 API
万相 Wan2.6 全新升级发布!人人都能当导演的时代来了
通义万相2.6全新升级,支持文生图、图生视频、文生视频,打造电影级创作体验。智能分镜、角色扮演、音画同步,让创意一键成片,大众也能轻松制作高质量短视频。
908 150
|
15天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1645 8
|
6天前
|
人工智能 前端开发 文件存储
星哥带你玩飞牛NAS-12:开源笔记的进化之路,效率玩家的新选择
星哥带你玩转飞牛NAS,部署开源笔记TriliumNext!支持树状知识库、多端同步、AI摘要与代码高亮,数据自主可控,打造个人“第二大脑”。高效玩家的新选择,轻松搭建专属知识管理体系。
365 152
|
7天前
|
人工智能 自然语言处理 API
一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸
一句话生成拓扑图!next-ai-draw-io 结合 AI 与 Draw.io,通过自然语言秒出架构图,支持私有部署、免费大模型接口,彻底解放生产力,绘图效率直接爆炸。
603 152
|
9天前
|
人工智能 安全 前端开发
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
AgentScope 重磅发布 Java 版本,拥抱企业开发主流技术栈。
565 13