fork入门

news2024/9/24 19:18:05

1哪个分支会打印

如下是fork的典型问题。fork之后有3个分支,分别是pid等于0,pid大于0,pid小于0。如果我们不了解fork的话,那么肯定会认为这里的if else分支只会有一个分支被执行。而实际的执行结果是两个分支都执行了。fork返回之后就创建了一个子进程,父进程在fork返回之后继续向下执行;子进程同样也是从fork返回之后开始执行。对于父进程来说,返回值是子进程的进程号,对于子进程来说,返回值是0。

fork创建一个进程,就类似于孕妈妈生孩子。将孕妈妈推进产房的时候是一个人,从产房出来的时候是两个人。孩子刚出生的时候共享父母的资源,比如房子,金钱等,fork创建的子进程也是共享着父进程的资源。

fork类似于产房,调用fork就相当于将孕妈妈推进了产房,父进程类似于孕妈妈,子进程类似于刚出生的孩子。

fork过程和现实世界有着高度的相似性。

#include <sys/types.h>
#include <unistd.h>

int main() {
  pid_t pid = fork();
  if (pid == 0) {
    printf("pid is 0\n");
  } else if (pid > 0) {
    printf("pid > 0\n");
  } else {
    printf("pid < 0\n");
  }
  return 0;
}

2fork和pthread_create的区别

fork是创建一个新的进程,pthread_create是创建一个新的线程。直观来看,两者的区别是显而易见的。

进程和线程的区别:

进程是资源封装的单位,线程是调度的单位。进程的资源包括内存、打开的文件、信号等。以内存为例,之所以说进程是资源封装的单位,分配给一个进程的内存,只有这个进程是可以访问的,这个进程内的所有线程都共享进程的内存资源,而一个进程的内存,其它进程是不能访问的。

进程的资源:

进程号pid在一个进程内,不管在哪个线程中调用getpid,返回的pid都是一样的,都是所在的进程的进程号。
内存

①内存,最常讨论的是堆内存和栈内存,堆内存是属于进程的资源,一个进程内的所有线程共享;栈内存属于线程的资源。

②全局变量是属于进程的资源,局部变量属于线程的资源。

所以说,并不是所有的内存都是线程共享的,栈内存就是一个线程专有的。

信号处理函数当我们通过signal或sigaction注册某个信号的处理函数时,不管是在哪个线程中注册的,那么这个回调函数对于整个进程都是生效的。
打开的文件打开的文件用一个fd来表示,打开的文件属于进程的资源。

进程是资源管理的基本单位,但是在父子进程之间,两者的资源也不是完全隔离的。不同的资源有不同的处理方式:内存是写时拷贝(cow, copy on write),fork之后,父子进程之间共享内存,当内存被写时,父子进程分家;信号处理函数、打开的文件、调度策略,子进程与父进程保持一致。

fork、exec踩坑记录

在用户态来看,fork和pthread_create是完全不相干的两个api,进程和线程的区别也是很清晰。fork和pthread_create都是用户态的api,两者最终都会调用同一个系统调用clone。在linux内核中,fork创建的进程和pthread_create创建的线程都是用一个struct task_struct来表示,区别就在于资源是不是共享,不共享则创建的是进程,共享则创建的是线程。

通过实际代码和strace来查看fork和pthread对clone的调用:

如下是fork代码,使用fork创建一个子进程。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
  sleep(2);

  printf("before fork\n");
  pid_t pid = fork();
  if (pid == 0) {
    for (int i = 0; i < 3; i++) {
      printf("child process, pid=%d\n", getpid());
      sleep(1);
    }
  } else if (pid > 0) {
    for (int i = 0; i < 3; i++) {
      printf("parent process, pid=%d\n", getpid());
      sleep(1);
    }
  }
  return 0;
}

如下是pthread的代码,c++中的std::thread最终也会通过pthread_create来创建一个线程。

#include <iostream>
#include <thread>
#include <unistd.h>

void func() {
}

int main() {
  sleep(2);

  std::cout << "before create thread\n";
  std::thread t(func);
  t.join();
  return 0;
}

