进程相关面试题20道

news2025/5/15 22:33:11

一、基础概念与原理

1.进程的定义及其与程序的本质区别是什么?

答案:进程是操作系统分配资源的基本单位,是程序在数据集合上的一次动态执行过程。核心区别:​

  • 动态性:程序是静态文件,进程是动态执行实例(有生命周期:创建→运行→终止)​
  • 资源分配:进程拥有独立的地址空间、文件描述符表等资源,程序本身不占用资源​
  • 并发性:多个进程可并发执行,程序需通过进程实例才能运行

2.进程主要有哪些状态?阻塞态和就绪态的本质区别是什么?

答案:就绪态、运行态、阻塞态。

        阻塞态 vs 就绪态:​

  • 就绪态:进程具备运行条件,仅等待 CPU 调度(资源已就绪,如内存、文件句柄)​
  • 阻塞态:进程因等待 I/O、信号量等事件暂停执行,即使 CPU 空闲也无法运行(资源未就绪)

3.为什么进程地址空间需要隔离?如何实现?

一、进程地址空间隔离的核心目的
  1. 安全性

    • 防止恶意进程通过内存篡改其他进程的数据(如病毒程序直接修改系统关键进程的内存)。
    • 限制用户态进程访问内核空间地址,避免因非法操作导致系统崩溃。
  2. 稳定性

    • 单个进程因内存越界、访问非法地址等问题崩溃时,不会影响其他进程的地址空间(如浏览器中某标签页崩溃不影响其他标签页)。
  3. 公平性

    • 每个进程拥有独立的虚拟地址空间,避免进程间直接竞争物理内存,操作系统通过页表映射和内存管理策略(如分页、交换)实现资源的公平分配。
  4. 兼容性

    • 允许不同进程使用相同的虚拟地址(如多个进程同时运行同一个程序),通过页表映射到不同的物理地址,避免地址冲突。
二、实现方式:基于 MMU 和页表的地址隔离机制

进程地址空间隔离的核心实现依赖 内存管理单元(MMU) 和 页表(Page Table),具体包括以下技术细节:

1. MMU 与页表映射的基本原理

  • MMU 功能:将进程使用的 虚拟地址(Virtual Address) 转换为物理内存中的 物理地址(Physical Address),同时实现地址空间隔离和权限控制。
  • 页表:每个进程拥有独立的页表(内核共享同一套页表),页表记录虚拟地址到物理地址的映射关系,以及访问权限(读 / 写 / 执行、用户态 / 内核态等)。
  • 页表基址寄存器(CR3):CPU 通过该寄存器找到当前进程的页表基地址,切换进程时更新该寄存器,实现页表隔离。

2. 页表结构的具体形式(以 x86 架构为例)

(1)32 位系统:三级页表(10+10+12 划分)
  • 虚拟地址长度:32 位(4GB 地址空间)。
  • 页大小:4KB(12 位页内偏移,2^12 = 4KB)。
  • 页表分级
    • 一级页表(页目录表,Page Directory, PD):10 位(对应虚拟地址高 10 位),指向二级页表基址。
    • 二级页表(页中间目录表,Page Directory Pointer Table, PDPT):10 位(中间 10 位),指向三级页表基址。
    • 三级页表(页表,Page Table, PT):10 位(低 10 位),指向物理页帧基址。
    • 页内偏移:12 位(最低 12 位,对应 4KB 页内地址)。
  • 地址空间划分
    • 用户空间:0x00000000 ~ 0xBFFFFFFF(3GB),用户态进程可访问。
    • 内核空间:0xC0000000 ~ 0xFFFFFFFF(1GB),仅内核态可访问。
(2)64 位系统:四级 / 五级页表(以 x86_64 为例)
  • 常见虚拟地址模式
    • 48 位虚拟地址(常用,如 Linux 的 x86_64)
      • 地址范围:0x0000000000000000 ~ 0x0000ffffffffffff(低 128TB,用户空间),0xffff000000000000 ~ 0xffffffffffffffff(高 128TB,内核空间)。
      • 页大小:4KB(12 位偏移)或 2MB/1GB(大页,减少页表级数)。
      • 页表分级(四级页表,9+9+9+9+12)
        • 一级页表(PGD,页全局目录):9 位。
        • 二级页表(PUD,页上层目录):9 位。
        • 三级页表(PMD,页中间目录):9 位。
        • 四级页表(PTE,页表项):9 位,指向物理页帧基址。
        • 页内偏移:12 位。
    • 57 位虚拟地址(支持更大地址空间)
      • 地址范围:0x0000000000000000 ~ 0x000fffffffffffffff(低 128PB),0xfff0000000000000 ~ 0xffffffffffffffff(高 128PB)。
      • 页表分级(五级页表,9+9+9+9+9+12):在四级页表基础上增加一级页表(P4D,四级页目录),每级 9 位,共 5 级页表,页内偏移 12 位。

