UNIX/LINUX fork函数的问题 并不适合共享

起因介绍

一位朋友问我一个关于socket通信的相关问题,其需要解决的问题如下:

需要存在一个服务器进程,服务器进程会进行监听,负责建立与客户端的socket连接,同时可以存在多个客户端进程,客户端进程之间可以进行通信,不过客户端之间并不会建立socket连接,通信是通过将信息发送给服务端进程,服务端进程查找与目标客户端建立的socket标志来进行信息的发送。

为了解决上面的问题,这位朋友在服务端进程中自定义了一个链表用于保存与服务器建立的socket连接的客户端名称以及对应的socket标识符,当成功建立连接的时候,则在链表中插入一个元素,当一个客户端发送退出指令的时候,则将对应的socket关闭并将信息从链表中删除,每次有客户端需要发送数据的时候,则需要遍历链表,然后找到目标socket进行通信。

同时为了实现通信的要求,则必须要使用多进程或者多线程,这位朋友在服务端的代码中每次监听成均通过fork来建立一个子进程来处理与某个客户端之间的通信,同时这位朋友,在代码中申请了一块共享内存,用于保存链表头。

出现的问题

上述的描述似乎是很合理的,但是他在运行后出现了一个奇怪的现象:当服务端进程启动后,同时启动两个客户端进程与服务端进程建立socket连接,当client1client2发送消息的时候,代码出现了死循环。

哪里出现了死循环呢?根据朋友的调试,发现死循环出现在在链表中查询目的socket的过程,他发现,链表变成了一个环,同时这个链表上只有一个结点,也就是只有自己的结点信息,导致一直在链表上进行循环。

问题的原因

为什么会出现死循环呢?出现问题的关键就在于他使用fork这个函数。为了更加简单的进行描述我们先进性一个简单的实验:

编写一个程序,同时创建一个变量,对其进行赋值,然后调用fork()查看是否父子进程均能获取变量的正确值,同时尝试在子进程中对变量的值进行修改,父进程等待子进程完成后查看变量的值,看一下值是否成功被修改。

注:上面的问题实际上来自于《操作系统导论》,非常好的一本书,推荐大家阅读。

可以很容易的写出上面实验的代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>int main()
{int x = 137;int ret = fork();if (ret < 0) {printf("one error occurs when fork(), the ret: %d.\n", ret);exit(ret);} else if (ret == 0) {printf("child process (%d), the value of x is: %d.\n", getpid(), x);x *= 2;} else {printf("parent process (%d), the value of x is: %d.\n", getpid(), x);ret = wait(NULL);printf("after child process write the varible, parent process (%d), the value of x is: %d.\n", getpid(), x);}return 0;
}

运行结果如下:

parent process (110), the value of x is: 137.
child process (111), the value of x is: 137.
after child process write the varible, parent process (110), the value of x is: 137.

可以看到子进程对于变量的修改父进程并不知道。这就是问题所在:通过fork创建的进程会复制父进程的所有信息(注意是复制而不是共享),一个通过fork创建的进程对变量的修改对于另一个进程不可见。

上面说的复制,指的是将进程完整的拷贝一遍,放到另一块内存区域中进行执行,这会有一个问题也就是,初始两个进程所有的值都是一样的,同时虚拟地址也是一样的(这一点体现在如果两个进程紧接着进行堆内存的申请,那么会在会获得同样的地址,这一点也很好理解,由于是复制,那么自然堆的状态也是一样的,获取到同样的虚拟空间也是合理地,但是实际的物理空间却不相同)。

有了上面的基础,我们再来看思考一下为什么会出现死循环?

问题的关键在于同时使用了共享内存和fork,使用的共享内存能够保证在子进程中进行修改,其他进程能够看见,我们来实际进行模拟一下:

  • 首先,服务端进程进行启动,申请共享内存,地址我们记为0,用headp_address变量保存该地址(即headp_address=0)由于此时的链表是空的,表头初始为空,即执行*headp_address=NULL
  • client1启动,此时向链表中插入一个新的结点,新结点通过malloc进行申请内存,假设此时申请的地址为4那么我们将该节点插入到链表头即执行*headp_address=new_node,此时new_node=4
  • client2启动,此时同样进行新结点内存的申请(通过malloc),根据之前的结论,此时会申请到相同的内存即还是会申请到4(这里是虚拟内存),但是由于对于共享内存上的修改,两个进程能够发现,此时发现链表中已经有一个元素了,于是使用头插法将当前的结点插入到链表的开头,也就是执行:
    new_node->next = *headp_address;
    *headp_address=new_node;
    
    根据前面的信息我们知道*headp_address=4,在执行上面的语句之后,我们发现*headp_address=4, *headp_address->next=4;也就是说这个时候链表变成了一个环,这也就是为什么后面通过某个客户端进行信息的发送的时候会出现死循环。

