build_embed_linux_system

Linux面试题整理(三):Linux应用面试问题整理

linux-app

interview-001

堆和栈的分别是什么,它们的操作效率上谁更快。

堆是一种动态分配的内存区域,用于存储程序运行时动态分配的数据。堆的内存分配和释放由程序员手动控制,通常使用malloc、calloc、realloc等函数进行内存分配,使用free函数进行内存释放,在C++中是new/delete, new[]/delet[]。堆的内存空间是不连续的,因此分配和释放内存的操作相对较慢。

栈是一种自动分配和释放的内存区域,用于存储函数调用时的局部变量、参数、返回地址等信息。栈的内存分配和释放由编译器自动管理,遵循先进后出(FILO)的原则。栈的内存空间是连续的,因此分配和释放内存的操作相对较快。

interview-002

常见的内存分配方式有哪些。

  1. 静态内存分配,在程序编译时就确定了所需内存空间的大小,并在程序运行期间固定不变。适用于程序中所需内存大小在编译时可以确定的情况,如全局变量、静态变量等
  2. 栈内存分配,由编译器自动管理,用于存储函数调用时的局部变量、参数、返回地址等信息。适用于函数内部的临时变量、函数调用时的参数传递等场景。
  3. 堆内存分配,由程序员手动控制,用于存储程序运行时动态分配的数据。适用于程序中需要动态分配内存的情况,如动态数组、链表、树等数据结构的创建和管理

interview-003

linux应用中的同步机制有哪些。

  1. 互斥锁,互斥锁是一种用于保护共享资源的同步机制,确保同一时间只有一个线程可以访问该资源。
  2. 条件变量,条件变量是一种用于线程间同步的机制,允许线程在满足特定条件时被唤醒。线程可以等待条件变量满足某个条件,当条件满足时,其他线程可以通过信号通知等待的线程继续执行。
  3. 信号量,信号量是一种用于控制对共享资源的访问的同步机制,它可以用来保护临界区,确保同一时间只有一个线程可以访问共享资源。
  4. 读写锁,读写锁是一种用于保护共享资源的同步机制,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
  5. 屏障(Barrier),屏障是一种用于线程间同步的机制,它允许线程等待其他线程到达某个点,然后再继续执行。
  6. 原子操作(atomic operations),原子操作是一种不可中断的操作,它可以确保对共享变量的操作是原子性的,即要么全部执行成功,要么全部不执行。

interview-004

linux应用进程间通讯机制(IPC机制)。

  1. 管道,包含匿名管道(pipe)和命名管道(fifo),其中匿名管道用于父子进程间的访问通讯,命名管道则既可以用于父子进程,也可以支持任意进程间的访问通讯
  2. 信号,信号是比较复杂的通信方式,用于通知接收进程有某种事件发生,通过signal处理相应的信号
  3. 消息队列,消息队列是消息的链接表,包括POSIX消息队列(mq_open/…/mq_close)和System V消息队列(msgget/…/msgrcv)。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。
  4. 共享内存,共享内存使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。它通常与其他通信机制(如信号量)结合使用,来达到进程间的同步及互斥。
  5. 信号量,信号量(semperature)是一种在进程间或同一进程的不同线程间提供同步的机制。它主要用于控制对共享资源的访问,以确保资源的一致性和避免竞争条件
  6. 套接字,套接字是更为一般的进程间通信机制,可用于不同机器之间的进程间通信
  7. 文件,文件在Linux系统中是一种基本的持久化存储机制,也可用于进程间通信。进程之间可以通过读写同一文件来交换数据

interview-005

简述下进程,线程和协程的定义。

  1. 进程是资源调度的基本单位,运行一个可执行程序会创建一个或者多个进程,进程是拥有资源的基本单位,每个进程都有独立的内存空间、代码以及数据栈等资源。一个进程内多个线程可以并发,多个进程也可以并发
  2. 线程是进程中实际执行任务的最小单位,同一进程中的多个线程共享进程的资源,例如内存、文件描述符等。线程是CPU调度的基本单位
  3. 协程是一种用户级的轻量级线程,具有自己的寄存器上下文和调度器,在单个线程中实现多个协程间的切换。它不是由操作系统内核所管理,而是完全由程序所控制(在用户态执行)

interview-006

简述下并发和并行。

  1. 并发,在同一个CPU核上,基于调度算法,实现进程基于时间交替执行,多个线程看起来同时执行。并发是一种逻辑上的概念,它允许程序中的多个任务在看似同时的情况下进行。
  2. 并行,并行是指两个或多个任务在同一个时间段内同时执行,即这些任务在物理上是同时进行的。并行是一种物理上的概念,它要求程序中的多个任务在真正的同时执行。

interview-007

说明下堵塞和非堵塞,同步和异步的理解。

阻塞(Blocking)和非阻塞(Non-blocking)说明。

  1. 堵塞: 进程调用阻塞的I/O操作时,该进程会被挂起(即进入睡眠状态),直到I/O操作完成。在I/O操作完成之前,进程无法执行其他任务
  2. 非堵塞: 进程调用非阻塞的I/O操作时,如果操作不能立即完成,函数会立即返回错误码(如EAGAIN或EWOULDBLOCK),而不会挂起进程。进程可以继续执行其他任务,然后在稍后的时间再次尝试I/O操作