3. 虚拟地址空间划分的细节(64 位补充)

  • x86_64 架构的典型划分
    • 用户空间(低地址)
      • 范围:0x0000000000000000 ~ 0x0000ffffffffffff(128TB),采用符号扩展(高位补 0),用户态进程可访问。
    • 内核空间(高地址)
      • 范围:0xffff000000000000 ~ 0xffffffffffffffff(128TB),采用符号扩展(高位补 1),仅内核态(CPU 特权级 ring 0)可访问。
    • 地址空间隔离原理
      • 用户态进程只能访问页表中标记为 “用户态可访问” 的页表项(PTE 中的 U 位为 1),内核空间的页表项 U 位为 0,用户态访问时触发权限错误(page fault)。
      • 即使不同进程的虚拟地址相同(如都访问 0x1000),通过各自的页表映射到不同的物理地址,实现 “地址空间独立”。

4. 页表项(PTE)的权限控制

每个页表项包含以下关键标志位,实现细粒度隔离:

 
  • R/W 位:是否允许写操作(0 表示只读,1 表示可写)。
  • U/S 位:用户态(U=1)或内核态(S=0)可访问。
  • P 位:是否存在于物理内存(0 表示页在磁盘交换区,触发缺页中断)。
  • XD 位(Execute Disable):是否禁止执行(防止代码注入攻击)。

5. 内核空间的共享与隔离

  • 共享性:所有进程共享同一套内核页表(通过内核页表基址寄存器切换),内核代码和数据在物理内存中仅存一份,节省内存。
  • 隔离性:用户态进程无法直接访问内核页表项(U/S 位为 0),必须通过系统调用(陷入内核态)才能访问内核空间,确保内核地址空间的安全性。

二、调度与资源管理

4. 时间片轮转(RR)调度算法的时间片长度对系统性能有何影响?

答案:​

  • 时间片过长:退化为 FCFS 算法,交互式任务响应延迟增加(如时间片 1s,用户按键需等待 1s 才能处理)​
  • 时间片过短:上下文切换频率增加(如时间片 1ms,1000 次 / 秒切换),CPU 开销上升(假设每次切换耗时 1μs,CPU 利用率降低 10%)​
  • 最优策略:根据典型交互任务处理时间设置(如 10-100ms),平衡响应时间和切换开销

5. 简述多级反馈队列调度算法的核心思想,为何能兼顾交互式和批处理任务?

答案:核心思想:​

  • 设置多个优先级队列,优先级越高时间片越短(如 Q1 时间片 10ms,Q2 时间片 20ms,Q3 时间片 40ms)​
  • 新进程先进入最高优先级队列,时间片用完未完成则降级到下一级队列​
  • 抢占策略:高优先级队列有任务时,中断低优先级队列任务​

优势:​

  • 交互式任务(如终端命令)在高优先级队列快速响应(短时间片)​
  • 批处理任务(如编译程序)降级到低优先级队列,充分利用剩余时间片

三、同步与互斥

6.什么是临界资源?临界区与临界资源的关系是什么?

答案:​

  • 临界资源:一次仅允许一个进程访问的共享资源(如打印机、全局变量、文件)​
  • 临界区:访问临界资源的代码段(需保证互斥执行)​

关系:临界区是操作临界资源的代码逻辑,临界资源是被保护的对象。多个进程的临界区若操作同一临界资源,需通过同步机制保证互斥。

7. 自旋锁(Spinlock)和互斥锁(Mutex)的适用场景有何不同?

答案:​

特性​

自旋锁​

互斥锁​

等待方式​

忙等待(循环检查锁状态)​

阻塞等待(进入睡眠队列)​

上下文切换​

无(适用于锁持有时间极短)​

有(适用于锁持有时间较长)​

适用场景​

内核态、多核 CPU、短临界区​

用户态、单核 CPU、长临界区​

优先级反转​

不支持​

支持(通过优先级继承机制)​

一、什么是优先级反转?

优先级反转(Priority Inversion) 是实时操作系统(RTOS)或多任务系统中可能出现的一种调度异常现象:高优先级任务被低优先级任务间接阻塞,且阻塞时间可能被中间优先级任务延长,导致高优先级任务的执行延迟远超预期。
本质原因是:低优先级任务持有高优先级任务需要的共享资源(如互斥锁),而中间优先级任务抢占了低优先级任务的执行,导致低优先级任务无法及时释放资源,进而阻塞高优先级任务

二、具体例子说明

场景设定:
  • 3 个任务:高优先级任务 H(优先级最高)、中优先级任务 M、低优先级任务 L(优先级最低)。
  • 任务 L 和 H 共享一个临界资源(如互斥锁保护的变量)。
执行过程:
  1. 初始状态:任务 L 正在运行,并获取了临界资源的互斥锁,进入临界区。
  2. H 就绪:此时任务 H 就绪,由于优先级高于 L,操作系统调度 H 执行。但 H 需要访问临界资源,发现锁被 L 持有,只能阻塞等待 L 释放锁。
  3. M 抢占:任务 L 恢复运行后,尚未退出临界区时,任务 M 就绪(优先级高于 L 但低于 H)。由于 M 优先级更高,操作系统调度 M 执行,抢占 L 的 CPU 时间。
  4. 阻塞延长M 持续执行,导致 L 无法及时释放临界资源,H 只能一直等待 M 执行完毕,L 才能继续运行并释放锁。
结果:
  • 高优先级任务 H 被低优先级任务 L 阻塞,且阻塞时间被中间优先级任务 M 显著延长,违背了 “高优先级任务优先执行” 的调度目标。

三、如何解决优先级反转?