问题的解决

这里提供几种解决方案:

  • 使用pthread替换fork()pthread可以实现子线程进行修改各个进程能够看到,也就是说其更偏向与实现共享的功能,而fork()则是偏向于复制。(实际上这两个函数都调用了系统调用clone,但是pthread_create在调用的时候增加了CLONE_VM标志,使得能够实现共享)。
  • 如果一定要使用fork()则应该通过共享内存实现共享,也就是链表的申请与释放也应该在共享内存上进行,这个时候则应该使用静态链表进行管理(也就是固定空间大小的链表)。
  • 如果要同时使用fork()和动态链表,我并没有想到什么比较好的方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/3817.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

提示工程师:如何写好Prompt

提示工程由来 提示工程是一门相对较新的学科&#xff0c;用于开发和优化提示以有效地将语言模型 (LM) 用于各种应用程序和研究主题。 研究人员使用提示工程来提高 LLM 在广泛的常见和复杂任务&#xff08;例如问题回答和算术推理&#xff09;上的能力。 开发人员使用提示工程…

JavaWeb(4)——HTML、CSS、JS 快速入门

一、JavaScript 数组 数组筛选&#xff08;查找&#xff0c;将原来数组中的某些数据去除&#xff09; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&quo…

接下来讲一讲Vue的数据代理