使用 gcc fork.c -o fork和g++ thread.c -o thread分别编译上边的代码,然后使用strace来跟踪调用系统调用的情况。strace是linux的一个工具,可以打印应用调用的系统调用,同时也会打印出来形参和返回值。

如下两个截图分别显示了fork调用clone和pthread_create调用clone, 可以看到fork和pthread_create调用clone时,入参的个数和参数的内容都是不一样的。其中最主要的区别是第二个参数,pthread_create调用clone时,flag中的参数CLONE_VM,CLONE_FILES,CLONE_SIGHAND等标志,说明创建的这个线程与父线程共享这些资源。理论上来说,直接使用clone比较灵活,有多种参数的组合,但在实际项目中,没有这样的使用场景,也要尽量避免直接使用clone,使用不当很容易出错。fork和pthread_create均是用户态的api,而不是系统调用,clone才是系统调用。

fork调用clone:

pthread_create调用clone:

clone flag

clone最重要的一个形参就是flag,传入不同的flag可以决定子进程和父进程的哪些资源共享,哪些资源不共享。调用pthread_create的时候,flag中包括CLONE_VM、CLONE_FILES、CLONE_FS、CLONE_THREAD。这里的CLONE是共享的意思,而不是克隆一份全新的。

通过形参的方式来实现系统调用,这样可以使系统调用有很强的可扩展性,当需要支持不同的功能的时候,直接通过增加flag或者修改flag就可以。保证在系统调用不变的情况下,更改支持的功能,同时也能保持向下的兼容。通过man clone,也能看到clone支持很多个flag,这些flag都是在不同的linux版本中支持的。

CLONE_VM子进程和父进程共享内存,子进程修改了内存,父进程能看到;反之亦然。
CLONE_FILES

子进程和父进程共享打开的文件,也就是共享进程的fd table。比如父进程中打开了一个tcp socket,一个tcp socket就是一个打开的文件,如果设置了这个标志,那么子进程中也可以使用;如果父进程或者子进程把这个fd关闭了,那么子进程和父进程都不能使用这个fd了。

这里要区分共享和继承的区别,对于打开的文件来说,现成会共享父线程的打开的fd,共享的话是一份,两者共用一份;fork的子进程会继承父进程的打开的fd,继承的话在父子进程中各有一份,这个时候如果只是父进程或者子进程把fd关闭了,那么不影响子进程或者父进程。

CLONE_FS共享文件系统,比如进程的工作目录或者文件系统的根目录。
CLONE_THREAD

这个标志标识将新进程加入到和父进程相同的线程组中。

线程组可以看做就是一个进程,一个进程内的所有线程都属于一个线程组。

我们使用getpid获取进程id的时候,不管是在哪个线程中获取,那么获取的值都是相同的,这个值也叫线程组id,即TGID,thread group id。

3创建进程

创建一个进程的过程并不神秘。举一个我们在c语言中使用结构体的例子,当我们使用c语言的时候,经常使用结构体:使用malloc为结构体申请一块内存,然后再逐个对结构体的成员进行赋值,这是典型的使用步骤。

fork创建一个新的进程,本质上与使用结构体的方式是类似的。在内核中,进程用一个结构体struct task_struct来表示,fork中首先申请了一个struct task_struct,然后将结构体的属性进行初始化,最后将之加入到运行队列。简单来说分为3步:创建对象,初始化对象,使用对象

使用结构体的例子:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

struct Person {
  char *name;
  int age;
};

int main() {
  struct Person *p = (struct Person *)malloc(sizeof(struct Person));
  if (p == NULL) {
    printf("p is null\n");
    return -1;
  }

  p->name = "xiaoming";
  p->age = 7;
  printf("name:%s,age:%d\n", p->name, p->age);
  return 0;
}

如果使用过c++,那么我们知道,c++中除了构造函数,还有拷贝构造函数以及移动构造函数。对于对象的拷贝来说,又分为浅拷贝和深拷贝,假如对象中有一个成员为char *数据类型,那么拷贝的时候只拷贝指针,就是浅拷贝;拷贝的时候如果将char *指针中的内容都拷贝,那么就是深拷贝。

