操作系统学习笔记(二)

简介: 进程切换比线程切换更消耗资源,因为进程切换需保存更多上下文,包括地址空间、寄存器、栈和文件描述符等,还要刷新TLB。线程切换仅需切换硬件上下文和内核栈,上下文更小,所以开销低。进程间通信有多种方式,如匿名管道(父子进程间)、命名管道(无亲缘关系进程)、信号、消息队列、共享内存和信号量等。这些通信方法各有特点,适用于不同场景。例如,匿名管道是半双工的,有名管道允许任何进程通过路径通信,信号用于进程间的简单通知,消息队列支持随机查询和按类型读取,共享内存允许多进程共享数据,而信号量则用于同步和控制对共享资源的访问。

1.进程切换为什么比线程更消耗资源?

​ 进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈;线程切换时只需要切换硬件上下文和内核栈。

​ 解析:

​ 进程是程序的动态表现。 一个程序进行起来后,会使用很多资源,比如使用寄存器,内存,文件等。每当切换进程时,必须要考虑保存当前进程的状态。状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开的文件描述符的集合,这个状态叫做上下文(Context)。可见,想要切换进程,保存的状态还不少。不仅如此,由于虚拟内存机制,进程切换时需要刷新TLB并获取新的地址空间。

​ 线程存在于进程中,一个进程可以有一个或多个线程。线程是运行在进程上下文中的逻辑流,这个线程可以独立完成一项任务。同样线程有自己的上下文,包括唯一的整数线程ID, 栈、栈指针、程序计数器、通用目的寄存器和条件码。可以理解为线程上下文是进程上下文的子集。

​ 由于保存线程的上下文明显比进程的上下文小,因此系统切换线程时,必然开销更小。

  1. 介绍一下进程之间的通信。

​ 为了提高计算机系统的效率.增强计算机系统内各种硬件的并行操作能力.操作系统要求程序结构必须适应并发处理的需要.为此引入了进程的概念。而进程并行时,需要考虑进程间的通信,进程间通信主要有以下几种方式:匿名管道、命名管道、信号、消息队列、共享内存、信号量、Socket。

​ 匿名管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

include #include #include #include #include int pipe_default[2]; int main() { pid_t pid; char buffer[32]; memset(buffer, 0, 32); if(pipe(pipe_default) < 0) { printf("Failed to create pipe!\n"); return 0; } if(0 == (pid = fork())) { close(pipe_default[1]); //关闭写端 sleep(2); if(read(pipe_default[0], buffer, 32) > 0) { printf("[Client] Receive data from server: %s \n", buffer); } close(pipe_default[0]); } else { close(pipe_default[0]); //关闭读端 char msg[32]="== hello world =="; if(-1 != write(pipe_default[1], msg, strlen(msg))) { printf("[Server] Send data to client: %s \n",msg); } close(pipe_default[1]); waitpid(pid, NULL, 0); } return 1; }

1

​ 有名管道

​ 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO)。

​ 有名管道不同于匿名管道之处在于它提供了一个路径名与之关联,以有名管道的文件形式存在于文件系统中,这样,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信,因此,通过有名管道不相关的进程也能交换数据。值的注意的是,有名管道严格遵循先进先出(first in first out) ,对匿名管道及有名管道的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。有名管道的名字存在于文件系统中,内容存放在内存中。

​ 信号

​ 信号是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。
​ 如果该进程当前并未处于执行状态,则该信号就有内核保存起来,知道该进程回复执行并传递给它为止。
​ 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消是才被传递给进程。

​ 以下列出几个常用的信号:
信号 描述
SIGHUP 当用户退出终端时,由该终端开启的所有进程都退接收到这个信号,默认动作为终止进程。
SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出,用于通知前台进程组终止进程。
SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl+)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
SIGKILL 用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。
SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。
SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.

​ 代码示例:

​ 下面的代码收到程序退出信号后会执行用户定义的信号处理函数来替代系统默认的处理程序。

include #include #include #include #include void sig_handle(int sig) { printf("received signal: %d, quit.\n", sig); exit(0); } int main () { signal(SIGINT, sig_handle); signal(SIGKILL, sig_handle); signal(SIGSEGV, sig_handle); signal(SIGTERM, sig_handle); int i = 0; while (1) { printf("%d\n", ++i); sleep(2); } printf("main quit."); return 0; }

1

​ 运行结果:

1 2 received signal: 15, quit.

1

​ 消息队列

​ 消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。
​ 与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
​ 另外与管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。

​ 消息队列特点总结:

​ (1)消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识.

​ (2)消息队列允许一个或多个进程向它写入与读取消息.

​ (3)管道和消息队列的通信数据都是先进先出的原则。

​ (4)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。

​ (5)消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。

​ (6)目前主要有两种类型的消息队列:POSIX消息队列以及System V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。

​ 共享内存

