操作系统问答
进程间通讯方式
1.管道:
管道是一种半双工的通信方式,可以在父子进程或者是兄弟进程之间进行通信。
2.命名管道:
也称FIFO(First in first out),是一种特殊的文件,可以在不相关的进程之间通信。
3.信号:
信号是一种异步通信方式,用于通知接收进程发生了某件事
4.消息队列:
消息队列是一种存放在内核中的消息链表,通过消息标识符进行通信。
5.共享内存:
共享内存是一种高效的进程间通信方式,多个进程可以直接访问同一块内存区域,避免了数据的复制。
6.信号量:
信号量是一个计数器,用于控制对共享资源的访问,可以防止多个进程同时访问共享资源。
7.套接字:
套接字是一种用于网络通信的通信机制,可以在不同主机之间进行进程间通信。
// shm demo
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main() {
// create shm
int shmId = shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT | 0666);
if (shmId == -1) {
cerr << "Failed to create shared memory." << endl;
return -1;
}
int* sharedDate = (int*)shmat(shmId, nullptr, 0);
if (sharedDate == (int*)-1) {
cerr << "Failed to attach shared memory." << endl;
return -1;
}
*sharedDate = 42;
// create child pid
pid_t pid = fork();
if (pid == -1) {
cerr << "Failed to fork." << endl;
}
if (pid == 0) {
// child
cout << "Child process: get Shared data is " << *sharedDate << endl;
// child write data to shm
*sharedDate = 100;
// del link
shmdt(sharedDate);
} else {
// father
// wait child
wait(nullptr);
cout << "Parent process: get Shared data is " << *sharedDate << endl;
// del link
shmdt(sharedDate);
// del shm
shmctl(shmId, IPC_RMID, nullptr);
}
return 0;
}
共享内存:
是一种高效的通信方式,相对于其他通信方式有一下特点:
1.速度快:
共享内存直接在京城见共享内存区域,无需进行数据的拷贝,因此速度非常快,适用于大量数据的传输。
2.低延迟:
由于共享内存通信直接操作内存,无需进行序列化和反序列化的过程,因此通信延迟低。
3.高吞吐量:
共享内通信可以实现高吞吐量的数据传输,特别适用于需要频繁,大量数据交换的场景。
4.无需内核参与:
共享内存通信通过内存进行数据交换,不需要内核的参与,因此减少了系统调用和内核态与用户态的切换。
一些局限性
1. 进程间同步:
由于多个进程可以同时访问共享内存,需要使用额外的同步机制(如信号量,互斥锁等)来确保数据的一致性和避免竞态条件。
2. 进程间协作:
共享内存通信只提供了数据的交换机制,而没有提供进程间的协作机制,因此需要借助其他机制来进行进程间的协调和同步。信号量,条件变量等
多线程的通讯和同步异步方式
1.共享内存:
多个线程可以通过访问共享的内存区域进行通信。线程可以读取和写入共享内存中的数据进行数据交换。为了保证线程安全需要使用同步机制,如互斥锁(mutex)或读写锁(read write lock)
来控制对共享内存的访问
2.消息队列:
多个线程可以通过消息队列进行通信。线程可以将消息发送到队列中,其他线程可以从队列中获取消息并进行处理。消息队列可以实现异步通信,发送方不需要等待接收方立即处理,提高了系统的并发性能。
3.信号量:
信号量是一种同步机制,用于控制多个线程之间的访问权限。线程可以通过信号量来进行同步和互斥操作。例如,可以使用信号量来限制同时访问共享资源的线程数
4.条件变量:
条件变量用于线程之间的通信和同步。一个线程可以等待某个条件变量满足,而另一个线程可以在满足条件时通知等待的线程。条件变量常用于生产者-消费者模型等场景。
5.Futures和Promises:
Futures(未来)和Promises(承诺)是一种异步编程模型,多个线程可以通过Futures和Promises来进行通信和同步。一个线程可以在Promise中设置结果,而其他线程可以使用Future来等待结果。
6.回调函数:
多线程可以通过回调函数进行通信。一个线程可以在某个事件发生时调用注册的回调函数,而其他线程可以在回调函数中处理相应的逻辑
线程 joinable 状态和 unjoinable 状态
1. joinable:
当创建一个线程时,它通常处于joinable转态
在joinable状态下,主线程可以调用join()或者detach()来控制线程的执行和资源释放
如果主线程调用detach()来分离子线程,主线程将不再与子线程关联,子线程将成为一个独立执行的线程
2. unjoinable:
在线程已经被join()或者detach()它进入unjoinable状态
在unjoinable状态下,线程无法再调用join()或者detach()来控制线程的执行和资源的释放
如果主线程试图调用join()来等待一个unjoinable状态的线程,会抛出system_error异常
程序编译的过程:
1.预处理
gcc -E main.c -o main.i
预处理对源代码进行处理,主要包括宏展开,头文件包含等操作
预处理的结构是一个被修改过的源码文件,通常以 .i或者 .ii为扩展名
2.编译
gcc -S main.i -o main.s
编译器将预处理后的源代码翻译成汇编代码
编译器进行词法分析,语法分析,语义分析,优化等操作
编译的结果是一个汇编文件,通常以 .s为扩展名
3.汇编
gcc -c main.s -o main.o
汇编器将汇编代码翻译成机器指令
汇编器将汇编代码中的每一天指令映射成对应的机器码
汇编的结果是一个可重定位目标文件,通常以 .o或者.obj 为扩展名
4.链接
gcc main.o -o main
链接器将目标文件以及其他库文件进行链接,生成最终可执行文件
链接器将目标文件引用的函数和变量与其他目标文件或库文件中的定义进行关联
链接的结果是一个可执行文件,可以直接运行
堆和栈的区别
堆和栈是计算机内存中的两种不同的分配方式,他们有不同的特点和用途:
1.存储数据类型
堆:
主要用于存储动态分配的数据,如对象和数据结构。堆中的数据可以在程序运行时动态分配和释放,通常由开发者显式地管理内存。
栈:
主要用于存储程序执行过程中的函数调用和局部变量,栈中的数据是有限的,具有固定的生命周期,通常由编译器和运行系统自动管理。
2.内存管理
堆:
内存分配和释放在堆中通常是显式的,开发者需要手动分配内存,同时也需要负责释放不再需要的内存,以避免内存泄漏
栈:
栈内存的分配和释放是隐式的,有编译器和运行时系统管理,当一个函数调用结束时,栈上的局部变量会被自动释放。
3.数据访问速度
堆:
堆内存的访问速度相对较慢,因为动态分配的内存需要查找并分配合适的内存块
栈:
栈内存的访问速度相对较快,因为栈上的数据结构通常具有固定大小,并且可以直接通过指针来访问。
4.存储限制
堆:
堆的大小通常受制于物理内存的大小,他可以动态增长,但可能会受到系统资源的限制
栈:
栈的大小通常是有限的,受到编译器和操作系统的限制,通常不太适合存储大型数据结构。
进程和线程的区别
1.定义
进程:
进程是程序的一次执行实例,它具有独立的内存空间,文件系统和系统资源。每个进程都有自己的地址空间,他们之间又相互独立。
线程:
线程是进程内的执行单元,多个线程可以共享一个进程的内存空间和系统资源。线程之间更轻量级,因为他们共享进程资源。
2.资源消耗
进程:
每个进程都有自己的独立内存空间,因此进程之间的资源消耗相对较大。
线程:
线程共享进程的内存空间,因此线程之间的资源消耗相对较小。
3.通信和同步
进程:
进程之间的通信相对较复杂,通常需要使用进程间通信,如管道,消息队列,共享内存,信号,套接字等,进程之间的同步也需要额外的同步机制。
线程:
线程之间可以更容易地共享数据,通常通过共享内存来实现,也可以使用线程同步机制,如互斥锁,信号量,条件变量等
4.创建和销毁
进程:
创建和销毁进程的开销较大,因为他们需要分配和释放独立的内存空间
线程:
创建和销毁线程的开销较小,因为他们共享进程的资源
5.安全性
进程:
由于进程之间相互隔离,一个进程的崩溃通常不会影响其他进程
线程:
现成之间共享进程的内存空间,因此一个线程的错误可能会影响整个进程
6.并行性
进程:
多个进程可以并行执行,因为他们在不同的地址空间,但并行进程之间的通信和同步较为复杂
线程:
多个线程可以在同一个进程并执行,因为他们共享相同的地址空间,通信和同步相对容易
自旋锁和互斥锁的区别
1.阻塞和忙等待
互斥锁:
当一个线程尝试获取一个已被其他线程持有的互斥锁时,他会被阻塞,直到锁可以使用。这意味着线程会进入休眠状态,不再占用CPU资源
自旋锁:
当一个线程尝试获取一个已被其他线程持有的自旋锁时,他会忙等待,即反复检查锁是否可用。线程不会放弃CPU资源,而是持续检查,直到获取锁。
2.CPU资源占用
互斥锁:
互斥锁的阻塞模式意味着线程在等待锁时不会占用CPU资源,因此在多线程应用中,他可以有效减少CPU资源浪费
自旋锁:
自旋锁在忙等待期间会持续占用CPU资源,可能会导致CPU资源浪费,特别是在高并发情况下
3.使用场景
互斥锁:
适用于低竞争情况下,当锁的竞争频率较低时,互斥锁通常更高效
自旋锁:
适用于高竞争情况下,当锁的竞争频率较高时,自旋锁可能比互斥锁更高效,因为他避免了线程切换的开销
4.实现方式
互斥锁:
通常由操作系统内核提供的原语来实现,可以利用操作系统的调度器进行线程阻塞和唤醒
自旋锁:
通常是通过原子操作实现,它使用硬件原语来实现忙等待,而不是依赖操作系统的调度器
TCP和UDP的区别
1.连接性
TCP:
TCP是面向连接的协议,使用三次握手建立连接,然后在数据传输完毕后使用四次挥手关闭连接
UDP:
UDP是无连接的协议,发送方和接收方在通信前不需要建立连接,也不需要维护连接状态
2.可靠性
TCP:
TCP提供可靠的数据传输,通过确认和重传机制来保证数据的可靠性
UDP:
UDP不提供数据包的确认和重传机制,因此无法保证数据的可靠性
3.有序性
TCP:
TCP保证数据的有序性,发送的数据包会按照发送的顺序一次到达接收方
UDP:
UDP不保证数据的有序性,发送的数据包可能以不同的顺序到达接收方
4.拥塞控制
TCP:
TCP具有拥塞控制机制,通过动态调整发送速率来避免网路拥塞
UDP:
UDP没有拥塞控制机制,发送方可以以任意速率发送数据,因此容易引起网络拥塞
5.开销
TCP:
TCP的开销较大,因为它需要维护连接状态,实现可靠性和有序性等机制
UDP:
UDP的开销较小,因为它没有连接状态和可靠性机制
6.使用场景
TCP:
由于TCP的可靠性和有序性,适用于需要确保数据完整,按顺序到达的应用,比如网页浏览,文件传输
UDP:
UDP适用于实时性要求较高,对可靠性要求相对较低的应用,比如实时游戏,音视频传输等
快速排序
void quickSort(vector<int>& arr, int left, int right){
if(left >= right){
return;
}
int pivot = arr[left];
int low = left;
int high = right;
while(low < high){
while(low < high && arr[high] >= pivot){
high--;
}
while(low < high && arr[left] <= pivot){
low++;
}
}
swap(arr[left], arr[low]);
quickSort(arr, left, low - 1);
quickSort(arr, low + 1, right);
return;
}