fork可以看做是构造一个进程,构造进程的过程类似于c++中的浅拷贝。以内存为例,fork之后,子进程会共享父进程的内存资源,并不是把父进程内存中的内容都拷贝了一份到子进程中。当内存被写时,这个时候父子进程的内存才会分家,又称写时拷贝。

3.1kernel_clone

fork的工作都是在函数kernel_clone中完成,kernel_clone做的事情分为3步:

(1)首先,要进行参数检查,主要检查flag的设置,有没有冲突的地方,如果参数检查不通过,则返回错误;否则,进行下一步。

(2)copy_process,这也是最重要的一步,创建一个struct task_struct,然后对结构体成员进行初始化化,从copy_process的名字也可以看出,主要是拷贝,新进程的内容,大部分是从父进程的task_struct中拷贝而来。

(3)进程已经创建,最后就是将进程唤醒,唤醒之后,进程就可以运行了。

pid_t kernel_clone(struct kernel_clone_args *args)
{
    //1.参数检查
	if ((args->flags & CLONE_PIDFD) &&
	    (args->flags & CLONE_PARENT_SETTID) &&
	    (args->pidfd == args->parent_tid))
		return -EINVAL;
	if (!(clone_flags & CLONE_UNTRACED)) {
		if (clone_flags & CLONE_VFORK)
			trace = PTRACE_EVENT_VFORK;
		else if (args->exit_signal != SIGCHLD)
			trace = PTRACE_EVENT_CLONE;
		else
			trace = PTRACE_EVENT_FORK;

		if (likely(!ptrace_event_enabled(current, trace)))
			trace = 0;
	}
    
    //2.copy_process
	p = copy_process(NULL, trace, NUMA_NO_NODE, args);
	
    //3.唤醒进程
	wake_up_new_task(p);
	return nr;
}

3.2copy_process

我的内核代码版本是5.10.186, copy_process函数有542行,fork的核心工作都是在copy_process中完成的。

copy_process中做的主要工作如下:

(1)参数检查

①在不同的命名空间下或者不同的用户下,不允许共享文件系统。

②如果标志中有CLONE_THREAD,说明创建的是一个线程,线程要和创建它的进程共享信号处理函数。

③如果共享信号处理函数,但是没有共享VM,也就是内存,这样是不允许的。

	/*
	 * Don't allow sharing the root directory with processes in a different
	 * namespace
	 */
	if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
		return ERR_PTR(-EINVAL);

	if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
		return ERR_PTR(-EINVAL);

	/*
	 * Thread groups must share signals as well, and detached threads
	 * can only be started up within the thread group.
	 */
	if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
		return ERR_PTR(-EINVAL);

	/*
	 * Shared signal handlers imply shared VM. By way of the above,
	 * thread groups also imply shared VM. Blocking this case allows
	 * for various simplifications in other code.
	 */
	if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
		return ERR_PTR(-EINVAL);

	...

(2)dup_task_struct,创建一个新的task_struct并初始化

在这个函数中首先申请一个task_struct,然后将父进程的task_struct拷贝到新申请的task_struct,然后返回新申请的task_struct,这个新的task_struct就表示新创建的进程。从dup_task_struct函数的名字也可以看出来,是对task_struct的复制。该函数调用之后子进程的task_struct和父进程的task_struct是完全一样。

在该函数之后,会对新的task_struct进行修改,一个最直接的例子就是新进程的pid和父进程pid肯定是不一样的,要单独进行设置。

(3)设置pid

	/* ok, now we should be set up.. */
	p->pid = pid_nr(pid);

(4)资源拷贝

调用了很多以copy开头的函数,对资源进行拷贝。如下是对打开的文件进行拷贝的,在该函数中首先要检查是不是设置了CLONE_FILES,如果设置了,说明要和父进程共享,直接将父进程的引用计数加1;否则,通过dup_fd对打开的文件进行拷贝。从这里可以看到clone和copy的区别,前者是共享,后者是拷贝一份。

static int copy_files(unsigned long clone_flags, struct task_struct *tsk)

