MST

星途 面试题库

面试题:C语言进程创建中的资源分配与继承

在Linux系统下,当使用C语言的fork函数创建新进程时,新进程会继承父进程的部分资源。请阐述哪些资源是被继承的,哪些资源是新分配的,并说明这种资源分配与继承机制在实际应用中的意义和可能带来的问题,举例说明如何解决这些问题。
43.5万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

继承的资源

  1. 进程地址空间:子进程获得与父进程相同的用户空间地址布局,包括代码段、数据段、堆和栈等。不过这些内存区域是写时复制(Copy - On - Write)的,即只有当父子进程中有一方尝试修改内存时,才会真正复制内存页。
  2. 文件描述符:子进程继承父进程打开的文件描述符,这些文件描述符指向相同的文件表项,意味着父子进程共享文件的偏移量等状态。
  3. 信号处理函数:子进程继承父进程设置的信号处理函数,对相同信号的处理方式与父进程相同。
  4. 当前工作目录:子进程继承父进程的当前工作目录。
  5. 用户ID和组ID:子进程继承父进程的实际用户ID、有效用户ID、实际组ID和有效组ID。

新分配的资源

  1. 进程ID(PID):每个进程都有唯一的PID,子进程会获得一个新的、唯一的PID。
  2. 父进程ID(PPID):子进程的PPID是父进程的PID,与父进程的PPID不同。
  3. 内存资源:虽然子进程继承了地址空间布局,但实际的物理内存是新分配的(采用写时复制机制,一开始物理内存共享,修改时才分配新的物理内存)。
  4. 文件描述符表:虽然文件描述符指向相同文件表项,但子进程有自己独立的文件描述符表。

实际应用中的意义

  1. 简化编程模型:对于一些需要进行并发操作且共享部分状态的场景,如网络服务器中,子进程可以继承父进程打开的监听套接字文件描述符,这样子进程可以直接处理客户端连接,无需重新初始化网络连接相关资源,简化了编程逻辑。
  2. 快速启动:通过继承父进程的一些资源,子进程无需重新构建整个运行环境,能快速启动并开始执行任务,提高了程序的响应速度。

可能带来的问题

  1. 资源竞争:父子进程共享文件描述符可能导致资源竞争问题。例如,父子进程同时对共享文件进行写操作,可能导致文件内容混乱。
  2. 信号处理冲突:继承相同的信号处理函数可能引发问题。如果父进程设置了一个信号处理函数用于清理资源,子进程继承后,在不恰当的时候收到信号,可能会执行不适合子进程的清理操作。

解决问题的示例

  1. 解决资源竞争问题:可以使用文件锁机制。例如,在父子进程写文件前,通过 fcntl 函数获取文件锁。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>

int main() {
    int fd = open("test.txt", O_CREAT | O_WRONLY, 0666);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        close(fd);
        return 1;
    } else if (pid == 0) { // 子进程
        struct flock lock;
        lock.l_type = F_WRLCK;
        lock.l_start = 0;
        lock.l_whence = SEEK_SET;
        lock.l_len = 0;
        if (fcntl(fd, F_SETLKW, &lock) == -1) {
            perror("fcntl");
            close(fd);
            return 1;
        }
        write(fd, "Child process writing\n", 20);
        lock.l_type = F_UNLCK;
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            perror("fcntl");
            close(fd);
            return 1;
        }
        close(fd);
    } else { // 父进程
        struct flock lock;
        lock.l_type = F_WRLCK;
        lock.l_start = 0;
        lock.l_whence = SEEK_SET;
        lock.l_len = 0;
        if (fcntl(fd, F_SETLKW, &lock) == -1) {
            perror("fcntl");
            close(fd);
            return 1;
        }
        write(fd, "Parent process writing\n", 20);
        lock.l_type = F_UNLCK;
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            perror("fcntl");
            close(fd);
            return 1;
        }
        close(fd);
    }
    return 0;
}
  1. 解决信号处理冲突问题:子进程可以在创建后重新设置适合自己的信号处理函数。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void parent_signal_handler(int signum) {
    printf("Parent received signal %d\n", signum);
}

void child_signal_handler(int signum) {
    printf("Child received signal %d\n", signum);
}

int main() {
    signal(SIGINT, parent_signal_handler);
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    } else if (pid == 0) { // 子进程
        signal(SIGINT, child_signal_handler);
        while (1) {
            sleep(1);
        }
    } else { // 父进程
        while (1) {
            sleep(1);
        }
    }
    return 0;
}