操作系统

本文详细介绍了操作系统的进程管理,包括进程与线程的概念、多线程的区别、进程通信方式(如管道、消息队列、共享内存)、死锁以及线程的高性能锁机制。同时,讲解了内存管理的层次结构、地址空间生成、内存分配策略等。此外,还探讨了Linux操作系统的内核态与用户态以及常用命令。
摘要由CSDN通过智能技术生成

目录

 

第一部分、进程管理

进程与线程

协程 Coroutine

管道

消息队列

共享内存

IPC小结

僵尸进程

死锁

线程

高性能锁机制

如何减少上下文切换

CAS函数

多线程编程

线程撕裂者

缓存

单机多核多线程高并发

第二部分、内存管理

层次结构

地址空间及生成

连续内存分配

内存空间压缩与置换

非连续内存分配

全局内存页置换

DMA

mmap

Linux操作系统

内核态与用户态

常用命令

附录:

FORK_TWICE

CATCH_SIGCHLD

IGNORE_SIGCHLD

MEMORY_SHARED

PIPE

PIPE_EMPTY_READ

PIPE_FULL_WRITE

CREATE_THREAD

Callable

lambda_in_thread

measure_time

multi_pthread_time

future

self_add

mutex

msg


第一部分、进程管理

进程与线程

  • 基本认识

    • (1)进程是程序运行的实例,内部保存了程序运行所需的资源。

    • (2)线程是进程的一个实体,是CPU调度的基本单位。线程只拥有运行必不可少的资源(如程序计数器、一组寄存器和栈),但是它可以与同属于同一个进程的其他线程共享进程的资源。

  • 多线程与多进程的区别(稳定性,通讯,切换

    • (1)系统开销

      • 维护多进程的系统开销大:包括进程的创建、销毁、切换等(栈、寄存器、虚拟内存、文件句柄等)。

      • 维护多线程的系统开销小:只需要维护程序计数器、寄存器和栈即可。

    • (2)通讯开销

      • 进程采用IPC进行通讯,相对复杂。

      • 线程共享进程内存,通讯相对简单,但同步机制要求高。

    • (3)稳定性

      • 进程稳定性高,进程间相互独立,一个进程崩溃一般不会引起另一个进程崩溃。

      • 线程稳定性低,一个线程崩溃,所属进程的所有线程都将崩溃。

  • 多线程

  • (1)线程数不能过多,因为线程采用轮询机制,线程过多将导致延迟增大,并且上下文切换频繁,会导致过多的CPU时间使用在切换上下文的操作上,降低系统的效率。

  • 进阶认识

    • (1)进程控制块PCB:OS为每一个进程维护一个PCB,用来保存该进程有关的各种状态信息。

      • (1-1)进程标识信息:~本进程标识、~父进程标识、~用户标识等

      • (1-2)CPU状态保护区:保存进程的运行现场信息

        • 用户可见寄存器:用户程序可以使用的数据,地址等寄存器。

        • 控制和状态寄存器:程序计数器、程序状态字。

        • 栈指针:过程调用、系统调用、中断处理和返回时需要使用。

      • (1-3)进程控制信息

    • (2)中断:CPU停止运行当前进程,转而去做别的事情。

      • (2-1)中断向量:中断服务程序的入口地址。

      • (2-2)发生中断后,OS最底层做了什么?(粗略)

        • 1、保存中断现场。

        • 2、转入中断服务程序。

        • 3、处理中断。

        • 4、调度下一次运行的进程。

        • 5、恢复中断现场。

    • (3)进程挂起:把一个进程从内存转到外存

      • 目的:充分合理使用系统资源。

      • 阻塞挂起状态:进程在外存并等待某个事件的发生。

      • 就绪挂起状态:进程在外村并等待CPU和内存。

    • (4)调度算法:

      • 批处理系统:~先来先服务、~短作业优先(实际无法预先知道进程运行时间)、~最短剩余时间优先(抢占式,短作业优先的改版)

      • 交互式系统:~时间片轮转法、~优先级调度(结合使用相同的时间片大小)、~多级反馈队列(在优先级调度的基础上,给进程不同大小的时间片)

        • ~多级反馈队列:通过给予进程指数递增的时间片大小,来避免频繁多次上下文切换。例如:进程需要100个时间片,在优先级调度算法中,可能需要100次进程上下文切换,而多级反馈队列中,最多只需要7次进程上下文的切换。

      • 实时系统:~单调速率调度、~最早截止时间调度

        • 单调速率调度:抢占式、静态优先级、周期越短,优先级越高。

        • 最早截止时间调度:根据截止时间动态分配优先级,截止时间越早优先级越高。

    • (5)操作系统如何完成进程调度:

      • OS中存在就绪队列和阻塞队列,每个队列中有进程的状态信息,当进程状态发生变化时,就从相应的队列中取下进程PCB,放入其他队列或者放入CPU运行。

    • (6)线程分类:~用户线程、~内核线程

  • 进程的通讯方式

    • 由于各个进程不共享相同的地址空间,任何一个进程的全局变量在另一个进程中都不可见,所以如果想要在进程之间传递数据,就需要通过内核,在内核开辟一块区域(缓冲区),使得该区域对多个进程都可见

     

协程 Coroutine

  • (1)进程切换 与 线程切换

    • 进程切换分两步:

      • 1-1、切换页目录,以使用新的地址空间。

      • 1-2、切换内核栈和硬件上下文。

    • 线程切换:

      • 2-1、切换内核栈和硬件上下文。

  • (2)协程

    • 2-1、协程是一种 用户态 的轻量级线程,它的调度完全依赖用户的操作。

    • 2-2、协程拥有自己的寄存器上下文和“栈”(可以共享栈且有stackless与stackful实现方式)

    • 2-3、上下文的切换非常快。

    • 2-4、同步逻辑写代码,异步执行程序。

  • (3)为何使用协程?

    • 3-1、使用异步的优势,避免IO操作阻塞线程。

      • 当协程挂起时,应该时当前协程发起异步操作的时候。

      • 当协程唤醒时,应该在其他协程退出,且当前协程异步操作完成的时候。

    • 3-2、“硬件上下文切换” --> 使用内存栈保存寄存器内容(386 -- 8 个寄存器, x86-64 14)

      • 上下文切换时,使用 寄存器 与 内存 之间的切换。

管道

  • 基本认识

    • (1)匿名管道,父子等有血缘关系的进程所有,半双工通讯。

    • (2)命名管道,内核所有,知道管道ID的进程即可使用,需要同步机制。

  • 使用方法

    • (1)匿名管道,数据读出多少,管道内容减少多少

      • int pipe (int file_descriptor[2]);
        // 返回值:成功:0,失败:-1
        // file_description 表示文件描述符,由用户指定。
      • 管道实例代码,附录—PIPE

        • (1)管道为空时读取,即还没来得及写入时。附录—PIPE_EMPTY_READ

          • (1-1)结论:阻塞模式下,进程将阻塞,直至管道被其他进程写入数据后,才能继续读取。

          • (1-2)结论:使用fcntl修改管道为非阻塞,则进程直接退出,read返回-1,设置errno = EAGAIN。

        • (2)管道为满时写入,即还没来得及读出时。附录—PIPE_FULL_WRITE

          • (2-1)结论:阻塞模式下,进程将阻塞,直至管道内容被其他进程读出后,才能继续写入。

          • (2-2)结论:使用fcntl修改管道为非阻塞,则进程直接退出,write返回-1,设置errno = EAGAIN。

        • (3)所有管道写端关闭时读取,结论:将把管道内的所有数据全部读出,最后read返回值未0后结束;若存在写端未关闭,则会进程会再度进入阻塞状态。

        • (4)所有管道读端关闭时写入,结论:无法写入,理解:没有人能读取信息,没必要写入。

        • (5)PIPE_BUF与原子性问题:

          • 小于等于PIPE_BUF的写操作必须时原子的:要写的数据将被连续地写入管道。

          • 大于PIPE_BUF的写操作写入时,不保证写入的原子性。

      • 平时经常使用。

      • ls | grep xx
    • (2)高级管道(略)。

    • (3)命名管道,有权限的非亲缘进程也可以通过pathname访问。

      • int mkfifo(const char * pathname, mode_t mode);
        // 返回值:成功:0,失败:-1
        // pathname 表示文件的路径名称
        // mode 表示访问权限
      • 该函数会生成一个管道文件(标识符: p ),对其的访问,与普通文件访问形式一致。

消息队列

  • 基本认识:

    • 将消息挂在内核区域内,然后进程可以根据消息类型进行读取,不必采用管道那样先进先出的方式。

    • 消息队列是存放在内核中的消息链表,每个消息队列链表会由消息队列标识符标识。

  • 使用方法:

    • (1)创建、访问消息队列

      • int msgget(key_t key, int msgflag);
        // 返回值:成功,消息队列ID,失败,-1
        // key 表示 消息队列的唯一标识
        // msgflag 表示 消息队列的权限
    • (2)发送消息到消息队列

      • int msgsend(int msgid, const void * msg_ptr, size_t msg_sz, int msgflg);
    • (3)接受消息从消息队列

      • int msgrcv(int msgid, void * msg_ptr, size_t msg_sz, long int msgtype, int msgflag);
    • (4)控制消息队列

      • int msgctl(int msgid, int command, struct msgid_ds *buf);
  • 案例 msg

  •  

共享内存

  • 基本认识

    • (1)物理内存上的一块区域被映射到不同进程的进程地址空间上,被不同的进程共享。

    • (2)进程对内存可以直接进行修改,因此需要一定的同步机制。

  • 使用方法

    • (1)构造共享内存

      • #include <sys/shm.h>
        int shmget (key_t key, size_t size, int shmflg); 
        // 返回值:成功:共享存储ID,指向共享内存首地址,失败:-1
        // key 表示 共享内存的唯一标识符,与id类似,但两者不同
        // size 表示 共享内存的大小
        // shmflg 表示 共享内存的访问权限
    • (2)访问共享内存

      • void * shmat (int shmid, const void * shmaddr, int shmflg); 
        // 返回值:成功:void*,指向共享内存段首地址,失败:-1
        // shmid 表示 共享内存的id, 从shmget的函数返回值中获得
        // shmaddr 表示 共享内存映射到当前进程的内存空间的起始位置,一般为NULL,表示自动选择映射的地址
        // shmflg 表示 标志位,一般为0
    • (3)分离共享内存

      • int shmdt (const void * addr);
        // 返回值:成功:0,失败:-1
        // addr 表示 共享存储段的指针,从shmat的函数返回值获得
    • (4)控制共享内存(删除)

      • int shmctl (int shmid, int cmd, struct shmid_ds * buf)
        // 返回值:成功:0,失败:-1
        // shmid 表示 共享内存的id, 从shmget的函数返回值中获得
        // cmd 表示 对共享内存采取的操作,与buf搭配使用
        // buf 表示共享内存基本属性的结构体
        // cmd = IPC_STAT : 获取内存属性,赋值给buf
        // cmd = IPC_SET : 设置内存属性,从buf中获取设置参数
        // cmd = IPC_RMID : 删除共享内存
    • (5)共享内存属性

      • // 包括:访问权限、内存大小、创建者PID、最后操作内存者PID、连接数量等
    • (6)命令行操作

      • # 查看 -m 表示共享内存,不加则显示所有ipc
        ipcs -m 
        # 删除
        ipcrm -m shmid
  • 共享内存实例代码,附录—MEMORY_SHARED

IPC小结

 

僵尸进程

  • 基本认识

    • (1)一个进程结束后,它所占有的资源没有被父进程回收并释放,就成为僵尸进程。

    • (2)进程仍占有的是内核资源,如PCB,用户空间资源在退出之前已经完全释放。

  • 避免僵尸进程的方式

    • (0)具体查看僵尸进程方式

      • ps -u 
        # STAT 为 Z的即为僵尸进程
    • (1)fork twice. 附录—FORK_TWICE

      • (1-1)父进程比子进程更早终止,则Init进程会接管子进程,进行善后处理。

      • (1-2)父进程wait子进程,可以直到子进程的状态,只是为了释放其资源则不需要参数。

        • // 1. 手动,wait成功返回子进程ID
          while (pid != wait()) // pid记录想要等待的子进程号
          // 2. 调用API
          pid_t waitpid(pid_t pid, int *statloc, int options); // 成功返回子进程id,失败返回-1。
          // pid == -1,为任意子进程
    • (2)捕获SIGCHLD信号,并在信号处理函数中wait子进程 (classic way)附录—CATCH_SIGCHLD

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值