1. 优先级继承协议(Priority Inheritance Protocol)
  • 核心思想:当高优先级任务 H 因等待低优先级任务 L 持有的资源而阻塞时,临时将 L 的优先级提升到 H 的优先级,使其尽快执行并释放资源。
  • 例子中的修复
    • 当 H 阻塞等待 L 的锁时,L 的优先级被提升至 H 的优先级。
    • M 优先级低于临时提升后的 L,无法抢占 LL 会优先执行并释放锁,H 得以继续运行。
2. 优先级天花板协议(Priority Ceiling Protocol)
  • 核心思想:为每个临界资源分配一个 “优先级天花板”(等于所有可能访问该资源的任务中的最高优先级)。当任务获取资源时,其优先级被提升至该资源的优先级天花板,直到释放资源。
  • 优势:提前避免中间优先级任务抢占,直接将持有资源的任务优先级提升到可能的最高值。
3. 使用非阻塞同步机制
  • 如无锁编程(Lock-Free)或原子操作,避免任务因等待锁而阻塞,但实现复杂度较高。

四、为什么自旋锁没有优先级反转问题?

  • 忙等待(Busy Waiting):等待锁的线程(如 T1)不会阻塞睡眠,而是持续在 CPU 上循环检查锁状态,直到获取锁。
  • 禁止内核抢占(Preemption Disabled):在多数内核实现中(如 Linux),获取自旋锁时会临时关闭内核抢占功能,确保持有锁的线程(如 T3)在临界区内不会被其他线程(包括中间优先级 T2)抢占。

典型场景:​

  • 自旋锁:多核 CPU 下线程频繁访问缓存友好的共享变量(如计数器)​
  • 互斥锁:I/O 操作前的设备访问控制(需等待磁盘响应,锁持有时间长)

四、死锁与异常处理

8. 死锁预防和死锁避免的核心区别是什么?银行家算法属于哪一类?

答案:​

  • 死锁预防:静态策略,在资源分配前破坏死锁必要条件(如禁止循环等待),可能降低资源利用率​
  • 死锁避免:动态策略,在资源分配时通过安全性检查(如银行家算法)确保系统始终处于安全状态​

银行家算法属于死锁避免,核心步骤:​

  1. 记录每个进程的最大需求(Max)、已分配资源(Allocation)、剩余需求(Need=Max-Allocation)​
  1. 计算系统可用资源(Available),模拟资源分配并检查是否存在安全序列(所有进程均可按某种顺序获得所需资源)

9. 僵尸进程和孤儿进程的区别是什么?如何回收僵尸进程?

答案:​

  • 僵尸进程:子进程已终止,但父进程未调用wait()/waitpid()回收状态,PCB 仍保留在进程表中(状态为 ZOMBIE)​
  • 孤儿进程:父进程先于子进程终止,子进程被 init 进程(PID=1)收养,init 会定期回收其状态​

回收僵尸进程:​

  1. 父进程调用waitpid(pid, &status, 0)主动回收指定子进程​
  1. 注册 SIGCHLD 信号处理函数,在信号中调用waitpid(-1, NULL, WNOHANG)非阻塞回收所有子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

// SIGCHLD信号处理函数:回收子进程资源
void handle_sigchld(int sig) {
    int status;
    pid_t pid;

    // 循环回收所有已终止的子进程(避免多个子进程同时退出时漏收)
    // waitpid(-1, &status, WNOHANG) 表示回收任意子进程(-1),非阻塞(WNOHANG)
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status)) {  // 子进程正常退出
            printf("回收子进程 PID %d,退出状态:%d\n", pid, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {  // 子进程被信号终止
            printf("回收子进程 PID %d,被信号 %d 终止\n", pid, WTERMSIG(status));
        }
    }
}

int main() {
    // 注册SIGCHLD信号处理函数
    struct sigaction sa;
    sa.sa_handler = handle_sigchld;  // 绑定处理函数
    sigemptyset(&sa.sa_mask);         // 信号处理期间不屏蔽其他信号
    sa.sa_flags = 0;                  // 无特殊标志(可替换为SA_RESTART避免系统调用被中断)

    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction失败");
        return 1;
    }

    // 创建子进程
    pid_t child_pid = fork();
    if (child_pid == -1) {
        perror("fork失败");
        return 1;
    }

    if (child_pid == 0) {  // 子进程
        printf("子进程 PID %d 运行中...\n", getpid());
        sleep(2);  // 模拟子进程运行
        exit(10);  // 子进程退出,状态码10
    } else {  // 父进程
        printf("父进程 PID %d 等待子进程退出...\n", getpid());
        while (1) {  // 父进程保持运行,等待信号触发
            sleep(1);
        }
    }

    return 0;
}

五、进程间通信(IPC)

10. 共享内存为何是最高效的 IPC 方式?其主要缺点是什么?

答案:高效原因:​

  • 无需内核空间和用户空间的数据拷贝(如管道 / 消息队列需两次拷贝:用户→内核→用户)​
  • 直接通过指针访问内存,省去协议解析和序列化开销​

主要缺点:​

  • 同步复杂:需手动实现同步机制(信号量、互斥锁),否则易引发竞态条件​
  • 地址空间依赖:依赖共享内存的物理地址或键值,跨平台兼容性差​
  • 数据一致性风险:多个进程同时修改数据时若未正确同步,导致脏读 / 幻读

