【Linux系统编程】第二十二弹---操作系统核心概念:进程创建与终止机制详解

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、进程创建

1.1、fork函数重识

1.2、fork函数返回值

1.3、写时拷贝

1.4、fork常规用法

1.5、fork调用失败的原因

 2、进程终止

2.1、进程退出场景

2.2、进程常见退出方法


1、进程创建

1.1、fork函数重识

进程 = 内核的相关管理数据结构(task_struct + mm_struct + 页表) + 数据和代码


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

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1

 进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序:

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

int main( void )
{
  pid_t id;
  printf("Before: pid is %d\n", getpid());
  // id = fork 先创建进程并赋值,返回值为-1则创建失败,直接退出
  if ( (id=fork()) == -1 )perror("fork()"),exit(1);
  printf("After:pid is %d, fork return %d\n", getpid(), id);
  sleep(1);
  return 0;
}

运行结果 

这里看到了三行输出,一行Before,两行After。进程6627(每次打印的pid都不同)先打印Before消息,然后它有打印After。另一个After消息有6628打印的。注意到进程6628没有打印before,为什么呢?如下图所示 

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

1.2、fork函数返回值

  • 子进程返回0。
  • 父进程返回的是子进程的pid。

为什么父进程返回的是子进程的pid,子进程返回的是0?

为了方便父进程对子进程进行标识,进而进行管理。

1.3、写时拷贝


通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

1.4、fork常规用法

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

1.5、fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

 
2、进程终止

进程终止是在做什么?

1、释放曾经的代码和数据所占据的空间。

2、释放内核数据结构。


2.1、进程退出场景

  • 1、代码运行完毕,结果正确(可以通过进程的退出码决定)。
  • 2、代码运行完毕,结果不正确(可以通过进程的退出码决定)。
  • 3、代码异常终止。

异常:

程序运行的时候,崩溃了----操作系统发现你的进程做不了该做的事,OS杀掉了进程。一旦出现异常,退出码没有意义!!!

为什么出现异常?原因是什么?

进程出异常的本质是进程收到操作系统发给进程的信号!

我们可以看进程退出的时候,退出信号是多少,就能判断进程为什么异常了!!!

场景一:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    printf("I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 0;
}

运行结果 

echo $? // 父进程bash获取到的最近一个子进程的退出码 

场景二: 

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    printf("I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
    return 100;// 返回100
}

运行结果 