阻塞与非阻塞:描述的是I/O操作是否会导致进程挂起。

同步(Synchronous)和异步(Asynchronous)说明。

  1. 同步: 在同步I/O操作中,进程发起I/O操作后会一直等待,直到I/O操作完成。同步I/O可以是阻塞的,也可以是非阻塞的。如果是阻塞的,进程会在I/O操作完成之前一直挂起;如果是非阻塞的,进程会不断轮询I/O操作的状态,直到操作完成。
  2. 异步: 在异步I/O操作中,进程发起I/O操作后不会等待操作完成,而是继续执行其他任务。当I/O操作完成时,操作系统会通知进程(通常通过信号或回调函数),异步一定是非堵塞的。

同步与异步:描述的是I/O操作完成后,进程是否需要等待通知。

interview-008

Linux系统的任务调度的算法有哪些。

  1. 先来先服务 – 非抢占式,按照请求的顺序调度
  2. 短作业优先 – 非抢占式,按照估计运行时间最短来调度
  3. 最短运行时间优先 – 抢占式,按剩余运行时间进行调度
  4. 时间片轮转 – 所有进程按照FCFS的原则排列,每次调度,把CPU时间分配给队首的进程
  5. 优先级调度 – 进程分配优先级,按照优先级进行调度
  6. 多级反馈队列 – 设置多个队列,采用不同的时间片,从而满足多个时间片工作的进程需求(时间片轮转+优先级调整)。

interview-009

C++程序生成可执行文件的步骤有哪些。

  1. 预处理,处理宏定义,展开头文件,删除注释
  2. 编译,将预处理后的文件转换成汇编代码
  3. 汇编,将汇编代码转换成二进制机器码
  4. 链接,将多个目标文件链接成一个可执行文件

interview-010

什么是静态链接和动态链接。如何生成静态链接库和动态链接库。

静态链接是在编译时将所有目标文件和库文件合并成一个可执行文件的过程。在静态链接中,链接器会将程序所需的所有库函数和数据复制到最终的可执行文件中。这意味着可执行文件在运行时不需要依赖外部的库文件,因为所有必要的代码和数据都已经包含在其中。使用ar命令,可以生成静态库。

# 通过ar命令生成静态库
gcc -c test.c -o test.o
ar rcs libtest.a test.o

# 使用静态库链接生成程序
gcc main.c libtest.a -o file

动态链接是在程序运行时才将库文件加载到内存中的过程。在动态链接中,链接器不会将库文件的代码复制到可执行文件中,而是在可执行文件中记录对库函数的引用。当程序运行时,操作系统会加载所需的库文件,并将其映射到内存中。

# 通过ar命令生成静态库
gcc -c -fPIC test.c -o test.o
gcc -shared -o libtest.so test.o

# 使用动态库链接生成程序
# gcc中-Wl,-rpath会指定程序链接时查找的目录,否则动态库放在目录下也会查找不到
gcc main.c -Wl,-rpath=. -L. -ltest -o file

interview-011

虚拟内存的目的包含哪些方面。

  1. 扩展物理内存。虚拟内存允许操作系统将磁盘空间作为内存的扩展,使得程序可以使用比实际物理内存更大的地址空间
  2. 内存管理的灵活性。虚拟内存提供了一种灵活的内存管理方式。操作系统可以根据需要将不同的程序和数据加载到物理内存中,而不必担心物理内存的大小限制
  3. 内存保护。虚拟内存通过将内存划分为不同的区域,并为每个区域分配不同的权限,实现了内存保护
  4. 内存共享。虚拟内存允许多个程序共享相同的物理内存区域。这对于实现进程间通信和资源共享非常有用
  5. 内存映射。虚拟内存可以将文件映射到内存中,使得文件可以像内存一样进行读写操作,提高文件操作的效率
  6. 内存压缩。一些操作系统还支持虚拟内存的压缩技术,通过压缩内存中的数据来减少内存的占用,从而可以在有限的物理内存中运行更多的程序。

interview-012

linux进程中止的方法.

  1. 从main返回
  2. 调用exit函数
  3. 调用_exit函数
  4. 通过信号中止,常见的信号包含SIGTERM和SIGKILL(kill命令发送)
  5. 通过异常中止
  6. 通过系统调用abort中止,产生SIGABRT信号
  7. 最后一个线程调用pthread_exit函数,结束进程

interview-013

内存碎片产生的原因,如何避免内存碎片。

内存碎片是指内存中存在的小块未使用内存,这些小块内存由于太小而无法满足程序的内存分配请求,导致内存利用率降低。内存碎片主要分为两种类型:外部碎片和内部碎片。

避免内存碎片的方法。

  1. 选择合适的内存分配和释放策略
  2. 使用内存管理工具
  3. 应用层使用内存池,合理设计数据结构

interview-014

什么是守护进程,守护进程如何创建的。