shared_memory_counter.c:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>
#include<semaphore.h>

#define SHM_NAME "/my_shared_memory"
#define SEM_NAME "/my_semaphore"
#define SHARED_SIZE sizeof(int)

int main(){
    //创建或者打开共享内存
    int fd = shm_open(SHM_NAME,O_CREAT | O_RDWR,0666);
    if (fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }

    //设置共享内存的大小
    if(ftruncate(fd, SHARED_SIZE) == -1){
        perror("ftruncate");
        exit(EXIT_FAILURE);
    }

    //将共享内存映射到进程的地址空间
    int *shared_counter = mmap(NULL,SHARED_SIZE,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if (shared_counter == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    //初始化计数器
    *shared_counter = 0;
    printf("共享计数器已初始化,初始值为: %d\n", *shared_counter);

    //创建或打开信号量用于同步
    sem_t *semaphore = sem_open(SEM_NAME,O_CREAT,0666,1);
    if (semaphore == SEM_FAILED) {
        perror("sem_open");
        exit(EXIT_FAILURE);
    }

    //创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if(pid == 0){
        //子进程:执行另外一个程序
        /*
        第一个参数 const char *path
        后续参数 const char *arg0, ...
        含义:传递给新程序的命令行参数列表,必须以 NULL 结尾(标记参数列表结束)。
        */
        execl("./child_process","child_process",NULL);
        perror("execl");  // 如果执行到这里,表示execl失败
        exit(EXIT_FAILURE);
    }else{
        // 父进程:直接修改共享变量
        for(int i = 0;i < 5;i++){
            sem_wait(semaphore);
            (*shared_counter)++;
            printf("父进程修改后,计数器值为: %d\n", *shared_counter);
            sem_post(semaphore);  // 释放信号量
            sleep(1);
        }

        //等待子进程结束
        wait(NULL);
        printf("父进程完成,最终计数器值为: %d\n", *shared_counter);

        // 修改后顺序(正确):
        munmap(shared_counter, SHARED_SIZE);
        close(fd);
        sem_close(semaphore);
        sem_unlink(SEM_NAME);
        shm_unlink(SHM_NAME);
    }

    return 0;
}

child_process.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>

#define SHM_NAME "/my_shared_memory"
#define SEM_NAME "/my_semaphore"
#define SHARED_SIZE sizeof(int)

int main(){
    //打开已经存在的共享内存对象
    int fd = shm_open(SHM_NAME,O_RDWR,0666);
    if (fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }

    // 将共享内存映射到进程地址空间
    int *shared_counter = mmap(NULL, SHARED_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shared_counter == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    // 打开已存在的信号量
    sem_t *semaphore = sem_open(SEM_NAME, 0);
    if (semaphore == SEM_FAILED) {
        perror("sem_open");
        exit(EXIT_FAILURE);
    }

    // 修改共享变量
    for (int i = 0; i < 5; i++) {
        sem_wait(semaphore);  // 等待信号量
        (*shared_counter)++;
        printf("子进程修改后,计数器值为: %d\n", *shared_counter);
        sem_post(semaphore);  // 释放信号量
        sleep(1);
    }

    // 清理资源
    sem_close(semaphore);
    munmap(shared_counter, SHARED_SIZE);
    close(fd);

    printf("子进程完成\n");

    return 0;
}

11. 管道(Pipe)和命名管道(FIFO)的主要区别是什么?

答案:​

特性​

管道(匿名管道)​

命名管道(FIFO)​

生命周期​

随父进程销毁​

随文件系统存在(需手动删除)​

通信范围​

仅限父子 / 兄弟进程(同祖先)​

任意进程(通过路径名访问)​

文件系统实体​

无(内核中的缓冲区)​

有(/dev/shm/ 下的特殊文件)​

打开方式​

自动创建(pipe () 函数)​

需 open () 打开(O_RDONLY/O_WRONLY)​

同步机制​

依赖内核缓冲区大小(默认 4KB)​

支持非阻塞打开(O_NONBLOCK)​

六、线程与进程对比

 12. 线程为何被称为 "轻量级进程"?其与进程的资源共享关系如何?

答案:轻量级原因:​

  • 上下文切换开销小(仅需保存线程栈、寄存器,无需切换地址空间)​
  • 共享进程资源(如堆、全局变量),创建 / 销毁成本低(约为进程的 1/10~1/100)​

共享与独立资源:​

  • 共享:进程地址空间(代码段、数据段、堆)、打开的文件描述符、信号处理句柄​
  • 独立:线程栈(含局部变量)、程序计数器(PC)、寄存器上下文、线程本地存储(TLS)

        同一进程中的不同线程共享进程的堆空间,不共享各自的栈空间。

资源是否共享
堆空间共享
全局变量、静态变量共享
代码段、数据段共享
打开的文件描述符共享
栈空间不共享(每个线程独立)
线程本地存储(TLS)不共享(线程专属)
寄存器值(如程序计数器、栈指针)不共享(线程执行上下文独立)

13. 什么是线程安全?如何实现线程安全的函数?

线程安全(Thread Safety) 是指一个函数、变量或资源在多线程并发访问时,仍能保证执行结果的正确性和可预测性,不会因线程调度顺序的不同而导致数据竞争(Data Race)或未定义行为。

1. 无状态(无共享数据)

函数不依赖任何全局变量、静态变量或堆内存(即 “无状态”),仅使用局部变量(栈内存)。此时每个线程的变量是独立的,自然线程安全。

2. 互斥锁(Mutex)

通过互斥锁(如 pthread_mutex_t)保护共享资源,确保同一时间只有一个线程能访问该资源。

3. 原子操作(Atomic Operations)

使用原子指令(CPU 支持的不可分割操作)替代锁,适合对简单变量(如计数器)的增量 / 减量操作。

4. 线程本地存储(Thread-Local Storage, TLS)

将共享变量改为每个线程独立的副本(线程本地存储),避免多线程竞争。

5. 不可变数据(Immutable Data)

数据一旦初始化就不再修改,多线程只能只读访问,无需同步。

6. 无锁数据结构(Lock-Free)

通过 CAS(Compare-And-Swap) 等原子操作实现线程安全的并发数据结构(如队列、哈希表),避免锁的开销。

七、系统调用与实现

 14. fork () 系统调用执行后,父子进程的虚拟地址空间如何变化?

答案:​

  • 写时复制(COW, Copy-On-Write):​
  1. fork () 后父子进程共享相同的物理内存页,虚拟地址空间布局相同(代码段、数据段、堆、栈)​
  1. 任意进程修改内存数据时(如赋值全局变量),内核为修改页创建副本,父子进程各自拥有独立副本​
  • 差异点:​
  • 父子进程的 PID、PPID 不同​
  • 子进程的fork()返回值为 0,父进程返回子进程 PID​
  • 未决信号和资源使用计数(如文件描述符引用计数增加)

15. exec () 系列函数的作用是什么?与 fork () 的区别是什么?​

答案:exec () 作用:用新的程序替换当前进程的地址空间(覆盖代码段、数据段、堆、栈),通常与 fork () 配合实现子进程执行新程序(如fork() + execvp()实现system()函数)​

核心区别:​

函数​

地址空间变化​

进程状态​

典型场景​

fork()​

复制原进程​

新建子进程​

创建子任务(如后台日志)​

exec()​

替换为新程序​

原进程被替换​

启动新程序(如命令行执行ls)​

八、实战与调试​

16. 如何用 ps 命令查看进程状态?常用参数有哪些?​

答案:常用命令:​

  • ps aux:显示所有用户的进程(a = 所有终端进程,u = 用户格式,x = 无终端进程)​
  • ps -ef:标准格式输出(e = 所有进程,f = 完整格式,显示父进程 PID)​
  • ps -p <pid>:查看指定进程详情​

关键字段:​

  • STAT:进程状态(S = 睡眠,R = 运行,Z = 僵尸,D = 不可中断睡眠)​
  • PID/PPID:进程 / 父进程 ID​
  • % CPU/% MEM:CPU / 内存利用率​
  • VSZ/RSS:虚拟内存大小 / 常驻内存大小

17. 进程核心转储(Core Dump)的作用是什么?如何启用和分析?​

答案:作用:进程异常终止时生成 core 文件,保存内存镜像、寄存器状态等信息,用于调试定位崩溃原因(如空指针解引用、数组越界)

如何启用 Core Dump

1. 设置 Core 文件大小限制

默认情况下,系统可能限制 Core 文件大小为 0(不生成),需临时或永久调整:

临时调整(当前终端有效)

ulimit -c unlimited # 不限制Core文件大小 # 或指定具体大小(单位:块,通常1块=512字节)

ulimit -c 10240 # 限制为5MB(10240×512=5242880字节)

永久调整(修改配置文件)
编辑 /etc/security/limits.conf,添加:

* hard core unlimited

* soft core unlimited

2. 设置 Core 文件保存路径和命名规则

修改 /etc/sysctl.conf,添加 / 修改以下行:

kernel.core_pattern = /var/crash/core.%e.%p.%t

 # 保存到/var/crash目录,格式为core.程序名.PID.时间戳

3.  sysctl -p # 立即生效

4. 然后使用-g编译并运行有问题的代码,此时会生成core文件

5.最后gdb ./test(可执行文件) core 即可恢复到崩溃之前,通过bt等查看...

九、高级话题

18. 什么是 CPU 亲和性(CPU Affinity)?如何实现进程绑定到特定 CPU 核心?

答案:CPU 亲和性:使进程固定在某个或某组 CPU 核心上运行,避免跨核心迁移带来的缓存失效(提高局部性,减少 TLB miss)​

实现方法:​

  • Linux 系统调用:sched_setaffinity(pid, sizeof(mask), &mask),其中 mask 位掩码表示允许运行的核心(如 0x1 表示核心 0,0x2 表示核心 1)​

        cpu_set_t mask;​

        CPU_ZERO(&mask);​

        CPU_SET(0, &mask); // 绑定到核心0​

        sched_setaffinity(0, sizeof(mask), &mask); // 绑定当前进程​

  • 任务管理器(Windows):右键进程→设置相关性,勾选目标 CPU 核心
#define _GNU_SOURCE  // 启用GNU扩展特性,确保sched.h中的所有定义可用
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>

int main(int argc,char *argv[]){
    if (argc != 2) {
        fprintf(stderr, "用法: %s <target_cpu>\n", argv[0]);
        return 1;
    }

    int target_cpu = atoi(argv[1]);
    cpu_set_t mask;

    //初始化CPU掩码并设置目标CPU
    CPU_ZERO(&mask);        // 清空掩码
    CPU_SET(target_cpu, &mask);  // 将目标CPU加入掩码

    //设置当前cpu的亲和性
    if(sched_setaffinity(0,sizeof(mask),&mask) == -1){
        perror("sched_setaffinity失败");
        return 1;
    }

    // 验证亲和性是否设置成功
    cpu_set_t current_mask;
    CPU_ZERO(&current_mask);
    if (sched_getaffinity(0, sizeof(current_mask), &current_mask) == -1) {
        perror("sched_getaffinity失败");
        return 1;
    }

    printf("进程PID %d 已绑定到CPU: ", getpid());
    for (int i = 0; i < CPU_SETSIZE; i++) {
        if (CPU_ISSET(i, &current_mask)) {
            printf("%d ", i);
        }
    }
    printf("\n");

    // 保持进程运行以便观察
    while (1) {
        sleep(1);
    }

    return 0;
}

19. 简述容器(如 Docker)与传统进程的隔离机制有何不同?

答案:​

隔离维度​

传统进程​

Docker 容器​

地址空间​

独立页表(MMU 隔离)​

共享宿主机内核,Namespace 隔离​

资源限制​

通过 ulimit 软限制​

cgroups 精确控制(CPU、内存、IO)​

文件系统​

共享宿主机文件系统​

镜像分层文件系统(UnionFS)​

网络​

共享宿主机网络栈​

虚拟网络(veth 设备、网桥)​

进程树​

属于宿主机进程树​

容器内 PID namespace 独立​

核心技术:​

  • Namespace:隔离 PID、UTS、IPC、网络等资源​
  • cgroups:限制资源使用量(如 CPU 配额、内存上限)

十、综合场景题​

20. 设计一个多进程下载工具,需考虑哪些关键问题?如何实现进程间协作?​

答案:关键问题:​

        文件分块:将大文件分割为 N 个块(如每个块 1MB),每个进程负责下载一个块​

        断点续传:记录每个块的下载进度(偏移量),支持失败重试​

        资源同步:避免多个进程同时写入文件同一位置(需加文件锁)​

        负载均衡:分配块时考虑网络延迟,动态调整进程任务(如某进程下载慢则重新分配块)​

协作方案:​

  • 主从架构:主进程创建子进程并分配文件块下载任务,通过管道接收子进程发送的进度信息,根据各子进程的完成情况进行动态调度。当所有子进程完成下载后,主进程按顺序将各块数据合并成完整文件,实现高效稳定的多进程文件下载功能。 ​主        
    • 进程负责分块、调度、合并文件​
    • 子进程通过管道 / 共享内存汇报下载进度(如当前块偏移、已下载字节数)​
    • 使用文件锁(fcntl()的 F_SETLK)保护文件写入,确保多个子进程按偏移量顺序写入

一、文件锁核心概念(fcntl.F_SETLK)

fcntl.F_SETLK 是 Linux 系统中通过 fcntl 函数实现的非阻塞文件锁

 
  • 非阻塞:尝试加锁时,若目标区域已被其他进程锁定,立即返回错误(errno=EAGAIN),不会阻塞当前进程
  • 写锁(F_WRLCK):用于写入场景,确保同一时间只有一个进程能修改文件的指定区域
  • 锁范围:通过 struct flock 结构体指定锁定的起始位置(l_start)和长度(l_len),支持对文件的部分区域加锁

二、多进程写入场景需求

假设我们要实现一个多进程分块写入大文件的功能:

 
  • 父进程将文件分为 3 个块(偏移 0-99、100-199、200-299)
  • 3 个子进程分别写入各自负责的块
  • 要求:每个子进程写入自己的块时,其他进程不能修改同一块区域(但可以并行写入不同块)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/wait.h> 
#include <errno.h>

// 子进程写入函数:offset=起始偏移,length=块长度,data=写入数据
void write_chunk(int offset, int length, char *data) {
    int fd = open("target.bin", O_RDWR);  // 以读写模式打开文件
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }

    struct flock lock;
    memset(&lock, 0, sizeof(lock));
    lock.l_type = F_WRLCK;               // 设置写锁
    lock.l_start = offset;                // 锁定区域起始偏移
    lock.l_len = length;                  // 锁定区域长度(0表示到文件末尾)
    lock.l_whence = SEEK_SET;             // 偏移相对于文件开头

    // 非阻塞加锁(F_SETLK):若区域已被锁定,立即返回错误
    if (fcntl(fd, F_SETLK, &lock) == -1) {
        if (errno == EAGAIN) {
            fprintf(stderr, "进程 %d 加锁失败:目标区域(%d-%d)已被占用\n", getpid(), offset, offset + length - 1);
        } else {
            perror("fcntl(F_SETLK) failed");
        }
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 加锁成功,定位到偏移并写入数据
    if (lseek(fd, offset, SEEK_SET) == -1) {
        perror("lseek failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    if (write(fd, data, length) != length) {
        perror("write failed");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("进程 %d 写入成功:偏移 %d-%d(数据:%c)\n", getpid(), offset, offset + length - 1, data[0]);

    // 释放锁(显式释放,虽然close会自动释放,但显式操作更安全)
    lock.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &lock) == -1) {
        perror("fcntl(F_UNLCK) failed");
    }
    close(fd);
    exit(EXIT_SUCCESS);
}

int main() {
    // 1. 初始化文件:创建300字节的空文件
    int fd = open("target.bin", O_CREAT | O_TRUNC | O_RDWR, 0666);
    if (fd == -1) {
        perror("open(target.bin) failed");
        return EXIT_FAILURE;
    }
    if (ftruncate(fd, 300) == -1) {  // 设置文件大小为300字节
        perror("ftruncate failed");
        close(fd);
        return EXIT_FAILURE;
    }
    close(fd);

    // 2. 定义3个子进程的写入任务(偏移0-99、100-199、200-299)
    struct {
        int offset;
        int length;
        char data[101];  // 存储100字节数据(+1用于空终止符,实际只用100字节)
    } chunks[] = {
        {0, 100, "A"},    // 填充'A'
        {100, 100, "B"},  // 填充'B'
        {200, 100, "C"}   // 填充'C'
    };

    // 3. 为每个块创建子进程
    pid_t pid;
    for (int i = 0; i < sizeof(chunks)/sizeof(chunks[0]); i++) {
        pid = fork();
        if (pid == -1) {
            perror("fork failed");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            // 子进程:填充数据(将第一个字符重复length次)
            char *data = malloc(chunks[i].length);
            memset(data, chunks[i].data[0], chunks[i].length);
            write_chunk(chunks[i].offset, chunks[i].length, data);
            free(data);
            exit(EXIT_SUCCESS);
        }
    }

    // 4. 父进程等待所有子进程结束(回收资源,避免僵尸进程)
    int status;
    while ((pid = wait(&status)) != -1) {
        if (WIFEXITED(status)) {
            printf("子进程 %d 正常退出(状态码:%d)\n", pid, WEXITSTATUS(status));
        } else {
            printf("子进程 %d 异常退出\n", pid);
        }
    }

    // 5. 验证文件内容(可选)
    fd = open("target.bin", O_RDONLY);
    if (fd != -1) {
        char buf[301] = {0};  // 读取300字节,+1用于空终止符
        if (read(fd, buf, 300) == 300) {
            printf("\n文件内容前300字节:\n");
            for (int i = 0; i < 300; i++) {
                printf("%c", buf[i]);
                if ((i + 1) % 100 == 0) printf(" (块%d结束)\n", (i + 1)/100);
            }
        } else {
            perror("read for verification failed");
        }
        close(fd);
    }

    return EXIT_SUCCESS;
}

0voice · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376405.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux复习笔记(五) 网络服务配置(dhcp)

二、网络服务配置 2.5 dhcp服务配置&#xff08;不涉及实际操作&#xff09; 要求&#xff1a;知道原理和常见的参数配置就行 2.5.1 概述DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09; DHCP&#xff08;Dynamic Host Conf…

windows版redis的使用

redis下载 Releases microsoftarchive/redishttps://github.com/microsoftarchive/redis/releases redis的启动和停止 进入路径的cmd 启动&#xff1a;redis-server.exe redis.windows.conf 停止&#xff1a;ctrlc 连接redis 指定要连接的IP和端口号 -h IP地址 -p 端口…

Java版OA管理系统源码 手机版OA系统源码

Java版OA管理系统源码 手机版OA系统源码 一&#xff1a;OA系统的主要优势 1. 提升效率 减少纸质流程和重复性工作&#xff0c;自动化处理常规事务&#xff0c;缩短响应时间。 2. 降低成本 节省纸张、打印、通讯及人力成本&#xff0c;优化资源分配。 3. 规范管理 固化企…

NineData 社区版 V4.1.0 正式发布,新增 4 条迁移链路,本地化数据管理能力再升级

NineData 社区版 V4.1.0 正式更新发布。本次通过新增 4 条迁移链路扩展、国产数据库深度适配、敏感数据保护增强‌等升级&#xff0c;进一步巩固了其作为高效、安全、易用的数据管理工具的定位。无论是开发测试、数据迁移&#xff0c;还是多环境的数据管理&#xff0c;NineData…

进阶2_1:QT5多线程与定时器共生死

1、在widget.ui中使用 LCD Number控件 注意&#xff1a;若 LCD 控件不是多线程&#xff0c;LCD控件则会瞬间自增到最大的数值&#xff0c;如上图&#xff0c;说明两者都是多线程处理 2、实现方式 1、创建 LCD 控件并修改为 LCD1 2、创建任务类 mytask. h&#xff0c;对任务类…

在虚拟机Ubuntu18.04中安装NS2教程及应用

NS2简介 一、主要组成部分&#xff1a; 1.NS2&#xff1a;模拟器本身&#xff0c;负责执行TCL脚本进行模拟&#xff0c;并生成trace文件输出结果。 2.NAM&#xff1a;网络动画模拟器&#xff0c;用于将模拟结果可视化。 二、使用的语言&#xff1a; 1.C&#xff1a;NS2中最重要…

VBA —— 第6章子程序与函数

子程序&#xff1a;实现特定功能的程序代码块 子程序语法&#xff1a; [修饰符] Sub 子程序名称([参数1&#xff0c;参数2&#xff0c;参数3]) 代码块 End Sub 子程序如何调用&#xff1a; 1 . 子程序名 [参数1&#xff0c;参数2&#xff0c;...] 2. Call 子程序名 [(参…

全新开发-iVX图形化编程VS完整IDE

本文针对传统软件开发的效率与可控性矛盾&#xff0c;系统阐释 iVX"图形化编程 全栈 IDE" 的复合架构如何突破行业瓶颈。通过 "可视化建模 - 标准代码生成 - 独立运行" 的技术闭环&#xff0c;iVX 实现开发效率提升 60% 与源码完全可控的双重目标。研究揭…

【Linux系列】跨平台安装与配置 Vim 文本编辑器

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

十天学会嵌入式技术之51单片机—day-10

第 20 章 18B20 温度检测 20.1 18B20 概述 20.1.1 简介 18B20 是一种常用的数字温度传感器&#xff0c;广泛应用于环境监测、工业控制、家居自动化 和设备温度监控等领域。 20.1.2 引脚功能 18B20 引脚功能如下图所示&#xff0c;需要特别强调的是&#xff0c;18B20 采用 1-…

【C++】17. 多态

上一章节中我们讲了C三大特性的继承&#xff0c;这一章节我们接着来讲另一个特性——多态 1. 多态的概念 多态(polymorphism)的概念&#xff1a;通俗来说&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态)&#xff0c;这里我们重点讲运行时多态…

家用或办公 Windows 电脑玩人工智能开源项目配备核显的必要性(含 NPU 及显卡类型补充)

一、GPU 与显卡的概念澄清 首先需要明确一个容易误解的概念&#xff1a;GPU 不等同于显卡。 显卡和GPU是两个不同的概念。 【概念区分】 在讨论图形计算领域时&#xff0c;需首先澄清一个常见误区&#xff1a;GPU&#xff08;图形处理单元&#xff09;与显卡&#xff08;视…

实现一个简单的 TCP 客户端/服务器

注意&#xff1a; TCP 三次握手建立连接建立连接后&#xff0c;TCP 提供全双工的通信服务&#xff0c;也就是在同一个连接中&#xff0c;通信双方 可以在同一时刻同时写数据&#xff0c;相对的概念叫做半双工&#xff0c;同一个连接的同一时刻&#xff0c;只能由一方来写数据T…

对抗帕金森:在疾病阴影下,如何重掌生活主动权?

帕金森病&#xff0c;一种影响全球超 1000 万人的神经退行性疾病&#xff0c;正无声地改变着患者的生活轨迹。随着大脑中多巴胺分泌减少&#xff0c;患者逐渐出现肢体震颤、肌肉僵硬、步态迟缓等症状&#xff0c;甚至连扣纽扣、端水杯这类日常动作都变得艰难。更棘手的是&#…

鸿蒙 UIAbility组件与UI的数据同步和窗口关闭

使用 EventHub 进行数据通信 Stage模型概念图 根据 Stage 模型概念图 UIAbility 先于 ArkUI Page 创建 所以&#xff0c;事件要先 .on 订阅 再 emit 发布 假如现在有页面 Page1 和他的 UIAbility // src/main/ets/page1ability/Page1Ability.ets onCreate(want: Want, laun…

Vue3学习(组合式API——计算属性computed详解)

目录 一、计算属性computed。 Vue官方提供的案例。(普通写法与计算属性写法) 使用计算属性computed重构——>简化描述响应式状态的复杂逻辑。 &#xff08;1&#xff09;计算属性computed小案例。 <1>需求说明。&#xff08;筛选原数组——>得新数组&#xff09; &…

Android Studio 模拟器配置方案

Android Studio 模拟器配置方案 1.引言2.使用Android Studio中的模拟器3.使用国产模拟器1.引言 前面介绍【React Native基础环境配置】的时候需要配置模拟器,当时直接使用了USB调试方案,但是有些时候可能不太方便连接手机调试,比如没有iPhone调不了ios。接下来说明另外两种可…

k8s中ingress-nginx介绍

1. 介绍 Ingress是一种Kubernetes资源&#xff0c;用于将外部流量路由到Kubernetes集群内的服务。与NodePort相比&#xff0c;它提供了更高级别的路由功能和负载平衡&#xff0c;可以根据HTTP请求的路径、主机名、HTTP方法等来路由流量。可以说Ingress是为了弥补NodePort在流量…

字节DeerFlow开源框架:多智能体深度研究框架,实现端到端自动化研究流程

&#x1f98c; DeerFlow DeerFlow&#xff08;Deep Exploration and Efficient Research Flow&#xff09;是一个社区驱动的深度研究框架&#xff0c;它建立在开源社区的杰出工作基础之上。目标是将语言模型与专业工具&#xff08;如网络搜索、爬虫和Python代码执行&#xff0…

算法第十八天|530. 二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先

530. 二叉搜索树的最小绝对差 题目 思路与解法 第一想法&#xff1a; 一个二叉搜索树的最小绝对差&#xff0c;从根结点看&#xff0c;它的结点与它的最小差值一定出现在 左子树的最右结点&#xff08;左子树最大值&#xff09;和右子树的最左结点&#xff08;右子树的最小值…