【嵌入式Linux应用开发基础】fork()函数
目录
一、fork 函数概述
1.1. 函数作用
1.2. 函数原型与头文件
1.3. 返回值
1.4. 核心特性
二、父子进程的区别与联系
2.1. 相同点
2.2. 不同点
三、典型应用场景
3.1. 多任务处理
3.2. 守护进程创建
3.3. 执行外部程序
3.4. 并行计算
四、fork 函数的关键注意事项
4.1. 返回值处理
4.2. 执行顺序不确定性
4.3. 资源继承
4.4. 僵尸进程处理
4.5. 多线程环境下的风险
4.6. 信号处理
4.7. 内存与资源的独立性
4.8. 性能影响
4.9. 错误处理
4.10. 替代函数
在嵌入式 Linux 应用开发中,fork 函数是一个至关重要的系统调用,用于创建新进程。它允许一个进程(父进程)创建一个与自身几乎完全相同的副本(子进程)。这两个进程(父进程和子进程)在 fork 调用之后,将各自执行后续代码,但具有不同的执行路径。
一、fork 函数概述
1.1. 函数作用
fork 函数通过复制调用它的进程来创建一个新进程。具体来说,内核为新进程分配新的进程控制块(PCB),并复制父进程的地址空间、打开的文件描述符、信号处理函数等资源。子进程从 fork 调用处开始执行,与父进程并发运行。然而,子进程和父进程在进程ID和fork函数的返回值上有所不同。
1.2. 函数原型与头文件
fork 函数的原型如下:
#include <unistd.h>
pid_t fork(void);
1.3. 返回值
fork 函数返回一个 pid_t 类型的值,该值有三种情况:
- 在父进程中:fork函数返回新创建的子进程的进程ID(PID)。父进程可以通过这个 PID 来识别和管理子进程。
- 在子进程中:fork函数返回0。子进程可以通过检查返回值为 0 来确定自己是子进程。
- 出错时:fork函数返回-1,并设置全局变量errno以指示错误原因。常见的错误原因包括系统资源不足(如内存不足)等。
1.4. 核心特性
-
写时复制(Copy-on-Write, CoW)):内存仅在写入时复制,提升效率。
-
独立执行流:父子进程并发执行,顺序由调度器决定。
-
文件描述符共享:子进程继承父进程打开的文件,需注意同步。
二、父子进程的区别与联系
2.1. 相同点
-
子进程继承了父进程的大部分属性,如用户 ID、组 ID、环境变量、当前工作目录、文件描述符等。意味着子进程在很多方面与父进程具有相似的运行环境。
-
子进程从父进程处复制了地址空间,包括代码段、数据段、堆和栈等。因此,子进程在
fork调用之后,看起来和父进程执行相同的代码。
2.2. 不同点
-
进程 ID(PID):父进程和子进程具有不同的 PID。父进程可以通过
fork的返回值获取子进程的 PID,而子进程可以通过getpid函数获取自己的 PID,通过getppid函数获取父进程的 PID。 -
返回值:
fork函数在父进程和子进程中返回不同的值,这是区分父子进程的关键。 -
资源使用:虽然子进程复制了父进程的资源,但在实际运行中,父子进程对资源的使用是相互独立的。例如,父子进程对全局变量的修改不会相互影响,因为它们有各自独立的地址空间。不过,对于共享资源(如打开的文件描述符),父子进程需要协调使用,以避免数据竞争和不一致。
三、典型应用场景
3.1. 多任务处理
在需要同时执行多个任务的场景下,fork 函数可用于创建子进程,让每个进程独立执行不同的任务,实现并发操作,提高系统整体效率。
①网络服务器:网络服务器通常需要同时处理多个客户端的请求。主进程可以作为监听进程,负责接收客户端的连接请求。每当有新的连接到来时,主进程调用 fork 创建一个子进程来专门处理该客户端的请求,而主进程继续监听新的连接。这样,服务器可以同时处理多个客户端,提高响应速度和并发处理能力。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define PORT 8080int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置套接字选项if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定套接字if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 监听连接if (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}while (1) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");continue;}pid_t pid = fork();if (pid < 0) {perror("fork failed");} else if (pid == 0) {// 子进程处理客户端请求close(server_fd);// 处理客户端请求的代码close(new_socket);exit(EXIT_SUCCESS);} else {// 父进程继续监听新连接close(new_socket);}}return 0;
}
②嵌入式系统中的数据采集与处理:在嵌入式设备中,一个进程可以负责从传感器采集数据,另一个进程可以对采集到的数据进行处理和分析。主进程创建一个子进程负责数据采集,主进程则专注于数据处理,两个进程并发执行,提高系统的数据处理效率。
3.2. 守护进程创建
守护进程是在后台运行的进程,通常在系统启动时启动,一直运行直到系统关闭,用于执行系统级任务,如日志记录、系统监控等。fork 函数是创建守护进程的关键步骤之一。
日志记录守护进程:系统需要记录各种事件和操作的日志,以方便后续的问题排查和系统分析。可以通过 fork 创建守护进程,让其在后台不断监听系统事件,并将相关信息记录到日志文件中。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>void create_daemon() {pid_t pid = fork();if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);}if (pid > 0) {// 父进程退出exit(EXIT_SUCCESS);}// 子进程继续执行// 创建新会话if (setsid() < 0) {perror("setsid failed");exit(EXIT_FAILURE);}// 改变工作目录if (chdir("/") < 0) {perror("chdir failed");exit(EXIT_FAILURE);}// 设置文件掩码umask(0);// 关闭标准输入、输出和错误输出close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 守护进程的工作逻辑,例如日志记录while (1) {// 日志记录代码sleep(1);}
}int main() {create_daemon();return 0;
}
3.3. 执行外部程序
在某些情况下,需要在程序中启动外部程序来完成特定功能。可以通过 fork 创建子进程,然后在子进程中使用 exec 系列函数来执行外部程序。
系统命令调用:在编写脚本或工具时,可能需要调用系统命令来完成某些操作,如文件压缩、进程管理等。通过 fork 创建子进程,在子进程中使用 exec 函数执行相应的系统命令。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程执行外部程序char *args[] = {"ls", "-l", NULL};if (execvp("ls", args) == -1) {perror("execvp failed");exit(EXIT_FAILURE);}} else {// 父进程等待子进程结束wait(NULL);}return 0;
}
3.4. 并行计算
在进行大规模数据处理或复杂计算时,可以利用 fork 创建多个子进程,将任务分解到不同的进程中并行执行,以加快计算速度。
数值计算:例如,计算一个大型矩阵的乘法,可以将矩阵分成多个子矩阵,每个子进程负责计算一部分子矩阵的乘积,最后将结果合并。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>#define MATRIX_SIZE 100
#define NUM_PROCESSES 4// 矩阵乘法函数
void matrix_multiplication(int start, int end) {// 执行矩阵乘法的代码for (int i = start; i < end; i++) {for (int j = 0; j < MATRIX_SIZE; j++) {// 计算矩阵元素}}
}int main() {pid_t pid;int i;int chunk_size = MATRIX_SIZE / NUM_PROCESSES;for (i = 0; i < NUM_PROCESSES; i++) {pid = fork();if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {int start = i * chunk_size;int end = (i == NUM_PROCESSES - 1) ? MATRIX_SIZE : (i + 1) * chunk_size;matrix_multiplication(start, end);exit(EXIT_SUCCESS);}}// 父进程等待所有子进程结束for (i = 0; i < NUM_PROCESSES; i++) {wait(NULL);}return 0;
}
四、fork 函数的关键注意事项
4.1. 返回值处理
-
父进程:返回子进程的 PID(>0)。
-
子进程:返回 0。
-
错误:返回 -1(如进程数达到上限、内存不足)。
pid_t pid = fork();
if (pid == -1) {// 处理错误
} else if (pid == 0) {// 子进程代码
} else {// 父进程代码
}
注意:必须检查返回值,否则可能导致逻辑错误或资源泄漏。
4.2. 执行顺序不确定性
-
父子进程的执行顺序由调度器决定,不能假设谁先运行。
-
解决方案:如需同步,使用
waitpid()、信号量或管道等机制。
4.3. 资源继承
-
子进程复制父进程的:
-
内存(代码、数据、堆栈,通过 写时复制 优化)。
-
打开的文件描述符(共享同一文件偏移量,可能导致写入竞争)。
-
信号处理函数、进程组、会话、环境变量等。
-
-
关键问题:
-
文件描述符需及时关闭(如网络套接字),避免资源泄漏。
-
使用
O_CLOEXEC标志打开文件,确保exec后自动关闭。
-
4.4. 僵尸进程处理
-
子进程终止后:若父进程未调用
wait()/waitpid(),子进程将变为僵尸进程(占用系统资源)。 -
解决方案:
-
父进程主动回收子进程状态。
-
使用信号
SIGCHLD处理函数异步回收。
-
4.5. 多线程环境下的风险
-
fork()仅复制调用线程,其他线程在子进程中“消失”。 -
风险:若父进程其他线程持有锁,子进程可能死锁或数据不一致。
-
建议:
-
在
fork()后立即调用exec()执行新程序(避免执行复杂逻辑)。 -
避免在多线程程序中直接使用
fork()。
-
4.6. 信号处理
-
子进程继承父进程的信号处理函数,但未决信号会被清空。
-
注意:子进程可能需要重新设置信号处理逻辑。
4.7. 内存与资源的独立性
-
父子进程的内存修改互不影响(因写时复制机制)。
-
例外:共享的文件描述符(如管道、套接字)会影响彼此。
4.8. 性能影响
-
频繁
fork()可能导致性能问题(即使有写时复制)。 -
替代方案:考虑使用线程池或
posix_spawn()。
4.9. 错误处理
-
检查
fork()是否失败(如返回 -1),防止后续逻辑错误。
if (pid == -1) {perror("fork failed");exit(EXIT_FAILURE);
}
4.10. 替代函数
-
vfork():创建子进程但不复制内存,子进程必须立即调用exec()或_exit()(已逐渐被废弃)。 -
posix_spawn():更安全、高效地创建新进程(封装了fork()+exec())。
总之,fork 函数是嵌入式 Linux 应用开发中实现进程控制和多任务处理的重要工具。深入理解其原理、使用方法及注意事项,对于开发高效、稳定的嵌入式系统至关重要。
相关文章:
【嵌入式Linux应用开发基础】fork()函数
目录 一、fork 函数概述 1.1. 函数作用 1.2. 函数原型与头文件 1.3. 返回值 1.4. 核心特性 二、父子进程的区别与联系 2.1. 相同点 2.2. 不同点 三、典型应用场景 3.1. 多任务处理 3.2. 守护进程创建 3.3. 执行外部程序 3.4. 并行计算 四、fork 函数的关键注意事…...
2024 年 CSDN 博客之星年度评选:技术创作与影响力的碰撞(统计时间2025-02-17 11:06:06)
摘要:在技术的海洋里,每一位博主都像是一座独特的灯塔,用自己创作的光芒照亮他人前行的道路。2024 年 CSDN 博客之星年度评选活动,正是对这些灯塔的一次盛大检阅,让我们看到了众多优秀博主在技术创作领域的卓越表现以及…...
串的基本操作--数据结构
目录 一、串的基本概述 二、串的存储结构 2.1定义属性存储结构 串长有两种表示方法: 1、用一个额外的变量length来存放串的长度; 2、串值后面加一个不计入串长的结束标记字符“\0”,此时的串长为隐含值。 2.2堆的顺序存储结构 三、串的基本操…...
Unity 命令行设置运行在指定的显卡上
设置运行在指定的显卡上 -force-device-index...
Dest1ny漏洞库: 美团代付微信小程序系统任意文件读取漏洞
大家好,今天是Dest1ny漏洞库的专题!! 会时不时发送新的漏洞资讯!! 大家多多关注,多多点赞!!! 0x01 产品简介 美团代付微信小程序系统是美团点评旗下的一款基于微信小程…...
设计模式:状态模式
状态机有3个要素:状态,事件,动作。 假如一个对象有3个状态:S1、S2、S3。影响状态的事件有3个:E1、E2、E3。每个状态下收到对应事件的时候,对象的动作为AXY。那么该对象的状态机就可以用如下表格来表示。S1收到事件E1的…...
【故障处理】- 执行命令crsctl query crs xxx一直hang
【故障处理】- 执行命令crsctl query crs xxx一直hang 一、概述二、故障处理三、解决方法 一、概述 Oracle RAC环境中,遇到执行crsctl query crs xxx等相关命令不返回任何结果,一直hang在那里。系统下执行命令ps -ef |grep crsctl query crs softwarever…...
Zabbix——监控Nginx
背景 在项目中使用Nginx之后,有时候我们需要知道Nginx具体的工作情况,这时候就需要使用zabbix进行Nginx的相关监控 这边我们有两种方法 使用普通的http请求的方式获取基本信息如果使用了Nginx Plus,就可以通过Nginx Plus的接口获取更多的信…...
开源工具推荐--思维导图、流程图等绘制
1. 前言 在工作中,经常要用到各种不同的工具,随着系统的升级,有些工具也在不断更新升级。这里收集整理一些好用的开源工具推荐,遵循以下一些基本原则:开源免费,商业工具的有效平替,轻量级&…...
【论文笔记】Transformer^2: 自适应大型语言模型
Code repo: https://github.com/SakanaAI/self-adaptive-llms 摘要 自适应大型语言模型(LLMs)旨在解决传统微调方法的挑战,这些方法通常计算密集且难以处理多样化的任务。本文介绍了Transformer(Transformer-Squared)…...
FFmpeg源码:av_strlcpy函数分析
一、引言 在C/C编程中经常会用到strcpy这个字符串复制函数。strcpy是C/C中的一个标准函数,可以把含有\0结束符的字符串复制到另一个地址空间。但是strcpy不会检查目标数组dst的大小是否足以容纳源字符串src,如果目标数组太小,将会导致缓冲区…...
Unity Shader学习6:多盏平行光+点光源 ( 逐像素 ) 前向渲染 (Built-In)
0 、分析 在前向渲染中,对于逐像素光源来说,①ForwardBase中只计算一个平行光,其他的光都是在FowardAdd中计算的,所以为了能够渲染出其他的光照,需要在第二个Pass中再来一遍光照计算。 而有所区别的操作是࿰…...
docker批量pull/save/load/tag/push镜像shell脚本
目录 注意: 脚本内容 执行效果 注意: 以下脚本为shell脚本通过docker/nerdctl进行镜像独立打包镜像的相关操作脚本内仓库信息和镜像存取路径需自行更改需自行创建images.txt并填写值,并且与脚本位于同级目录下 [rootmaster01 sulibao]# l…...
五十天精通硬件设计第32天-S参数
系列文章传送门 50天精通硬件设计第一天-总体规划-CSDN博客 目录 1. S参数基础 2. S参数在信号完整性中的作用 3. 单端 vs. 差分S参数 4. S参数的关键特性 5. S参数的获取与使用 6. S参数分析中的常见问题 7. 实际案例:PCIe通道分析 8. 工具推荐 总结 信号完整性中…...
6.2.4 基本的数据模型
文章目录 基本的数据模型 基本的数据模型 基本的数据模型包含层次模型,网状模型和关系模型。 层次模型:使用树型结构表示数据间联系。记录间的联系用指针实现,简单高效。但是只能表示1:n的联系,且对插入、删除的限制多。网状模型…...
DeepSeek ,银行营销会被 AIGC 颠覆吗?
AI 让银行营销更智能,但更重要的是“懂客户” AI 在银行营销中的应用已经不仅仅局限于文案生成,而是渗透到了整个营销流程。 据悉,中国银行已经开始利用 AI 大模型构建智能营销助手系统,结合知识图谱和 AI 技术,实现…...
第150场双周赛:好数字之和、分割正方形 Ⅰ、分割正方形 Ⅱ、最短匹配字符串
Q1、好数字之和 1、题目描述 给定一个整数数组 nums 和一个整数 k,如果元素 nums[i] 严格 大于下标 i - k 和 i k 处的元素(如果这些元素存在),则该元素 nums[i] 被认为是 好 的。如果这两个下标都不存在,那么 nums…...
HDFS是如何存储和管理大数据
HDFS(Hadoop Distributed File System,Hadoop分布式文件系统)是专为大数据处理而设计的分布式文件系统,具有高吞吐量、高容错性等特点,适用于大规模数据存储和管理。以下是HDFS存储和管理大数据的详细机制:…...
进阶——第十六届蓝桥杯嵌入式熟练度练习(开发板捕获频率和占空比)
单通道捕获频率 HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1); void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {if(htim->InstanceTIM2) {cap1HAL_TIM_ReadCapturedValue(&htim2,TIM_CHANNEL_1);TIM2-&…...
智能协同:数据集成平台与DeepSeek驱动的数据分析与智能调度革新
前言 企业面临着海量数据的挑战与机遇。如何高效地整合多源数据、精准分析并智能决策,成为企业提升竞争力的关键。本文解析轻易云数据集成平台与DeepSeek技术结合在数据分析和智能调度方面的创新应用,揭示其为企业带来的高效、智能与精准的业务价值。 …...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