首先讲一下原生js的数据代理 原生的 Object.defineProperty() let aa wewewlet person {name: "王李斌",age: 12} Object.defineProperty(person, "address", {// value: 14&#xff0c; 给字段设置值//enumerable:true, 设置动态设置的字段为可以遍历/…

心电前置放大电路制作与原理详细分析(附电路板实物图)

心电前置放大电路制作与原理详细分析(附电路板实物图) 实验目的实验结果实验电路图原理解释与计算实验测试过程实验参数测量实验洞洞板焊接实验目的 心电信号具有微弱、低频、和高阻抗等特性,极其容易受到干扰。为了实现心电信号的放大,前置放大器需要满足高输入阻抗、高共…

JS--es6模板字符串

一、模板字符串空格 const str 这是一个${" "}空格; console.log(str); // 这是一个 空格二、模板字符串换行 1.转义 const str 这是一个换行\n内容; console.log(str); //这是一个换行 //内容2.缩进换行 const code function test() {con…

Java List集合 -- 最常用的两种排序方法

现在有一个类 public class Person {private int id;private int age;private String name;public Person(int id, int age, String name) {this.id id;this.age age;this.name name;}public int getId() {return id;}public int getAge() {return age;}public String getN…

基于STM32的homeassistant(采用FreeRTOS操作系统)【第一、二章优化拓展:Wifi、服务器连接验证以及UASRT串口区分】

第一、二章优化拓展开发环境&#xff1a; 主控STM32F103C8T6WIFI模块ESP01S开发语言C开发编译器 KEIL 组网方式WIFI服务器协议MQTT 硬件连接 STM32ESP01S3.3V3.3V GND GND GPIO2 (USRAT2-TX) RXGPIO3 (USART3-RX)TX 本章要点&#xff1a; 对ESP01S的AT指令的反馈指令进…

【Go】实现一个代理Kerberos环境部分组件控制台的Web服务

实现一个代理Kerberos环境部分组件控制台的Web服务 背景安全措施引入的问题SSO单点登录 过程整体设计路由反向代理登录会话组件代理YarnHbase 结果 背景 首先要说明下我们目前有部分集群的环境使用的是HDP-3.1.5.0的大数据集群&#xff0c;除了集成了一些自定义的服务以外&…

vite 引入局部组件 必须带.vue

11:03:47 AM [vite] Internal server error: Failed to resolve import “./components/layoutsHeader” from “src/views/layouts/layouts.vue”. Does the file exist? 在这里插入图片描述

ADC 的初识

ADC介绍 Q: ADC是什么&#xff1f; A: 全称&#xff1a;Analog-to-Digital Converter&#xff0c;指模拟/数字转换器 ADC的性能指标 量程&#xff1a;能测量的电压范围分辨率&#xff1a;ADC能辨别的最小模拟量&#xff0c;通常以输出二进制数的位数表示&#xff0c;比如&am…

ENSP实验一:防火墙基础配置

1、搭建拓扑图 配置client&#xff08;内网&#xff09;、FTP Server&#xff08;外网&#xff09;的IP地址 客户端设置&#xff1a; 服务端设置&#xff1a; 2、配置防火墙命名 进入防火墙&#xff0c;输入密码&#xff1a;默认为admin123 <USG6000V1>system-view /…

计算机网络 day8 动态路由 - NAT - SNAT实验 - VMware的网卡的3种模式

目录 动态路由&#xff1a;IGP 和 EGP 参考网课&#xff1a;4.6.1 路由选择协议概述_哔哩哔哩_bilibili ​编辑 IGP&#xff08;Interior Gateway Protocol&#xff09;内部网关协议&#xff1a; EGP&#xff08;Interior Gateway Protocol&#xff09;外部网关协议&#x…

python标准库模块,json

展示了如何使用json模块进行编码和解码操作的常规示例&#xff1a; Python标准库模块——json&#xff08;编码解码json格式&#xff09; json模块简介 json模块是Python中的一个编码和解码JSON格式的轻量级模块&#xff0c;主要用于将Python对象编码为JSON格式输出或存储&a…

【论文阅读】聚集多个启发式信号作为监督用于无监督作文自动评分

摘要 本文提出一个新的无监督的AES方法ULRA&#xff0c;它不需要真实的作文分数标签进行训练&#xff1b;ULRA的核心思想是使用多个启发式的质量信号作为伪标准答案&#xff0c;然后通过学习这些质量信号的聚合来训练神经自动评分模型。为了将这些不一致的质量信号聚合为一个统…

CPU/内存相关术语

一、IO多路复用 IO多路复用是一种高效的I/O模型&#xff0c;可以监视多个文件描述符&#xff0c;当任何一个文件描述符就绪&#xff08;可读或可写&#xff09;时&#xff0c;就会通知程序进行读写操作。这种方式可以避免使用多线程或多进程的方式进行I/O操作&#xff0c;从而…

vue代码格式化,Prettier - Code formatter格式化规则文件

vue2&#xff0c;vue3格式化代码使用方法&#xff1a; 1、新建文件名&#xff1a; .prettierrc.cjs&#xff0c;里面放上下面的代码片段&#xff0c;直接粘贴即可 2、把 .prettierrc.cjs文件放在项目的根目录中 // prettier的默认配置文件 module.exports {// 一行最多 100 …

【Whisper】《OpenAI Whisper 精读【论文精读】》学习笔记

方法 Whisper在论文中表示使用单模型&#xff08;single model&#xff09;来完成多个语音任务&#xff08;multitask&#xff09;&#xff0c;李沐教授认为优点是设计上比较干净&#xff1b; I. 关于单模型效果的疑问 但是他同时也提出了两个疑问&#xff1a; 使用单模型会…

hudi系列-KeyGenerator 分区提取器

record key与hoodie key hudi支持数据更新,在upsert语义下,需要用记录级别的主键来表示每行数据的唯一性。主键是由record key和分区路径共同构成的 record key:记录键,分区下唯一,当为非分区表时等同于主键,虽然在源码中声明了a recordKey that acts as primary key fo…

矩阵AB和BA的特征值相同

手写的&#xff0c;如下图&#xff1a; 即可证明&#xff0c;矩阵AB的特征值和BA的特征值相同。 关于矩阵转置和逆矩阵混合运算&#xff0c;有如下规律&#xff1a;

unity01 界面布局

布局 坐标系 遵循左手定则&#xff0c;中指是y轴、食指是x轴、大拇指是z轴。 可以理解为x轴代表东西方向&#xff0c;z轴代表南北方向&#xff0c;y轴代表上下方向。 常用快捷键 鼠标中键&#xff1a;移动地图 右键&#xff1a;移动视角 shift鼠标左键单击gimo导航器的小方…