【秋招】嵌入式面试八股文 - 操作系统 篇

本文为 第六章操作系统 部分,具体整篇目录可以看前言!

【秋招】嵌入式面试八股文分享

第一部分(纯八股)

1 什么是进程?什么是线程?

进程(Process)

  • 操作系统资源分配的基本单位
  • 拥有独立的内存空间和系统资源
  • 包含代码、数据、堆栈等

线程(Thread)

  • CPU调度的基本单位
  • 共享所属进程的内存空间和资源
  • 只拥有必要的运行资源(如程序计数器、寄存器和栈)

主要区别

资源占用

占用独立内存空间

共享进程内存空间

通信开销

较大(需IPC机制)

较小(直接访问共享数据)

创建开销

较大

较小

切换开销

较大(需保存/恢复更多上下文)

较小

安全性

较高(相互独立)

较低(共享内存可能导致冲突)

并发性

多进程并发

多线程并发

2 内核线程和用户线程的区别?

  • 内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
  • 用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
  • 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
  • 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
  • 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

3 进程和线程有什么区别?

3.1 根本区别

  • 进程是资源分配的基本单位,线程是程序执行的最小单位

3.2 资源开销

  • 进程有自己独立地址空间(代码空间和数据空间),每启动一个进程,系统会为它分配地址空间。
  • 线程没有自己独立的地址空间,线程共享进程中的数据,使用相同的地址空间。每个线程都有自己的堆栈。
  • 线程切换的资源开销要比进程小。涉及频繁切换,就选择线程。
  • 线程开销小,但是不利于进行资源包含;进程开销大,但是有利于资源保护。

3.3 关于通信

  • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。(ftok函数)
  • 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也跟着死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

3.4 执行过程

  • 每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

4 何时使用多进程,何时使用多线程?

  • 对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
  • 要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。

5 进程的几种状态?

​关于IO:

  • 用户程序进行IO的读写,会用到read&write两大系统调用。read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区

同步IO:

  • 是指用户空间线程是主动发起IO请求的一方,内核空间是被动接受方。

  • 是指内核kernel是主动发起IO请求的一方,用户线程是被动接受方。
  • R (Running/Runnable):正在运行或就绪状态
  • S (Sleeping):可中断睡眠,等待某事件完成
  • D (Disk Sleep):不可中断睡眠,通常是等待I/O
  • Z (Zombie):僵尸状态,进程已终止但父进程未回收
  • T (Stopped):进程被暂停,如收到SIGSTOP信号
  • X (Dead):进程即将被销毁

状态转换:

  • R → S:进程等待资源或事件
  • S → R:等待的事件发生,进程被唤醒
  • R → D:进程请求不可中断的操作,如磁盘I/O
  • D → R:不可中断操作完成
  • R → Z:进程终止,但父进程未调用wait()
  • R/S → T:进程收到SIGSTOP信号
  • T → R:进程收到SIGCONT信号

6 进程创建的方式?

6.1 使用fork创建

fork()是Unix/Linux中创建进程的传统方法:

#include <unistd.h>
pid_t fork(void);


特点

  • 创建调用进程的副本(子进程)
  • 子进程获得父进程数据空间、堆、栈的副本
  • 父子进程共享代码段(只读)
  • 采用写时复制(Copy-On-Write)技术优化内存使用
  • 返回值:父进程中返回子进程PID,子进程中返回0,失败返回-1

示例

#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    
    if (pid < 0) {
        // 创建失败
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程,PID: %d,父进程PID: %d\n", getpid(), getppid());
    } else {
        // 父进程
        printf("父进程,PID: %d,子进程PID: %d\n", getpid(), pid);
    }
    
    return 0;
}

6.2 vfork系统调用

vfork()fork()的一种特殊形式:

#include <unistd.h>
pid_t vfork(void);


特点

  • 创建进程的目的是执行exec系列函数
  • 子进程与父进程共享地址空间(不使用写时复制)
  • 父进程会被挂起,直到子进程调用exec或exit
  • 子进程必须小心不修改共享的变量
  • 现代系统中,fork的性能已经很好,vfork使用较少