场景三(main函数内部异常):

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    int* p = NULL;
    printf("I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
    *p = 100;// 对空指针进行解引用
    return 0;
}

 运行结果

 场景四(使用信号命令):

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    while(1)
    {
      printf("I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
    return 0;
}

运行结果 

 从场景三和场景四看到,都是报的段错误,证明两个的退出信号是一样的。

退出码所代表的意思是什么呢?通过一个程序来看看。

 代码演示

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
int main()
{
    for(int i=0;i<255;i++)
    {
        printf("%d :%s\n",i,strerror(i));// 打印退出码代表的意思
    }
    return 100;// 返回100
}

运行结果 

除了系统的退出码之外,我们能不能自己编写一套退出码呢???

 答案是可以的,下面我们通过一个除法函数来实现自己的退出码并打印错误信息。

代码演示一

#include<stdio.h>

int Div(int x,int y)
{
  if(y==0)
  {
   return -1;
  }
  else 
  {
    return x/y;
  }
}
int main()
{
  int result = Div(10,100);
  printf("result:%d [%s]\n",result);
  result = Div(10,0);
  printf("result:%d [%s]\n",result);
  return 0;
}

运行结果 

从运行结果我们可以看到0一定是正常的运行结果,但是-1不能确定是正常运行,还是y等于0的时候的结果,为了知道是什么原因,我们可以自主编写一个错误码,并将错误码打印出来,具体代码如下:

优化

#include<stdio.h>

// 枚举常量
enum 
{
  Sucess = 0,
  Div_zero,
  Mod_zero,
};
int exit_code = Sucess;

const char* CodeToErrString(int code)
{
  switch(code)
  {
    case Sucess:
      return "Sucess";
    case Div_zero:
      return "Div_zero";
    case Mod_zero:
      return "Mod_zero";
    default:
      return "Unkown error";
  }
}
int Div(int x,int y)
{
  if(y==0)
  {
   // y等于0则设置退出码
   exit_code = Div_zero;
   return -1;
  }
  else 
  {
    return x/y;
  }
}
int main()
{
  int result = Div(10,100);
  printf("result:%d [%s]\n",result,CodeToErrString(exit_code));
  result = Div(10,0);
  printf("result:%d [%s]\n",result,CodeToErrString(exit_code));
  return 0;
}

运行结果 

 通过运行结果我们可以清楚看到-1是除0错误

结论:

衡量一个进程退出,我们只需要看两个数字:退出码和退出信号。

  • 1、先确认是否异常。
  • 2、不是异常,就一定是代码跑完了,看退出码即可。 


2.2、进程常见退出方法


正常终止(可以通过 echo $? 查看进程退出码):

  • 1. 从main返回
  • 2. 调用exit
  • 3. 调用_exit

异常退出:

  • ctrl + c,信号终止

_exit函数 

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值

代码演示一

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

int main()
{
  printf("hello linux\n");
  _exit(-1);// 退出码为-1
  return 0;
}

运行结果 

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。 

代码演示二

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

void func()
{
  _exit(12);// 不在main函数内部,照样会结束程序
}
int main()
{
  printf("hello linux\n");
  func();
  return 0;
}

运行结果 

_exit函数可以从任意函数中结束程序。 

exit函数

#include <unistd.h>
void exit(int status);

_exit函数前面的两个演示与exit函数的效果是一样的,此处我们看看两者的区别。 

代码演示 

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

int main()
{
  printf("hello linux");
  sleep(2);
  exit(2);
  // _exit(2);
  return 0;
}

运行结果 

从上面的代码我们可以看到,exit函数会重刷缓冲区,_exit不会重刷缓冲区,具体细节如下图: 

exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:

  • 1. 执行用户通过 atexit或on_exit定义的清理函数。
  • 2. 关闭所有打开的流,所有的缓存数据均被写入。
  • 3. 调用_exit。

return退出


return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。 

代码演示

#include<stdio.h>

int main()
{
  printf("hello linux\n");
  return 1;
}

运行结果 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图像压缩编码(4)--H.26x系列视频压缩编码_2

目录 H.261 视频编码标准 H.261的编码与解码 1&#xff09; 帧内/帧间编码 2&#xff09;运动补偿 3&#xff09;量化 4&#xff09;环路滤波器 5&#xff09;缓存器 压缩数据的分层 数据复用结构 H.264的编码与解码 H.261 视频编码标准 实际应用时&#xff0c;要求有…

【C++】list详解及模拟实现

目录 1. list介绍 2. list使用 2.1 修改相关 2.2 遍历 2.3 构造 2.4 迭代器 2.5 容量相关 2.6 元素访问 2.7 操作相关 3. 模拟实现 3.1 节点类 3.1.1 初始结构 3.1.2 节点的构造函数 3.2 迭代器类 3.2.1 初始结构 3.2.2 迭代器 3.2.3 迭代器-- 3.2.4 解引…

基于VUE的医院抗生素使用审核流程信息化管理系统

开发背景 随着医疗行业的快速发展和信息技术的不断进步&#xff0c;医院内部管理系统的信息化建设变得尤为重要。抗生素作为治疗感染性疾病的重要药物&#xff0c;在临床使用过程中需要严格控制以避免滥用导致的耐药性问题。传统的抗生素使用审核流程往往依赖于人工审核&#x…

第十一章 从0-1搭建一个简单的JavaWeb系统(三)

目录 一、工程代码结构 二、代码实现 三、运行效果 四、未完待续 本章节的每一段代码&#xff0c;建议全部自己敲一遍&#xff0c;加深印象&#xff0c;切勿直接复制黏贴。 一、工程代码结构 本章节实现注销&#xff08;退出&#xff09;功能&#xff0c;以下图片中标红的…

苹果CMS插件:优化蜘蛛访问内容,提升百度收录率

确保蜘蛛抓取原始内容 专为苹果CMS设计的广告管理插件&#xff0c;能够智能识别搜索引擎蜘蛛与普通访客&#xff0c;确保蜘蛛访问时展示原始内容&#xff0c;从而提升被百度等搜索引擎收录的几率。 广告显示提升收益 对于普通访客&#xff0c;该插件则优先显示广告内容&#…

【网络】高级IO——select版本TCP服务器

目录 前言 一&#xff0c;select函数 1.1.参数一&#xff1a;nfds 1.2.参数二&#xff1a; readfds, writefds, exceptfds 1.2.1.fd_set类型和相关操作宏 1.2.2.readfds, writefds, exceptfds 1.2.3.怎么理解 readfds, writefds, exceptfds是输入输出型参数 1.3.参数三…

面试速通宝典——1

1. 内存有哪几种类型&#xff1f; ‌‌‌‌  内存分为五个区&#xff0c;堆&#xff08;malloc&#xff09;、栈&#xff08;如局部变量、函数参数&#xff09;、程序代码区&#xff08;存放二进制代码&#xff09;、全局/静态存储区&#xff08;全局变量、static变量&#…

2024-1.2.12-Android-Studio配置

本地博客: https://k1t0111.github.io/ K1T0 最近在做一些app方向的移动技术开发学习&#xff0c;但是由于AS的配置问题&#xff0c;市面上找不到最新的2024版本的AS的相关配置。笔者也是踩了很多坑&#xff0c;因此想写一篇文章记录一下最新的AS 2024 1.2.12的对应java环境的一…

springboot框架VUE3学院网站系统开发mysql数据库设计java编程计算机网页源码maven项目

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

python 识别省市、区县并组建三级信息数据库

一、网址&#xff1a; 全国行政区划信息查询平台 二、分析并搭建框架 检查网页源码&#xff1a; 检查网页源码可以发现&#xff1a; 所有省级信息全部在javaScript下的json中&#xff0c;会在页面加载时加载json数据&#xff0c;填充到页面的option中。 1、第一步&#xff1a…

探秘 Web Bluetooth API:连接蓝牙设备的新利器

引言 随着物联网技术的快速发展&#xff0c;蓝牙设备在日常生活中扮演着越来越重要的角色。而在 Web 开发领域&#xff0c;Web Bluetooth API 的出现为我们提供了一种全新的方式来连接和控制蓝牙设备。本文将深入探讨 Web Bluetooth API 的使用方法和原理&#xff0c;帮助开发…

浅显易懂的Git教程

Git概述 SVN与Git的对比 SVN&#xff08;Subversion&#xff09; 类型&#xff1a;集中式版本控制系统 工作流程&#xff1a; 从中央服务器下载最新版本到本地。在本地进行开发。提交更改回中央服务器。 优点&#xff1a; 简单易用&#xff0c;适合小型团队。版本历史清…

vs2022快捷键异常不起作用解决办法

安装了新版本的vs2022&#xff0c;安装成功后&#xff0c;发现快捷键发生异常&#xff0c;之前常用的快捷键要么发生改变&#xff0c;要么无法使用&#xff0c;比如原来注释代码的快捷键是ctrlec&#xff0c;最新安装版本变成了ctrlkc&#xff0c;以前编译代码的快捷键是F6或者…

初始MYSQL数据库(6)—— 事务

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; MYSQL 目录 事务的概念 事务的ACID特性 使用事务 查看支持事务的存储引擎 事务的语法 保存点 自动/手动提交事务 事务的隔离性和…

Python模拟鼠标轨迹[Python]

一.鼠标轨迹模拟简介 传统的鼠标轨迹模拟依赖于简单的数学模型&#xff0c;如直线或曲线路径。然而&#xff0c;这种方法难以捕捉到人类操作的复杂性和多样性。AI大模型的出现&#xff0c;能够通过深度学习技术&#xff0c;学习并模拟更自然的鼠标移动行为。 二.鼠标轨迹算法实…

【原创】java+swing+mysql仓库管理系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 文末有本人名片&#xff0c;希望和大家…

Qt开发技巧(四)“tr“使用,时间类使用,Qt容器取值,类对象的删除,QPainter画家类,QString的转换,用好 QVariant类型

继续讲一些Qt技巧操作 1.非必要不用"tr" 如果程序运行场景确定是某一固定语言&#xff0c;就不需要用tr,"tr"之主要针对多语种翻译的&#xff0c;因为tr的本意是包含英文&#xff0c;然后翻译到其他语言比如中文&#xff0c;不要滥用tr&#xff0c;如果没有…

‌内网穿透技术‌总结

内网穿透是一种网络技术&#xff0c;通过它可以使外部网络用户访问内部网络中的设备和服务。一般情况下&#xff0c;内网是无法直接访问的&#xff0c;因为它位于一个封闭的局域网中&#xff0c;无法从外部访问。而通过内网穿透&#xff0c;可以将内部网络中的设备和服务暴露在…

底盘四轮转向运动学解析(含代码)

目录 写在前面的话四轮转向运动学解析四轮转向理论图解robot_control.py 完整代码关键参数完整代码 公式解析&#xff08;根据代码&#xff09;反相--模式1详细图解 正相--模式2轴心--模式3 写在前面的话 网上找了很多资料&#xff0c;对于四轮转向运动学描述的很少&#xff0…

爬虫过程 | 蜘蛛程序爬取数据流程(初学者适用)

蜘蛛程序&#xff08;也称网络爬虫&#xff0c;是搜索引擎的重要组成部分&#xff09; 主要功能&#xff1a;遍历互联网&#xff0c;抓取网站信息并建立索引&#xff0c;便于用户在搜索引擎中检索到最新的网页内容工作原理&#xff1a;从初始网站页面的URL开始&#xff0c;发送…