零跑科技C++一二面面经
1、new和malloc的区别?
2、32位系统的指针变量占多大字节?int*呢?空类呢?
在32位系统中,指针变量和int* 通常各占用4字节,空类通常大小为1字节。
3、c++多态?
静态多态(编译时多态)
静态多态是在编译期间确定函数调用的绑定方式,主要通过函数重载和模板实现。
函数重载: 在同一作用域内,可以定义多个同名函数,但它们的参数列表(参数个数、类型、顺序)不同。编译器根据函数调用时传入的参数类型和数量来确定调用哪个函数。
c++#include <iostream> class Printer { public: void print(int i) { std::cout << "Printing integer: " << i << std::endl; } void print(double d) { std::cout << "Printing double: " << d << std::endl; } }; int main() { Printer p; p.print(5); // 调用 print(int) p.print(3.14); // 调用 print(double) return 0; }函数模板: 通过模板,可以编写泛型函数,使其能够处理多种数据类型。编译器在编译时根据传入的参数类型生成相应的函数实例。
c++#include <iostream> template <typename T> void print(T t) { std::cout << "Printing: " << t << std::endl; } int main() { print(5); // 打印整数 print(3.14); // 打印浮点数 print("Hello"); // 打印字符串 return 0; }
动态多态(运行时多态)
动态多态是在运行期间确定函数调用的绑定方式,主要通过虚函数和继承实现。
虚函数: 在基类中使用 virtual 关键字声明的成员函数称为虚函数。派生类可以重写这些虚函数。当通过基类指针或引用调用虚函数时,程序会在运行时根据对象的实际类型来决定调用哪个派生类的重写函数。
#include <iostream>
class Animal {
public:
virtual void makeSound() {
std::cout << "Animal makes sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() override {
std::cout << "Dog barks" << std::endl;
}
};
int main() {
Animal* animal = new Dog();
animal->makeSound(); // 输出: Dog barks
delete animal;
return 0;
}4、为什么需要虚析构?虚析构实现原理?
C++中,析构函数负责清理对象资源(如释放动态内存、关闭文件)。当通过基类指针删除派生类对象时,若基类析构函数不是虚函数,C++默认调用基类的析构函数,忽略派生类的析构函数。
实现原理:
虚析构依赖虚表机制。编译器为含虚函数的类生成虚表,虚表指针(vptr)存储在对象中。删除时,根据vptr查找虚表中的析构函数地址,按派生到基类的顺序调用。意外的是,即使基类无其他虚函数,添加虚析构也会引入虚表。
虚表(vtable)机制
虚析构的实现依赖C++的虚函数表(vtable):
- 虚表生成:
- 当类声明虚函数(包括虚析构函数),编译器为该类生成虚表。
- 虚表是一个函数指针数组,存储该类的虚函数地址,包括析构函数。
- 例如,Base有虚析构函数,其虚表可能包含Base::~Base地址;Derived重写后,其虚表包含Derived::~Derived地址。
- 虚表指针(vptr):
- 每个含虚函数的对象在内存布局中包含一个隐藏的虚表指针(vptr),指向其类的虚表。
- vptr通常位于对象开头(具体位置编译器定义)。
- 动态绑定:
- 调用虚函数(包括析构函数)时,运行时通过对象的vptr访问虚表,找到正确函数地址。
- 删除对象时,delete触发析构函数调用,顺序为派生类到基类,确保资源逐级释放。
5、c++可以不写构造函数吗?编译器默认除了构造还会有什么?
是的,C++允许不显式定义构造函数,编译器会自动生成默认构造函数。
如果未定义,编译器默认生成默认构造函数、析构函数、拷贝构造函数和拷贝赋值运算符(C++11起还可能生成移动构造函数和移动赋值运算符)。
生成规则表
| 函数 | 生成条件 | 行为 |
|---|---|---|
| 默认构造函数 | 无构造函数定义 | 成员默认初始化 |
| 析构函数 | 未定义析构函数 | 清理成员,非虚 |
| 拷贝构造函数 | 未定义拷贝构造 | 逐成员浅拷贝 |
| 拷贝赋值运算符 | 未定义赋值运算符 | 逐成员浅赋值 |
| 移动构造函数 | 未定义拷贝/移动/析构,C++11支持 | 逐成员移动 |
| 移动赋值运算符 | 同上 | 逐成员移动赋值 |
6、深拷贝和浅拷贝区别?
浅拷贝: 复制对象时,仅复制成员的值(包括指针地址),不复制指针指向的内容,导致新旧对象共享同一块内存。
**深拷贝:**复制对象时,不仅复制成员变量,还复制指针指向的内容,新对象拥有独立的内存副本。
区别
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制内容 | 仅复制成员值(包括指针地址) | 复制成员值及指针指向的内容 |
| 内存关系 | 共享动态内存 | 独立内存副本 |
| 实现方式 | 默认拷贝构造/赋值 | 需显式定义拷贝构造/赋值 |
| 性能 | 高效,少内存操作 | 较慢,需分配和复制 |
| 风险 | 双重释放、内存冲突 | 无共享问题,安全 |
| 适用场景 | 无指针或简单类 | 含动态内存或复杂结构 |
7、智能指针哪个c++版本引入的,作用?
8、设计模式了解哪些?自己写过吗?单例模式怎么写?单例要返回非静态指针类型怎么弄?
9、队列和栈的区别?
队列(Queue)
定义
队列是一种线性数据结构,遵循**先进先出(FIFO)**原则。数据从一端(队尾,rear)进入,从另一端(队首,front)离开,类似现实中的排队。
基本操作
- 入队(Enqueue):将元素添加到队尾。
- 出队(Dequeue):移除并返回队首元素。
- 查看队首(Front):获取队首元素,不移除。
- 判空(Empty):检查队列是否为空。
- 判满(Full,数组实现时):检查队列是否已满。
实现示例
#include <iostream>
#define MAX_SIZE 100
class Queue {
private:
int arr[MAX_SIZE];
int front, rear;
public:
Queue() : front(0), rear(-1) {}
void enqueue(int value) {
if (rear < MAX_SIZE - 1) arr[++rear] = value;
else std::cout << "Queue full\n";
}
int dequeue() {
if (front <= rear) return arr[front++];
std::cout << "Queue empty\n";
return -1;
}
};
int main() {
Queue q;
q.enqueue(1);
q.enqueue(2);
std::cout << q.dequeue() << "\n"; // 输出 1
std::cout << q.dequeue() << "\n"; // 输出 2
return 0;
}栈(Stack)
定义
栈是一种线性数据结构,遵循**后进先出(LIFO)**原则。数据从一端(栈顶,top)进入和离开,类似叠放的盘子。
基本操作
- 压栈(Push):将元素压入栈顶。
- 弹栈(Pop):移除并返回栈顶元素。
- 查看栈顶(Top/Peek):获取栈顶元素,不移除。
- 判空(Empty):检查栈是否为空。
- 判满(Full,数组实现时):检查栈是否已满。
实现示例
#include <iostream>
#define MAX_SIZE 100
class Stack {
private:
int arr[MAX_SIZE];
int top;
public:
Stack() : top(-1) {}
void push(int value) {
if (top < MAX_SIZE - 1) arr[++top] = value;
else std::cout << "Stack full\n";
}
int pop() {
if (top >= 0) return arr[top--];
std::cout << "Stack empty\n";
return -1;
}
};
int main() {
Stack s;
s.push(1);
s.push(2);
std::cout << s.pop() << "\n"; // 输出 2
std::cout << s.pop() << "\n"; // 输出 1
return 0;
}10、osi七层以及相应协议?网络层的协议?应用层?
1. 物理层
- 功能:
- 传输原始比特流(0和1),定义物理连接(如电缆、电压)。
- 处理硬件接口、信号传输、比特同步。
- 协议/标准:
- Ethernet(IEEE 802.3):定义物理介质和信号。
- USB:通用串行总线标准。
- Wi-Fi(IEEE 802.11):无线物理传输。
- 示例:网线传输电信号。
2. 数据链路层
- 功能:
- 将比特组织成帧(frame),提供节点间可靠传输。
- 错误检测(CRC)、流量控制、MAC寻址。
- 协议:
- Ethernet(IEEE 802.3):MAC地址和帧格式。
- PPP(Point-to-Point Protocol):点对点通信。
- Wi-Fi(IEEE 802.11):无线局域网帧传输。
- 示例:交换机根据MAC地址转发帧。
3. 网络层
- 功能:
- 处理数据包(packet)的路由和转发,定义逻辑地址(如IP地址)。
- 跨网络通信,路径选择。
- 协议:
- IP(Internet Protocol,IPv4/IPv6):核心协议,负责寻址和路由。
- ICMP(Internet Control Message Protocol):错误报告和诊断(如ping)。
- ARP(Address Resolution Protocol):IP地址到MAC地址映射。
- RIP/OSPF:路由协议,计算最佳路径。
- 示例:路由器根据IP地址转发数据包。
4. 传输层
- 功能:
- 提供端到端的可靠数据传输,管理连接和流量。
- 分段、重传、拥塞控制。
- 协议:
- TCP(Transmission Control Protocol):可靠、有序传输。
- UDP(User Datagram Protocol):快速、无连接传输。
- 示例:TCP确保网页数据按序到达。
5. 会话层
- 功能:
- 管理会话,建立、维护和终止通信连接。
- 同步对话、恢复中断。
- 协议:
- NetBIOS:网络基本输入输出系统。
- RPC(Remote Procedure Call):远程调用协议。
- 示例:视频会议中维持会话。
6. 表示层
- 功能:
- 数据格式转换(如加密、压缩、编码)。
- 确保数据在发送和接收端一致。
- 协议:
- SSL/TLS:安全传输层协议,加密数据。
- JPEG/PNG:图像编码格式。
- MIME:多用途互联网邮件扩展。
- 示例:HTTPS加密网页数据。
7. 应用层
- 功能:
- 提供用户接口和网络服务,直接支持应用程序。
- 处理具体应用逻辑。
- 协议:
- HTTP/HTTPS(HyperText Transfer Protocol):网页传输。
- FTP(File Transfer Protocol):文件传输。
- SMTP(Simple Mail Transfer Protocol):邮件发送。
- DNS(Domain Name System):域名解析。
- Telnet/SSH:远程登录。
- 示例:浏览器用HTTP加载网页。
OSI七层协议总结表
| 层级 | 功能 | 典型协议 |
|---|---|---|
| 物理层 | 比特传输 | Ethernet, USB, Wi-Fi |
| 数据链路层 | 帧传输、MAC寻址 | Ethernet, PPP |
| 网络层 | 数据包路由、IP寻址 | IP, ICMP, ARP, RIP, OSPF |
| 传输层 | 端到端传输、可靠性 | TCP, UDP |
| 会话层 | 会话管理 | NetBIOS, RPC |
| 表示层 | 数据转换、加密 | SSL/TLS, JPEG, MIME |
| 应用层 | 用户接口、网络服务 | HTTP, FTP, SMTP, DNS, SSH |
OSI与TCP/IP模型对比
- OSI:7层,理论模型,清晰分层。
- TCP/IP:4层(链路、网络、传输、应用)。
- 映射:
- OSI物理+数据链路 → TCP/IP链路层。
- OSI网络 → TCP/IP网络层。
- OSI传输 → TCP/IP传输层。
- OSI会话+表示+应用 → TCP/IP应用层。
11、网络加密算法知道哪些?
对称加密算法
- 定义:加密和解密使用同一密钥,速度快,适合大数据加密。
- 原理:通过密钥对明文进行变换(如置换、代换),生成密文。
常见算法
- DES(Data Encryption Standard)
- 密钥长度:56位。
- 特点:1977年NIST发布,分组加密(64位块),用Feistel结构。
- 现状:因密钥短,易被暴力破解,已废弃。
- 应用:早期银行系统。
- 3DES(Triple DES)
- 密钥长度:168位(3个56位密钥)。
- 特点:三次DES加密(加密-解密-加密),增强安全性。
- 缺点:速度慢,逐渐被AES取代。
- 应用:过渡性金融系统。
- AES(Advanced Encryption Standard)
- 密钥长度:128、192、256位。
- 特点:2001年NIST选定,基于Rijndael算法,分组128位,提供高安全性和效率。
- 模式:CBC(带初始化向量)、GCM(带认证)。
- 应用:HTTPS、VPN、磁盘加密。
非对称加密算法
- 定义:使用公钥加密、私钥解密,或反之,解决密钥分发问题。
- 原理:基于数学难题(如大整数分解、离散对数)。
常见算法
- RSA(Rivest-Shamir-Adleman)
- 密钥长度:1024、2048、4096位(推荐2048+)。
- 原理:基于大素数乘积分解难度。
- 公钥:(e, n),私钥:(d, n)。
- 加密:C = M^e mod n,解密:M = C^d mod n。
- 特点:安全但计算慢。
- 应用:SSL/TLS证书、数字签名。
- ECC(Elliptic Curve Cryptography)
- 密钥长度:160-256位(相当于RSA 3072位)。
- 原理:基于椭圆曲线上的离散对数问题。
- 特点:短密钥高安全性,低计算开销。
- 应用:移动设备加密、区块链(如比特币)。
- DSA(Digital Signature Algorithm)
- 密钥长度:1024、2048位。
- 特点:仅用于签名,不加密,基于离散对数。
- 应用:软件分发验证。
哈希算法(消息摘要算法)
- 定义:将任意长度输入映射为固定长度输出(摘要),不可逆,用于验证完整性。
- 原理:单向函数,抗碰撞性。
常见算法
- MD5(Message Digest 5)
- 输出长度:128位。
- 特点:1991年发布,快速但易碰撞。
- 现状:安全性低,已废弃。
- 应用:早期校验(如文件下载)。
- SHA-1(Secure Hash Algorithm 1)
- 输出长度:160位。
- 特点:NIST标准,2005年发现碰撞漏洞。
- 现状:不推荐使用。
- 应用:早期SSL证书。
- SHA-2(SHA-256、SHA-512)
- 输出长度:256、512位等。
- 特点:改进SHA-1,抗碰撞性强。
- 应用:数字签名、区块链。
- SHA-3
- 输出长度:可变(如256、512位)。
- 特点:2015年NIST发布,基于Keccak算法,抗量子威胁潜力。
- 应用:新兴安全场景。
对比表
| 类型 | 算法示例 | 密钥长度 | 作用 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 对称加密 | AES, DES | 128-256位 | 数据加密 | 速度快 | 密钥分发难 |
| 非对称加密 | RSA, ECC | 2048位+ / 256位 | 密钥交换、签名 | 安全分发 | 计算慢 |
| 哈希算法 | SHA-256, MD5 | 无密钥,输出256位+ | 数据完整性验证 | 快速、无密钥 | 不可逆 |
| 密钥交换 | DH, ECDH | 可变 | 协商对称密钥 | 安全协商 | 需认证 |
12、tcp的三次握手四次挥手回收写过吗?写服务器基本模式?服务器端socket调用顺序?
TCP三次握手
定义
三次握手是TCP建立连接的过程,通过三次数据交换确认客户端和服务器均准备好通信。
步骤
- SYN(同步)
- 客户端发送SYN包(标记SYN=1),包含初始序列号(Seq=x)。
- 状态:客户端进入SYN_SENT。
- SYN-ACK(同步确认)
- 服务器收到SYN,回复SYN+ACK包(SYN=1,ACK=1),包含服务器序列号(Seq=y)和确认号(Ack=x+1)。
- 状态:服务器进入SYN_RCVD。
- ACK(确认)
- 客户端收到SYN-ACK,回复ACK包(ACK=1),确认号为Ack=y+1,序列号为Seq=x+1。
- 状态:客户端和服务器进入ESTABLISHED。
流程图
客户端 服务器
| SYN(x) |
|------------------>|
| SYN(y), ACK(x+1)|
|<------------------|
| ACK(y+1) |
|------------------>|TCP四次挥手
定义
四次挥手是TCP断开连接的过程,确保双方数据传输完成并释放资源。
步骤
- FIN(结束)
- 主动关闭方(假设客户端)发送FIN包(FIN=1,Seq=x),表示无数据发送。
- 状态:客户端进入FIN_WAIT_1。
- ACK(确认)
- 被动方(服务器)收到FIN,回复ACK包(ACK=1,Ack=x+1),确认收到关闭请求。
- 状态:服务器进入CLOSE_WAIT,客户端进入FIN_WAIT_2。
- FIN(结束)
- 服务器发送完剩余数据后,发送FIN包(FIN=1,Seq=y)。
- 状态:服务器进入LAST_ACK。
- ACK(确认)
- 客户端回复ACK包(ACK=1,Ack=y+1)。
- 状态:客户端进入TIME_WAIT,服务器进入CLOSED,客户端等待后也CLOSED。
流程图
客户端 服务器
| FIN(x) |
|------------------>|
| ACK(x+1) |
|<------------------|
| |
| FIN(y) |
|<------------------|
| ACK(y+1) |
|------------------>|服务器基本模式
基于TCP的服务器模式通过Socket API实现,接受客户端连接并处理请求。
基本流程
- 创建套接字:分配服务器端Socket。
- 绑定地址:指定IP和端口。
- 监听连接:等待客户端请求。
- 接受连接:建立与客户端的连接。
- 数据处理:收发数据。
- 关闭连接:释放资源。
示例
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
int main() {
// 1. 创建套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
std::cerr << "Socket creation failed\n";
return 1;
}
// 2. 绑定地址
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY; // 任意IP
addr.sin_port = htons(8080); // 端口8080
if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
std::cerr << "Bind failed\n";
return 1;
}
// 3. 监听
if (listen(server_fd, 5) == -1) { // 最大5个排队连接
std::cerr << "Listen failed\n";
return 1;
}
std::cout << "Server listening on port 8080...\n";
// 4. 接受连接
sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
std::cerr << "Accept failed\n";
return 1;
}
// 5. 数据处理
char buffer[1024] = {0};
read(client_fd, buffer, 1024);
std::cout << "Received: " << buffer << "\n";
const char* response = "Hello from server!";
write(client_fd, response, strlen(response));
// 6. 关闭连接
close(client_fd);
close(server_fd);
return 0;
}服务器端Socket调用顺序
socket()
- 创建套接字,返回文件描述符。
- 参数:协议族(AF_INET)、类型(SOCK_STREAM)、协议(0)。
bind()
- 绑定IP和端口到套接字。
- 参数:套接字FD、地址结构(sockaddr)、结构大小。
listen():设置套接字为监听状态,指定连接队列长度。
accept():接受客户端连接,返回新套接字FD用于通信。
数据处理(read()/write()):收发数据,阻塞或非阻塞模式。
close():关闭套接字,释放资源。
13、进程和线程的区别?线程可以共享哪些资源?
进程(Process)
定义
进程是程序的一次执行实例,是操作系统分配资源的基本单位,拥有独立的内存空间和系统资源。
特性
- 独立性:每个进程有私有虚拟地址空间,互不干扰。
- 资源:包括代码段、数据段、堆、栈、文件描述符等。
- 状态:运行、就绪、阻塞等。
- 开销:创建、切换和销毁耗费较多资源(如fork)。
线程(Thread)
定义
线程是进程内的执行流,是CPU调度的基本单位,多个线程共享进程的资源。
特性
- 依赖性:线程存在于进程内,无独立地址空间。
- 轻量级:创建和切换开销小(如pthread_create)。
- 并发:同一进程内线程并行执行,提高效率。
进程与线程的区别
| 特性 | 进程 | 线程 |
|---|---|---|
| 定义 | 程序执行实例,资源分配单位 | 进程内执行流,调度单位 |
| 内存空间 | 独立虚拟地址空间 | 共享进程地址空间 |
| 资源 | 独占代码、数据、堆、栈等 | 共享代码、数据、堆,私有栈 |
| 创建开销 | 高(fork复制内存) | 低(仅分配栈和上下文) |
| 通信 | IPC(如管道、消息队列) | 直接访问共享内存 |
| 独立性 | 强,崩溃不影响其他进程 | 弱,一线程崩溃可能影响进程 |
| 切换开销 | 高(上下文切换涉及内存映射) | 低(仅切换栈和寄存器) |
| 示例 | 运行两个程序 | 一个程序内多任务 |
共享资源
线程运行在同一进程内,共享进程的大部分资源,包括:
- 代码段(Text Segment):存储程序指令,所有线程执行相同代码。
- 数据段(Data Segment):全局变量和静态变量,线程可直接访问。
- 堆(Heap):动态分配内存(如new/malloc),线程共享。
- 文件描述符:打开的文件、网络套接字等,线程可共用。
- 信号处理配置:信号掩码和处理函数(部分系统依赖)。
- 当前工作目录:进程级属性,线程共享。
私有资源
线程也有独立部分,确保执行隔离:
- 栈(Stack):局部变量、函数调用帧,每个线程独有。
- 寄存器:如程序计数器(PC)、栈指针(SP),线程切换时保存。
- 线程ID:唯一标识线程。
- 线程局部存储(TLS):如__thread变量,线程私有数据。
14、遇到过死锁吗?如何避免?
死锁的定义
死锁是指多个进程或线程因循环等待彼此持有的资源而无法继续执行的状态。例如,线程A持有资源1等待资源2,线程B持有资源2等待资源1,二者永久阻塞。
死锁的四个必要条件
- 互斥(Mutual Exclusion):资源只能被一个进程/线程独占。
- 持有并等待(Hold and Wait):持有至少一个资源,同时等待其他资源。
- 不可抢占(No Preemption):资源只能由持有者主动释放。
- 循环等待(Circular Wait):形成资源请求的闭环。
死锁的成因
- 资源竞争:多线程争抢锁、文件或内存。
- 顺序不当:请求资源的顺序不一致。
- 设计缺陷:未考虑并发场景的复杂性。
如何避免死锁
预防死锁(破坏必要条件)
破坏死锁的四个条件之一:
破坏互斥
- 方法:尽量使用非独占资源(如读写锁替代互斥锁)。
- 限制:不适用于必须独占的资源(如文件写入)。
破坏持有并等待
- 方法:要求线程一次性申请所有资源,否则不持有任何资源。
破坏不可抢占
- 方法:允许操作系统抢占资源(少见,需OS支持)。
- 限制:C++标准库无直接支持。
破坏循环等待
- 方法:规定资源请求顺序(如按锁地址排序)。
避免死锁(动态检测)
银行家算法:
- 原理:在分配资源前检查是否会导致死锁。
- 适用:操作系统资源管理,线程中少用。
超时机制:
- 方法:尝试锁超时后放弃,重新调度。
检测与恢复
- 检测:
- 方法:构建资源分配图,检查循环依赖。
- 工具:调试器(如gdb)或系统日志。
- 恢复:
- 方法:终止一个线程/进程,释放资源。
- 示例:数据库死锁检测后回滚事务。
15、进程间通信?
管道(Pipe)
- 定义:单向数据流通道,通常用于父子进程通信。
- 类型:
- 匿名管道:无名,限于亲缘进程(如fork后)。
- 命名管道(FIFO):有文件路径,可用于无关进程。
- 原理:内核维护缓冲区,写端输入,读端输出。
消息队列(Message Queue)
- 定义:进程通过队列发送和接收消息,支持结构化数据。
- 原理:内核维护消息链表,进程按ID存取。
共享内存(Shared Memory)
- 定义:多个进程映射同一物理内存,实现直接数据共享。
- 原理:内核分配共享区域,进程通过映射访问。
信号量(Semaphore)
- 定义:用于同步和互斥的计数器,控制资源访问。
- 原理:通过P(减少)和V(增加)操作管理。
信号(Signal)
- 定义:异步通知机制,发送简单事件。
- 原理:操作系统向进程发送信号,触发处理函数。
套接字(Socket)
- 定义:网络通信接口,也可用于本地进程间通信。
- 原理:基于TCP/UDP协议,支持跨主机或本地。
文件映射(File Mapping)
- 定义:将文件映射到内存,进程共享访问。
- 原理:类似共享内存,但基于文件系统。
IPC方法对比
| 方法 | 通信方式 | 速度 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 管道 | 数据流 | 中等 | 亲缘进程单向通信 | 简单 | 单向,受限 |
| 消息队列 | 消息 | 中等 | 结构化数据传递 | 双向、灵活 | 开销大 |
| 共享内存 | 内存共享 | 很快 | 大数据共享 | 高效 | 需同步 |
| 信号量 | 同步控制 | 快 | 资源访问控制 | 轻量 | 不传数据 |
| 信号 | 事件通知 | 快 | 简单通知 | 异步 | 信息少 |
| 套接字 | 网络通信 | 中等 | 跨主机/本地通信 | 通用 | 复杂 |
| 文件映射 | 文件共享 | 中等 | 持久化数据共享 | 可持久化 | I/O依赖 |
16、中断?硬中断,软中断?
中断(Interrupt)
定义
中断是CPU暂停当前执行流程,转而处理特殊事件的机制,通常由硬件设备或软件触发。中断后,CPU保存上下文,跳转到中断处理程序,处理完恢复原任务。
作用
- 响应性:快速处理外部事件(如键盘按下)。
- 多任务:支持时间片调度(如时钟中断)。
- 异常处理:捕获错误(如除零)。
工作流程
- 触发:硬件或软件发出中断信号。
- 保存上下文:CPU存储寄存器(如PC、SP)。
- 跳转:根据中断向量表(Interrupt Vector Table, IVT)调用处理程序。
- 处理:执行中断服务例程(ISR)。
- 恢复:返回原任务。
硬中断(Hardware Interrupt)
定义
硬中断由外部硬件设备发起,通过中断控制器(如PIC、APIC)通知CPU,通常异步发生。
特点
- 来源:硬件(如键盘、鼠标、网卡)。
- 优先级:通过IRQ(中断请求线)区分,高优先级可抢占低优先级。
- 异步性:不可预测,需立即响应。
示例
- 时钟中断:每秒触发多次,驱动进程调度。
- 键盘中断:按键产生信号,CPU读取输入。
实现机制
- 中断控制器:管理多个设备的中断请求,分配IRQ号。
- 中断向量表:映射IRQ到处理程序地址。
软中断(Software Interrupt)
定义
软中断由软件或内核触发,通常通过指令(如int)或异常发起,常用于系统调用或延迟处理硬中断任务。
特点
- 来源:软件(如程序调用write())。
- 同步性:由代码显式触发,可控。
- 延迟执行:常用于硬中断的下半部处理(bottom half)。
示例
- 系统调用:open()触发软中断,切换到内核态。
- 异常:除零错误触发软中断,调用异常处理。
- Linux软中断:网络数据包处理(softirq)。
实现机制
- 指令触发:如x86的int n(n为中断号)。
- 上下文切换:用户态到内核态。
- 软中断表:Linux中如softirq_vec,处理网络、调度等任务。
硬中断与软中断区别
| 特性 | 硬中断 | 软中断 |
|---|---|---|
| 触发来源 | 硬件设备 | 软件或内核 |
| 发生时机 | 异步,外部事件 | 同步,代码执行 |
| 优先级 | 高,由IRQ决定 | 较低,通常延迟处理 |
| 处理方式 | ISR立即执行 | 可延迟(如Linux softirq) |
| 示例 | 键盘输入、时钟 | 系统调用、网络包处理 |
| 开销 | 中断切换成本 | 上下文切换成本 |
17、孤儿进程,僵尸进程?
孤儿进程(Orphan Process)
定义
孤儿进程是指父进程在子进程结束前退出,子进程被操作系统收养,通常由init进程(PID 1)接管,继续运行直到完成。
产生原因
- 父进程异常终止(如崩溃)。
- 父进程主动退出,未等待子进程(如未调用wait())。
生命周期
- 父进程创建子进程(fork())。
- 父进程提前结束(exit())。
- 子进程成为孤儿,PPID变为1(init)。
- 子进程正常结束,init自动回收。
僵尸进程(Zombie Process)
定义
僵尸进程是指子进程已结束(调用exit()或异常终止),但父进程未通过wait()或waitpid()回收其退出状态,子进程的进程表项仍保留,处于“僵尸”状态(状态码Z)。
产生原因
- 父进程未及时调用wait()。
- 父进程长期运行,未处理子进程状态。
生命周期
- 父进程创建子进程。
- 子进程结束,释放资源但保留进程表项(PID、退出码)。
- 父进程未回收,子进程成为僵尸。
- 父进程调用wait()或结束,僵尸被清理。
18、常用的系统调度算法?
1. 先来先服务(FCFS,First-Come, First-Served)调度算法
原理: 按照进程到达就绪队列的顺序分配CPU,先到达的进程先被调度执行。
优点: 实现简单,公平地处理进程。
缺点: 可能导致“长进程”占用CPU过长时间,导致“短进程”等待过久,增加平均周转时间。
示例:
进程 到达时间 执行时间
P1 0 5
P2 1 3
P3 2 2
执行顺序:P1 -> P2 -> P3
完成时间:5 8 10
平均周转时间:(5-0 + 8-1 + 10-2)/3 = 6.672. 短作业优先(SJF,Shortest Job First)调度算法
- 原理: 优先调度估计运行时间最短的进程。
- 优点: 能最小化平均周转时间。
- 缺点: 需要准确预测进程的执行时间,可能导致“长进程”饿死。
- 示例:
进程 到达时间 执行时间
P1 0 7
P2 2 4
P3 4 1
执行顺序:P1 -> P3 -> P2
完成时间:7 8 12
平均周转时间:(7-0 + 8-4 + 12-2)/3 = 73. 优先级调度算法
- 原理: 为每个进程分配一个优先级,优先级高的进程先被调度执行。
- 优点: 适用于需要对关键任务进行及时响应的系统。
- 缺点: 可能导致低优先级进程长时间得不到执行,发生“饥饿”现象。
- 示例:
进程 到达时间 执行时间 优先级(越小越高)
P1 0 5 2
P2 1 3 1
P3 2 2 3
执行顺序:P1 -> P2 -> P3
完成时间:5 8 10
平均周转时间:(5-0 + 8-1 + 10-2)/3 = 6.674. 时间片轮转(RR,Round Robin)调度算法
- 原理: 将CPU时间划分为固定长度的时间片,按顺序循环分配给各个进程。
- 优点: 公平地分配CPU时间,适用于交互式系统。
- 缺点: 时间片的大小对系统性能影响较大,过小会导致频繁的上下文切换,过大则可能导致响应时间增加。
- 示例:
进程 到达时间 执行时间 时间片=2
P1 0 5
P2 0 3
P3 0 2
执行顺序:P1 P2 P3 P1 P2 P1
时间轴: 0-2 2-4 4-6 6-8 8-10 10-11
完成时间:11 10 6
平均周转时间:(11-0 + 10-0 + 6-0)/3 = 95. 多级反馈队列调度算法
- 原理: 结合优先级调度和时间片轮转,根据进程的行为动态调整其优先级和时间片大小。
- 优点: 能兼顾多方面的系统目标,如提高系统吞吐量和缩短平均周转时间。
- 缺点: 实现复杂,需要合理设置各级队列的优先级和时间片大小。
- 示例:
队列 时间片 进程
Q1 2 P1(5), P2(3)
Q2 4 P1剩余
执行:P1(2) P2(2) P2(1) P1(3)
完成时间:P1=9, P2=5
平均周转时间:(9+5)/2 = 7