ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【Linux】进程控制(一):进程的创建和终止

【Linux】进程控制(一):进程的创建和终止

一、进程创建

1.1 fork函数初识

  • 在进程概念(一)中已经讲解大部分fork知识

在Linux中fork是一个非常重要的函数,它从已存在的进程中创建一个新的进程 。新的进程为子进程,而原来的进程为父进程。

代码语言:javascript

AI代码解释

#include <unistd.h>//包含的头文件 pid_t fork(void); 返回值:子进程中返回0,⽗进程返回⼦进程id,出错返回-1

在这里插入图片描述

总而言之: 进程调用fork,当控制转移到内核中的fork代码后,内核做: • 分配新的内存块和内核数据节后给子进程 • 将父进程部分数据结构内容拷贝到子进程中 • 添加子进程到系统进程列表当中 • fork返回,开始调度器调度

我们来看一段代码:

代码语言:javascript

AI代码解释

#include <stdlib.h> #include <stdio.h> #include <unistd.h> int main( void ) { pid_t pid; printf("Before: pid is %d\n", getpid()); if ((pid=fork()) == -1 ) { perror("fork()"); exit(1); } printf("After:pid is %d, fork return %d\n", getpid(), pid); sleep(1); return 0;

运行结果:

  1. 这⾥看到了三行输出,⼀行before,两行after。进程5431先打印before消息,然后它又打印after。
  2. 另⼀个after由5432打印的。注意到进程5432没有打印before,为什么呢?如下图所⽰

结论:所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。


1.2 fork函数返回值

我们先说结论:子进程返回0,父进程返回子进程的pid所以我们可以利用这个结论来让父进程和子进程执行不同的执行流(代码块)

观察如下代码:

代码语言:javascript

AI代码解释

#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { printf("i am parent,pid: %d, ppid %d\n\n", getpid(), getppid()); pid_t id = fork(); if(id == 0) { while(1) { printf("i am child,pid: %d, ppid %d\n", getpid(), getppid()); sleep(1); } } else if(id > 0) { while(1) { printf("i am parent,pid: %d, ppid %d\n", getpid(), getppid()); sleep(1); } } else { peeor("fork()"); exit(1); } return 0; }

运行结果如下:

在这里插入图片描述

1.3 写时拷贝

关于写实拷贝,我们有在进程概念(六)中简单提到过:【点击进入】

  • 我们通过下面这张图更加深入的了解写时拷贝

在这里插入图片描述

写时拷贝是操作系统为优化进程创建而设计的内存管理策略。当父进程创建子进程时,操作系统并不立即复制父进程的数据段,而是让父子进程共享同一物理内存页,仅将相关页表项标记为只读权限。

  1. 数据结构继承:子进程的内核数据结构完全以父进程为模板,包括:
    • 进程控制块(task_struct)
    • 内存描述符(mm_struct)
    • 页表结构
  2. 权限设置:
    • 代码段:保持只读权限(代码本身不应修改)
    • 数据段:原本可读写的页被强制设置为只读权限,为写时拷贝做准备
  3. 当任一进程尝试修改共享数据时:
    • 访问异常:CPU检测到对只读页的写操作,触发页保护异常
    • 异常转换:操作系统识别此为写时拷贝场景,将异常转换为缺页中断
    • 内存分配:内核分配新的物理内存页
    • 数据拷贝:将原页内容复制到新页
    • 权限更新:更新页表项,指向新物理页并恢复可读写权限

因为有写时拷贝技术的存在,所以父子进程得以彻底分离离!完成了进程独立性的技术保证! 写时拷贝是⼀种延时申请技术,可以提高整机内存的使用率。


1.4 fork常规用法

  1. 一个进程希望复制自己,使子进程同时执行不同的代码段。例如父进程等待客户端请求,生成子进程来处理请求。
  2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.5 for调用失败的原因

fork函数创建子进程也可能会失败,有以下两种情况:

  1. 系统中有太多的进程,内存空间不足,子进程创建失败。
  2. 实际用户的进程数超过了限制,子进程创建失败。

二、进程终止

2.1 进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果错误
  3. 代码异常终止
返回列表