守护进程是一种在后台运行的特殊进程,它通常在系统启动时自动启动,并在系统关闭时自动关闭。守护进程不与任何终端关联,因此用户无法直接与它们进行交互。守护进程通常用于执行系统级任务,如网络服务、日志记录、定时任务等。通过创建子进程、脱离终端、改变工作目录、重设文件权限掩码、关闭文件描述符等步骤,可以创建一个守护进程。

守护进程的创建方式如下所示。

void daemonize(void) {
    pid_t pid;

    // 创建子进程
    pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    } else if (pid > 0) {
        // 父进程退出
        exit(0);
    }

    // 子进程成为新的会话组长
    if (setsid() < 0) {
        perror("setsid");
        exit(1);
    }

    // 改变工作目录到根目录
    if (chdir("/") < 0) {
        perror("chdir");
        exit(1);
    }

    // 重设文件权限掩码
    umask(0);

    // 关闭所有不需要的文件描述符
    int fd;
    for (fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) {
        close(fd);
    }

    // 打开系统日志
    openlog("my_daemon", LOG_PID, LOG_DAEMON);
    syslog(LOG_INFO, "Daemon started");
}

//Linux系统中也封装了此函数。
#include <unistd.h>

// @nochdir:如果该参数为0,则守护进程将改变其工作目录到根目录(/);如果该参数为非0,则守护进程将保持其当前工作目录。
// @noclose:如果该参数为0,则守护进程将关闭所有打开的文件描述符(包括标准输入、标准输出和标准错误);如果该参数为非0,则守护进程将保持所有文件描述符打开
int daemon(int nochdir, int noclose);

注意:守护进程是后台运行的特殊进程,但后台进程并不一定是守护进程,所以”程序 &”只是后台进程,而并非守护进程。

interview-015

什么是僵尸进程和孤儿进程,讲述下产生原因,以及解决办法。

僵尸进程是指已经完成执行(通过调用exit()系统调用)但在进程表中仍然有一个条目的进程。

当一个子进程先于其父进程结束时,子进程会变成僵尸进程。这是因为父进程需要通过wait()或waitpid()系统调用来获取子进程的退出状态。如果父进程没有调用这些函数,子进程就会一直保持僵尸状态。

解决方法: 父进程调用wait()或waitpid():父进程通过调用这些函数来获取子进程的退出状态,从而使子进程彻底结束

孤儿进程是指父进程先于子进程结束,从而使子进程成为一个没有父进程的进程。

当父进程结束时,子进程会被init进程(PID为1的进程)收养,init进程会成为子进程的新父进程

孤儿进程会被init进程管理和清理,因此不需要特别的处理。

interview-016

进程的状态有哪些,切换的方式。

进程的状态包含创建,就绪,运行,堵塞和中止。

新建 → 就绪 ↔ 运行 → 终止
          ↑    ↓
          阻塞状态

此外还有两种特殊状态,停止状态和僵尸状态。

  1. 停止状态为接收到SIGSTOP信号进入停止,SIGCONT信号后恢复运行。
  2. 僵尸状态为进程已经完成执行,但是父进程没有调用wait()或waitpid()获取子进程的状态信息,子进程进入僵尸状态。

interview-017

Linux系统中常用的命令有哪些,简述功能。

Linux系统中常用的命令有cd、cp、cat、chmod、chown、grep、man、mkdir、ps、top、touch、vim等,功能如下所示。

shell命令 功能
cd 切换目录
cp 复制文件
cat 显示文件内容
chmod 修改文件权限
chown 修改文件所有者
grep 在文件中查找指定的字符串
man 查看命令手册
mkdir 创建目录
ps 查看进程状态
top 查看进程状态
touch 创建文件
vim 文本编辑器

interview-018

Linux系统中常用的进程创建方式有哪些。

  1. fork(),基础的系统调用,用于创建一个新的进程,新进程是原进程的子进程。子进程会复制父进程的大部分资源,包括内存空间、文件描述符等
  2. vfork(),创建一个新的进程,新进程是原进程的子进程。子进程会复制父进程的大部分资源,包括内存空间、文件描述符等
  3. exec(),用于在一个进程中执行一个新的程序
  4. clone() 是一个更灵活的创建进程的系统调用,它允许用户指定新进程与父进程共享哪些资源,如内存空间、文件描述符等

interview-019

dd命令和FIO测试读写,有什么区别。

dd命令和FIO都可以用于测试磁盘的读写速率。

  1. dd命令可以从一个文件或设备读取数据,然后将其写入到另一个文件或设备中;dd 在进行写操作时,数据可能会被暂时存储在文件系统缓存中,而不是立即写入磁盘,从而使得测试结果看起来比实际的磁盘读写速率要高。适合用于快速测试磁盘的基本读写性能,或者在没有其他工具可用的情况下进行简单的性能评估。
  2. FIO可以通过设置参数来绕过文件系统缓存,直接对磁盘进行读写操作,从而得到更准确的测试结果。此外,FIO 还可以提供详细的统计信息,如平均读写速率、IOPS(每秒输入输出操作次数)、延迟等,帮助用户更全面地了解磁盘的性能。适用于需要进行详细和复杂的 I/O 性能测试的场景,如数据库服务器、存储系统等。