{

    ...

    if (clone_flags & CLONE_FILES) {

        atomic_inc(&oldf->count);

        goto out;

    }

    newf = dup_fd(oldf, NR_OPEN_MAX, &error);

    if (!newf)

        goto out;

    tsk->files = newf;

    error = 0;

out:

    return error;

}

	retval = copy_semundo(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_security;
	retval = copy_files(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_semundo;
	retval = copy_fs(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_files;
	retval = copy_sighand(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_fs;
	retval = copy_signal(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_sighand;
	retval = copy_mm(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_signal;
	retval = copy_namespaces(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_mm;
	retval = copy_io(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_namespaces;

(5)copy_thread

copy_thread是一个和cpu架构有关的函数,每个cpu架构都实现了自己的copy_thread,在其中主要的工作是设置线程的栈信息。这个函数中做了很重要的事情,我们非常关心的两个问题都是在这个函数中做的:

①fork之后,子进程的返回值为什么是0?

返回值保存在一个特定的寄存器中,在copy_thread中将该寄存器设置为0,那么返回值就为0。

②子进程执行的第一条指令是哪个,也就是说子进程是从哪条指令开始执行的?

在copy_thread中设置新进程第一个执行的函数为ret_from_fork,该函数从内核空间返回到用户空间。

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

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

相关文章

客流预测 | 基于Transformer下车站点客流推断研究(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于Transformer的车站客流推断研究是指利用Transformer模型来预测车站的客流情况。Transformer是一种强大的深度学习模型&#xff0c;特别擅长处理序列数据。研究可以为城市交通管理提供重要决策支持&#xff0c;帮…

恢复丢失的数据:iPhone 恢复指南

不小心删除了 iPhone 上的重要数据并意识到它没有备份&#xff1f;您并不孤单。在 iPhone 上恢复永久删除的数据似乎令人生畏&#xff0c;但并非总是不可能。我们将探索各种方法&#xff0c;包括使用专门的软件和备份恢复选项&#xff0c;为您提供恢复丢失数据的最佳机会。 常见…

裸机:LCD

什么是LCD&#xff1f; LCD&#xff0c;全称为Liquid Crystal Display&#xff0c;即液晶显示屏&#xff0c;是一种利用液晶物质的光学特性&#xff0c;通过控制电场来改变光的透过性&#xff0c;从而实现图像显示的技术。以下是关于LCD的详细解释&#xff1a; 一、LCD的基本…

ElementPlus实现页面,上部分是表单,下部分是表格

效果 <template><el-dialog v-model"produceDialogFormVisible" draggable custom-class"dialog-title" :title"title" :close-on-click-modal"false"><el-form label-width"120px"><el-row :gutter&q…

【STM32H743】将全局变量定义到指定内存MDK

STM32H743将全局变量定义到指定内存MDK 2024年8月31日 #elecEngeneer 上链 参考硬汉嵌入式。 这样Target里面的设置就作废了。 把H743的几个SRAM写上 ; ************************************************************* ; *** Scatter-Loading Description File generated by…

5G智慧工地项目汇报方案

1. 项目概述 5G智慧工地项目旨在通过5G技术提升建筑工地的通信、安防、质量管理和精益化管理水平&#xff0c;打造科技感十足的“5G智慧建造体验中心”。 2. 智慧工地需求 当前智慧工地需求集中在实时化、可视化、多元化、智慧化和便捷化&#xff0c;以满足全时段安全管理和…

《机器学习》周志华-CH4(决策树)

4.1基本流程 决策树是一类常见的机器学习方法&#xff0c;又称“判别树”&#xff0c;决策过程最终结论对应了我们所希望的判定结果。 一棵决策树 { 一个根结点 包含样本全集 若干个内部结点 对应属性测试&#xff0c;每个结点包含的样本集合根据属性测试结果划分到子结点中 若…

基于ssm+vue的汽车租赁管理系统

摘要 随着移动应用技术的发展&#xff0c;越来越多的用户借助于移动手机、电脑完成生活中的事务&#xff0c;许多的传统行业也更加重视与互联网的结合&#xff0c;以提高商家知名度和寻求更高的经济利益。针对传统汽车租赁系统&#xff0c;租赁信息、续租信息等问题&#xff0c…

第4章-07-将WebDriver获取的Cookie传递给Requests

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年CSDN全站百大博主。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于专栏:Web爬虫入门与实战精讲,后续完整更新内容如下。 文章…

linux下基本指令(持续更新)

目录 1.adduser 2.passwd 3.userdel 4. su - 5.ls 6.pwd ​编辑 7.cd 8.touch 9.mkdir &#x1f680; 10. rmdir && rm &#x1f680; 11.whoami &#xff08;who am i) 12.clear 13.tree (需要安装 yum install -y tree) 14.who 1.adduser 语法&…

TCP协议(1)

目录 一、TCP协议介绍 二、TCP协议格式 2.1、解包与分用 2.2、TCP的可靠性 2.3、TCP的工作模式 2.4、确认应答(ACK)机制 2.5、32位序号与确认序号 2.6 16位窗口大小 2.7 六个标志位 2.7.1、SYN 2.7.2、FIN 2.7.3、ACK 2.7.4、PSH 2.7.5、URG 2.7.6、RST 2.8、T…

Arco Voucher - 不知道有什么用的凭证单据录入表单插件

关于 Arco Voucher Arco Voucher 插件是一款不知道有什么用的凭证单据录入表单插件&#xff0c;可能只是为了看着像传统的凭证单据。 动态表头 附件上传/预览 添加凭证明细 https://apps.odoo.com/apps/modules/browse?authorzerone40 如有插件定制化需求或其他插件资源…

MATLAB智能优化算法-学习笔记(2)——变邻域搜索算法求解旅行商问题【过程+代码】

旅行商问题 (TSP) 旅行商问题(Traveling Salesman Problem, TSP)是经典的组合优化问题之一。问题的描述是:给定若干个城市以及每对城市之间的距离,一个旅行商需要从某个城市出发,访问每个城市恰好一次,最后回到出发城市,目标是找到一条总距离最短的环路。TSP 是 NP-har…

通用 PDF OCR 到 Word API 数据接口

通用 PDF OCR 到 Word API 数据接口 文件处理&#xff0c;OCR&#xff0c;PDF 高可用图像识别引擎&#xff0c;基于机器学习&#xff0c;超精准识别率。 1. 产品功能 通用识别接口&#xff1b;支持中英文等多语言字符混合识别&#xff1b;formdata 格式 PDF 文件流传参&#xf…

MySql执行计划(Explain关键字详解)

文章目录 预备知识学习本内容的前提必须了解1.什么是Explain?2.如何使用Explain?3.explain字段详解3.1、ID字段(情况1)、id值不同:(情况2)、id值相同:(情况3)、id列为null:(情况4)、子查询优化后3.2、select_type字段:表示那个是主要的查询1.simmple:2.primary:3.derived:…

WeStorm(没有指向JVM)

##一直困扰了好久&#xff0c;之前打开IDEA会弹出这个&#xff1a; 然后重启IDEA就没弹出来了。但是的但是&#xff0c;最近打开WebStorm也弹出来这个&#xff0c;重启也解决不了&#xff0c;一开始以为是JDK的问题&#xff0c;但是检查了好几遍&#xff0c;发现都没问题&…

沉浸式体验Stability AI文生图、图生图、图片PS功能(中篇)

今天小李哥就来介绍亚马逊云科技推出的国际前沿人工智能模型平台Amazon Bedrock上的Stability Diffusion模型开发生成式AI图像生成应用&#xff01;本系列共有3篇&#xff0c;在上篇中我们学习了如何在亚马逊云科技控制台上体验该模型的每个特色功能&#xff0c;如文生图、图生…

Vue setup语法糖

未使用setup语法糖 <script lang"ts">export default {name: "App",setup() {let name "张三"let age 20function handleClick() {age 1}return {name,age,handleClick,}}} </script><template><div class"class&…

基于django+vue+uniapp的摄影竞赛小程序

开发语言&#xff1a;Python框架&#xff1a;djangouniappPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员主界面 教师管理 学…

open3d无法读取的obj点云文件处理方案

open3d无法读取的obj点云文件处理方案 open3d读取obj文件什么原因解决方案导入选择点云文件选择 file -> save->保存格式解决 数据下载 open3d读取obj文件 import open3d as o3dif __name__ __main__:# 读取obj文件mesh o3d.io.read_triangle_mesh(r/home/gj/gj/open3…