​ 进程间本身的内存是相互隔离的,而共享内存机制相当于给两个进程开辟了一块二者均可访问的内存空间,这时,两个进程便可以共享一些数据了。但是,多进程同时占用资源会带来一些意料之外的情况,这时,我们往往会采用上述的信号量来控制多个进程对共享内存空间的访问。

#include <iostream> #include <stdlib.h> #include <string.h> #include <sys/shm.h> #include <sys/ipc.h> #include <unistd.h>  using namespace std; int main() {   char *shmaddr;   char *shmaddread;   char str[]="Hello, I am a processing. \n";   int shmid;    key_t key = ftok(".",1);   pid_t pid1 = fork();   if(pid1 == -1){      cout << "Fork error. " << endl;      exit(1);  }   else if(pid1 == 0){      //子进程      shmid = shmget(key,1024,IPC_CREAT | 0600);      shmaddr = (char*)shmat(shmid, NULL, 0);                 strcpy(shmaddr, str);      cout << "[Writer] write: " << shmaddr << endl;      shmdt(shmaddr);  }   else  {      //父进程      pid_t pid2 = fork();      if(pid2 == -1){        cout << "Fork error. " << endl;        exit(1);     }     else if(pid2 == 0){        //子进程        sleep(2);        shmid = shmget(key,1024,IPC_CREAT | 0600);        shmaddread = (char*)shmat(shmid, NULL, 0);                cout << "[Reader] read: " << shmaddread << endl;        shmdt(shmaddread);     }  }   sleep(3);   return 0; }
    1

​ 信号量

​ 信号量主要用来解决进程和线程间并发执行时的同步问题,进程同步是并发进程为了完成共同任务采用某个条件来协调他们的活动,这是进程之间发生的一种直接制约关系。

​ 对信号量的操作分为P操作和V操作,P操作是将信号量的值减一,V操作是将信号量的值加一。当信号量的值小于等于0之后,再进行P操作时,当前进程或线程会被阻塞,直到另一个进程或线程执行了V操作将信号量的值增加到大于0之时。锁也是用的这种原理实现的。

​ 信号量我们需要定义信号量的数量,设定初始值,以及决定何时进行PV操作。
相关文章
|
存储 缓存 Linux
计算机操作系统学习笔记(2)——存储器结构
计算机操作系统学习笔记(2)——存储器结构
209 0
|
6月前
|
监控 Linux 调度
操作系统学习笔记(一)
在Linux中,使用`ps -aux | grep PID`来查看特定进程的状态,或者用`top`指令监控进程和内存。通过`cat 文件名 | grep 关键词`或`grep -i 关键词 文件名`搜索日志文件。`grep`是一个强大的文本搜索工具,支持多种参数,如`-i`忽略大小写,`-c`计数,`-f`从文件读取关键词。要临时更改主机名用`hostname 新主机名`,永久更改则用`hostnamectl set-hostname 新主机名`
46 0
|
缓存 Linux 应用服务中间件
计算机操作系统学习笔记(11)——零拷贝
计算机操作系统学习笔记(11)——零拷贝
93 0
|
消息中间件 Shell Linux
计算机操作系统学习笔记(7)——进程通信
计算机操作系统学习笔记(7)——进程通信
94 0
|
存储 缓存 Linux
计算机操作系统学习笔记(5)——内存管理
计算机操作系统学习笔记(5)——内存管理
112 0
|
6月前
|
Ubuntu Unix Linux
Linux 学习笔记一: 常见操作系统相关概念
Linux 学习笔记一: 常见操作系统相关概念
Linux 学习笔记一: 常见操作系统相关概念
|
11月前
|
程序员 Linux
不愧是华为内部的“操作系统学习笔记”,一篇说细节,一篇讲哲学
当然重要,身为程序员的我们,那更应该深刻理解和掌握操作系统,虽然我们日常 CURD 的工作中,即使不熟悉它们,也不妨碍我们写代码,但是当出现问题时,没有这些基础知识,你是无厘头的,根本没有思路下手,这时候和别人差距就显现出来了,可以说是程序员之间的分水岭。
|
编译器 调度 C语言
【学习笔记】小 O 带你掌握操作系统的心跳 - OneOS 内核启动
一、简介 内核启动介绍了整个系统从硬件上电如何一步步进入用户程序的过程。一般情况下,启动过程分为硬件上电,首先运行和体系架构相关的启动汇编文件,进行一些最基本硬件的初始化 (例如 CPU 配置,时钟,栈地址,RAM 等),为内核运行铺垫好环境,然后初始化内核各模块 (例如调度器,定时器等),接着创建系统任务 (例如空闲任务) 和用户任务,最后启动调度和运行用户程序。
166 0
计算机操作系统学习笔记(14)——复习要点笔记
计算机操作系统学习笔记(14)——复习要点笔记
83 0
|
NoSQL Linux 应用服务中间件
计算机操作系统学习笔记(13)——⾼性能⽹络模式:Reactor 和 Proactor
计算机操作系统学习笔记(13)——⾼性能⽹络模式:Reactor 和 Proactor
97 0