【操作系统】02.进程通信、调度、线程
【嵌入式八股】一、语言篇(https://www.nowcoder.com/creation/manager/columnDetail/mwQPeM
【嵌入式八股】二、计算机基础篇(本专栏)https://www.nowcoder.com/creation/manager/columnDetail/Mg5Lym
【嵌入式八股】三、硬件篇https://www.nowcoder.com/creation/manager/columnDetail/MRVDlM
【嵌入式八股】四、嵌入式Linux篇https://www.nowcoder.com/creation/manager/columnDetail/MQ2bb0
进程通信
15.LINUX进程间通信方式有哪些?有什么优缺点?
-
管道:用来实现进程间相互发送非常短小的、频率很高的消息,通常适用于两个进程间的通信。
- 无名管道(内存文件):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系。半双工速度慢,利用内核中的一串缓存,容量有限。只有具有亲缘关系的进程间通讯。面向字节流。
- 有名管道(FIFO文件,借助文件系统):有名管道也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,管道是先进先出的通信方式,克服了管道没有名字的限制。但速度慢。
- 流管道(s_pipe): 去除了第一种限制,可以双向传输(全双工),或者用两个管道实现全双工也行。
-
共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。但需要进程自行解决进程同步和互斥问题,往往与信号量,配合使用来实现进程间的同步和通信。共享内存用来实现进程间共享的、非常庞大的、读写操作频率很高的数据。
-
消息队列:消息队列是有消息的链表数据块,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。是一种异步的通信方式可以使多个进程之间传递数据块。消息队列通常用于进程间的同步,进程可以从队列中读取数据,或者向队列中写入数据。进行通信时不再需要考虑同步问题,使用方便, 但是信息的复制需要额外消耗CPU的时间,通信不及时,不适宜于信息量大或操作频繁的场合。
-
套接字:是一种基于网络协议的通信方式,适用于不同机器间进程通信,在本地也可作为两个进程通信的方式。
-
信号:信号是一种异步通信方式,可以向进程发送一个软件中断请求。用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。主要作为进程间以及同一进程不同线程之间的同步手段。
-
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。实现进程、线程对临界区的同步及互斥访问。主要作为进程间以及同一进程内不同线程之间的同步手段。不能用来传递复杂消息,只能用来同步。
管共消,套两信。
16.什么情况用消息队列?什么情况用共享内存?传YUV图像,发控制命令呢?
消息队列通常用于以下情况:
- 进程间通信:当多个进程需要在不同的时间进行异步通信时,消息队列是一个很好的选择。每个进程可以将消息发送到队列中,而其他进程可以从队列中接收消息。这对于分布式系统和多进程应用程序非常有用。
- 解耦和流量控制:消息队列可以用于解耦不同部分的应用程序,使它们能够独立演化。它还可以用于流量控制,以确保接收方可以以自己的速度处理消息,而不会被发送方过多的数据淹没。
- 持久化消息:消息队列通常可以配置为持久化消息,即使接收方不在线,也可以在稍后接收和处理消息。
共享内存通常用于以下情况:
- 高性能数据共享:共享内存是在进程之间共享数据的一种非常高效的方式,因为多个进程可以直接访问相同的内存区域,而不需要复制数据。
- 低延迟通信:与消息队列相比,共享内存通常具有更低的延迟,因为数据可以直接从一个进程传递到另一个进程,而不需要中间的消息传递。
- 数据一致性:共享内存通常用于需要多个进程访问相同数据的情况,这样可以确保数据的一致性,而不需要复制多个副本。
对于传输YUV图像和发控制命令的情况,可以考虑以下建议:
- 如果您需要在不同的进程之间传输YUV图像,共享内存可能是更好的选择,因为它提供了高性能和低延迟的数据传输方式。您可以将YUV图像存储在共享内存中,然后多个进程可以直接访问这些数据。
- 控制命令通常可以通过消息队列传递,因为它们通常不需要高性能或低延迟,而且消息队列提供了一个方便的方式来在不同的进程之间进行异步通信。您可以将控制命令打包成消息并将其发送到消息队列中,然后接收方进程可以从队列中接收并处理这些命令。
17.匿名管道的限制
- 匿名管道只能用于单向半双工通信,即只能用于一个进程向另一个进程传递数据。如果需要进行双向通信,需要创建两个匿名管道。
- 匿名管道只能在创建它的进程及其子进程之间进行通信,即只能用于具有亲缘关系的进程之间。
- 匿名管道的缓冲区大小是固定的,一旦缓冲区被填满,写进程就会被阻塞,直到读进程读取数据后才能继续写入数据。因此,在使用匿名管道时需要注意控制数据的流量。
- 匿名管道的生命周期与创建它的进程相关联。如果创建它的进程结束了,管道也会被销毁。
18.说⼀下mmap的通信过程:orange:
- 打开文件:首先,进程需要打开一个文件,可以使用标准的文件打开函数(例如open())来打开文件并获取文件描述符。
- 文件大小确定:进程需要确定要映射的文件的大小。可以使用标准的文件操作函数(例如stat())来获取文件的大小。
- 创建内存映射:进程使用mmap()函数创建一个内存映射区域。mmap()函数的参数包括文件描述符、映射区域的大小、映射方式(例如读写、只读等)以及映射的起始位置等。调用mmap()函数后,操作系统会在进程的虚拟地址空间中创建一个映射区域,并与文件建立关联。
- 进程间共享:多个进程可以通过访问同一个文件的映射区域来进行通信。当一个进程对映射区域进行写操作时,其他进程也能够读取到相应的数据变化。这样,进程之间就可以通过读写映射区域来进行通信。
- 同步机制:在使用mmap进行进程间通信时,需要考虑同步机制来确保数据的一致性。常见的同步机制包括互斥锁(mutex)和信号量(semaphore)。进程在对映射区域进行读写操作之前,需要先获取相应的同步锁来保证数据的完整性。
- 关闭映射:在通信结束后,进程需要调用munmap()函数来关闭映射区域。munmap()函数会解除映射并释放相应的资源。
需要注意的是,使用mmap进行通信需要谨慎处理同步机制,避免出现数据竞争和不一致的情况。此外,mmap还可以用于共享内存和文件的高效访问,但在使用时需要注意内存管理和权限控制等问题。
import mmap
import os
# 创建文件并写入数据
file_path = "mmap_example.txt"
with open(file_path, "w") as file:
file.write("Hello, mmap!")
# 打开文件并获取文件描述符
file_descriptor = os.open(file_path, os.O_RDWR)
# 获取文件大小
file_size = os.path.getsize(file_path)
# 创建内存映射
mmap_data = mmap.mmap(file_descriptor, file_size, mmap.MAP_SHARED, mmap.PROT_READ | mmap.PROT_WRITE)
# 进程间通信
pid = os.fork() # 创建子进程
if pid == 0:
# 子进程
mmap_data.seek(0) # 定位到映射区域开头
print("Child process read:", mmap_data.readline().decode())
mmap_data.close()
else:
# 父进程
mmap_data.seek(0) # 定位到映射区域开头
mmap_data.write(b"Hello from parent process!")
mmap_data.close()
# 关闭文件描述符
os.close(file_descriptor)
# 删除文件
os.remove(file_path)
在这个示例中,首先创建了一个文件并写入数据。然后打开文件并获取文件描述符,获取文件大小。接下来,使用mmap()
函数创建了一个内存映射区域mmap_data
,通过文件描述符、文件大小以及映射方式等参数进行了映射的创建。然后,使用os.fork()
创建了一个子进程,子进程和父进程都能够访问mmap_data
映射的内存区域。子进程通过读取映射区域来获取父进程写入的数据,父进程通过写入映射区域来与子进程进行通信。最后,关闭映射和文件描述符,并删除文件。
19.共享内存的读写时系统调用有几次?
共享内存的读写涉及到系统调用的操作,通常包括以下几次系统调用:
- shmget():在创建或获取共享内存段时,通常需要调用
shmget()
系统调用。此调用用于获取共享内存的标识符,并可以指定内存大小和权限等参数。 - shmat():要访问共享内存,进程需要将共享内存附加到其地址空间中。这是通过
shmat()
系统调用完成的。该调用将共享内存连接到进程的地址空间,返回一个指向共享内存的指针。 - 读写操作:一旦共享内存被附加到进程的地址空间中,进程可以直接对共享内存进行读写操作,而无需进行系统调用。读写操作本身不涉及系统调用。
- shmdt():当进程完成对共享内存的访问时,需要调用
shmdt()
系统调用来将共享内存从进程的地址空间中分离。这会使共享内存不再对该进程可用。 - shmctl():在不再需要共享内存段时,可以使用
shmctl()
系统调用来进行控制和清理操作。这包括删除共享内存段,以及获取或设置共享内存的属性。
所以,在共享内存的读写过程中,通常至少涉及4次系统调用:shmget()
、shmat()
、读写操作、shmdt()
。如果需要进行进一步的管理和控制,还可能涉及shmctl()
系统调用。每次系统调用都会引入一定的开销,因此在设计使用共享内存的应用程序时需要注意性能和资源管理。
20.进程对信号的响应
- 忽略信号:当进程对某个信号进行忽略处理时,这个信号将被自动忽略,进程不会做出任何响应。可以通过signal()函数或者sigaction()函数来设置信号的处理方式为忽略。
- 捕获信号:当进程对某个信号进行捕获处理时,当该信号发生时,进程会执行一个特定的处理函数,该函数被称为信号处理函数。可以通过signal()函数或者sigaction()函数来设置信号的处理方式为捕获,并指定信号处理函数。
- 默认信号处理方式:对于大多数信号,系统都有一个默认的处理方式。例如,当进程收到SIGTERM信号时,系统默认会终止进程。当进程收到SIGINT信号时,系统默认会中断进程。如果进程没有对信号进行处理,系统将使用默认的处理方式。
21.线程通信方法
- 锁机制:通过对共享资源的加锁和解锁,来保证同一时间只有一个线程访问共享资源。常见的锁机制包括互斥锁、读写锁等。
- 条件变量:通过等待和唤醒来进行线程间通信。当一个线程发现自己需要等待某个条件时,就会等待,而另一个线程当满足这个条件时,就会唤醒等待的线程。
- 信号量:信号量是一个计数器,用于同步和互斥。它可以保证同一时间只有一定数量的线程可以访问共享资源。
- 管道(Pipe):管道是一种在进程间进行通信的方法,也可以用于线程间通信。它是一个先进先出的数据结构,其中一个线程可以往管道中写入数据,另一个线程则可以从管道中读取数据。
- 消息队列(Messag
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
查阅整理上千份嵌入式面经,将相关资料汇集于此,主要包括: 0.简历面试 1.语言篇 2.计算机基础【本专栏】 3.硬件篇 4.嵌入式Linux (建议PC端查看)