示例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    pid_t pid = vfork();
    
    if (pid < 0) {
        // 创建失败
        perror("vfork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程,PID: %d\n", getpid());
        // 子进程必须调用exec或exit
        _exit(0);  // 注意使用_exit而非exit
    } else {
        // 父进程
        printf("父进程,PID: %d,子进程PID: %d\n", getpid(), pid);
    }
    
    return 0;
}

6.3 clone系统调用

clone()是Linux特有的系统调用,提供更细粒度的控制:

#define _GNU_SOURCE
#include <sched.h>
int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...);


特点

  • 允许选择父子进程间共享的资源(如文件描述符、信号处理等)
  • 是实现线程的基础(pthread库在底层使用clone)
  • 通过flags参数控制共享程度
  • 常用flags: CLONE_FILES:共享文件描述符表CLONE_FS:共享文件系统信息CLONE_VM:共享内存空间CLONE_SIGHAND:共享信号处理函数

示例

#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)  // 1MB栈空间

// 子进程执行的函数
int child_func(void *arg) {
    printf("子进程,PID: %d\n", getpid());
    return 0;
}

int main() {
    // 分配栈空间,栈向下增长
    void *stack = malloc(STACK_SIZE);
    if (!stack) {
        perror("malloc failed");
        return 1;
    }
    
    // 创建子进程
    pid_t pid = clone(child_func, (char*)stack + STACK_SIZE, 
                      CLONE_VM | SIGCHLD, NULL);
    
    if (pid < 0) {
        perror("clone failed");
        free(stack);
        return 1;
    }
    
    printf("父进程,PID: %d,子进程PID: %d\n", getpid(), pid);
    
    // 等待子进程结束
    waitpid(pid, NULL, 0);
    free(stack);
    
    return 0;
}

6.2 fork和vfork的区别?

  • void exit( int status) 结束当前进程并将status返回;exit结束进程会刷新缓冲区
  • void _exit(int status)  这个不会进行缓冲区刷新

注意:

  • 内存共享: fork():子进程获得父进程内存的副本(写时复制)vfork():子进程与父进程共享内存空间
  • 执行顺序: fork():父子进程执行顺序不确定vfork():父进程挂起,直到子进程调用exec或exit
  • 使用场景: fork():通用进程创建vfork():创建进程后立即执行exec
  • 安全性: vfork()更危险,子进程可能破坏父进程的数据
  • 性能: 历史上vfork()更高效,现代系统中差异不大

6.3 为什么子进程中使用exit()可能导致问题,而应该使用_exit()?

  • exit()会执行清理操作,包括刷新标准IO缓冲区和调用atexit()注册的函数
  • 在vfork()创建的子进程中,由于共享地址空间,使用exit()可能会: 清空父进程的IO缓冲区执行父进程注册的atexit()函数关闭父进程需要的文件描述符
  • _exit()直接调用系统调用退出,不执行用户空间清理操作
  • 因此,vfork()后的子进程应使用_exit()或exec(),避免干扰父进程

6.4 调度策略和优先级

Linux支持多种调度策略:

  • SCHED_OTHER(CFS):普通进程的默认策略
  • SCHED_BATCH:批处理进程,不需要交互
  • SCHED_IDLE:优先级最低的进程
  • SCHED_FIFO:实时进程,先进先出,不会被抢占(除非被更高优先级进程)
  • SCHED_RR:实时进程,时间片轮转

优先级系统:

  • nice值:范围-20到19,值越小优先级越高,默认为0

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

双非本,211硕。本硕均为机械工程,自学嵌入式,在校招过程中拿到小米、格力、美的、比亚迪、海信、海康、大华、江波龙等offer。八股文本质是需要大家理解,因此里面的内容一定要详细、深刻!这个专栏是我个人的学习笔记总结,是对很多面试问题进行的知识点分析,专栏保证高质量,让大家可以高效率理解与吸收里面的知识点!掌握这里面的知识,面试绝对无障碍!

全部评论
有不完善的地方欢迎大家提意见
点赞 回复 分享
发布于 05-07 18:53 山东
大家有问题可以留言
点赞 回复 分享
发布于 05-06 15:38 山东

相关推荐

评论
9
39
分享

创作者周榜

更多
牛客网
牛客企业服务