进程启动和进程终止

news2024/11/15 1:34:41

文章目录

  • 进程
    • 程序
    • 进程
    • 进程ID
    • 进程表项
    • C程序的启动过程
      • 启动例程
      • 进程终止
      • atexit函数
        • 示例--终止函数的执行流程以及多种进程终止方式的对比
      • 进程启动和退出流程图
      • 查看系统中的进程

进程

程序

  • 程序是存放在磁盘文件中的可执行文件。当代码进行编辑保存后使用gcc等编译工具进行编译链接最后生成的可执行文件就是程序。

    image-20240827111700110

进程

  • 程序的运行实例叫做进程(当程序运行起来就变成了进程)

    image-20240827111927095

    可以使用指令ps来查看此时正在运行的hello进程

    ps -elf | grep hello | grep -v grep 
    
    #ps -elf: 列出系统中所有进程的详细信息。其中,-e表示显示所有进程,-l表示长格式输出,-f表示显示完整格式。
    #grep hello: 从上一步的输出中筛选出包含"hello"字符串的行。
    #grep -v grep: 从第二步的输出中排除包含"grep"字符串的行,以避免将grep命令本身也作为结果输出。
    

    image-20240827112118858

  • 进程具有独立的权限和职责。如果系统中的某个进程崩溃,它不会影响到其余的进程。这是因为系统会给每一个进程分配一个虚拟内存,当一个进程崩溃并不会影响到别的进程的内存空间。

  • 每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯(进程间通信IPC)

进程ID

  • 每个Linux进程都有一个唯一的数字标识符,称为进程ID,进程ID一定是非负整数。

    在Linux中可以使用指令ps指令来查看此时后台的所有进程

    ps -elf | more
    
    #ps 是一个用于报告当前系统进程状态的命令。
    #-elf 是 ps 命令的选项组合,其中:
    #-e 表示显示所有进程,包括其他用户的进程。
    #-l 表示使用长格式输出,显示更详细的信息。
    #-f 表示显示完整格式,包括进程的UID、PID、PPID、C、STIME、TTY、TIME、CMD等详细信息。
    # | 是管道符,用于将前一个命令的输出作为后一个命令的输入。
    # more 是一个用于分页显示文本内容的命令,当输出内容较多时,可以使用它来逐页查看。
    

    image-20240827113548989

    这里的PID就是进程的标识符,而PPID指的就是父进程的进程标识符,即创建这个进程的进程。进程标识符为1的是init进程,它是由0号进程idleswapper创建的。0号进程在系统启动时由内核自动创建,它是唯一一个没有通过fork()kernel_thread()产生的进程。所以说0号进程是所有进程的祖先。

进程表项

当一个程序运行起来后,系统会给它分配一片虚拟内存空间,这片内存空间可能远远大于实际的物理内存。在虚拟内存中,用户空间占用大部分内存,这片空间存放着进程的代码段、数据段和堆栈段等等;还有一部分是内核空间,当一个进程运行的时候会在内核中生成一个进程表项用来存储这个进程的信息。例如进程操作的文件的信息、信号、还有和物理内存映射等相关信息。

在Linux系统中可以查询这个进程表项的相关信息:

首先跳转到/usr/src/linux-headers-6.5.0-41-generic/include/linux这个目录下,然后使用grep "struct task_struct {" * -nir查找在哪里出现了这个字段,然后打开对应的头文件查看

image-20240828212926477

有关内存的结构体:image-20240828214453838

进程ID:image-20240828214545884

有关信号的结构体:image-20240828214802522

C程序的启动过程

前边讲过C程序编程可执行程序需要经过:预处理、编译、汇编、链接四个步骤,在这之后就会生成一个可执行文件C程序的启动过程。当去运行可执行文件的时候,总是从主函数开始。其实在主函数之前内核还会启动一个特殊例程就是启动例程

启动例程

启动例程在系统中其实已经编译好了,通常放在/lib/lib32或/lib/lib64目录下。编译器在编译时会将启动例程编译到可执行文件中去。

启动例程的作用

  • 搜集命令行的参数传递给main函数中的argcargv

    在命令行中通常会输入以空格隔开的若干个字符串,启动例程将这些字符串的个数和内容传给main函数中的argcargv

  • 搜集环境信息构建环境表并传递给main函数

    将环境变量$PATH等信息构建成一个环境表传给main函数

  • 登记进程的终止函数

    在每个进程结束之前都会去执行一个默认的终止函数,例如C程序在return之前就会去执行一个终止函数。终止函数主要是负责资源的释放,例如文件描述符的关闭、缓存的强制清空等。系统默认有一个终止函数,但是用户也同样能够自己编写一个终止函数,然后向内核去注册这个终止函数,在程序结束之前由用户去自定义释放哪些资源。

进程终止

  • 正常终止

    • 从main函数中返回 (return 0)
    • 调用标准C库函数 (exit(0))
    • 调用系统调用函数_exit_Exit
    • 最后一个线程从启动例程中返回
    • 最后一个线程调用pthread_exit(最后一个线程调用这个意味着所有的工作执行完毕)
  • 异常终止

    • 调用abort

      在C语言中有一个断言函数assert,它的原理就是当不满足条件时,通过调用abort()函数来终止程序的运行,实际上abort函数会向自身发送SIGABRT信号,触发默认的SIGABRT信号处理程序

    • 接收到一个信号并终止

      在实际开发中经常会遇到死循环的情况,这时候需要按下Ctrl+CCtrl+Z来结束程序的运行,实际上这两个操作是发送了一个信号来终止程序的运行。分别是SIGINTSIGSTP信号。

    • 最后一个线程对取消请求做处理响应

  • 进程返回

    • 通常程序运行成功返回0,否则返回非0;

      在实际开发中,如果成功执行的话,return的返回值是0反之是非0,exit系统调用函数的传参也是一样。

    • shell中可以查看进程的返回值:echo $?

默认每个进程终止的时候都会去调用进程终止函数,实际上可以用户可以自己定义终止函数,由用户自己去指定要释放的资源,然后通过向内核注册用户自己编写的函数就可以了。然后并不是每一种终止方式都会去调用终止函数,下边用具体的实例来演示哪些终止方式在调用的时候会去调用终止函数。

atexit函数

#include <stdlib.h>

int atexit(void (*function)(void));

//功能:允许用户注册一个函数,在main函数终止时自动被调用
//参数:一个函数指针即函数名
//返回值:如果成功执行返回0,否则返回非0

注意事项

  • 可以使用多次atexit函数来注册多个退出处理函数,这些函数按照它们被注册的相反顺序被调用。即最后注册的函数将先被执行(执行顺序原理类似于栈)
  • 如果atexit函数注册失败,它会返回非零值。这种情况一般发生在注册了太多的退出处理函数
示例–终止函数的执行流程以及多种进程终止方式的对比
#include "header.h"

void exit_handler1(void)
{
	printf("first term func1\n");
}

void exit_handler2(void)
{
	printf("second term func2\n");
}

void exit_handler3(void)
{
	printf("third term func3\n");
}

int main(int argc, char **argv)
{
	if(argc < 3)
	{
		fprintf(stderr,"usage: %s [filepath] [exit_type:exit|_exit|return]\n",argv[0]);
		exit(EXIT_FAILURE);
	}

	//向内核登记终止函数
	if(atexit(exit_handler1) != 0)
	{
		perror("atexit");
		exit(EXIT_FAILURE);
	}

	if(atexit(exit_handler2) != 0)
	{
		perror("atexit");
		exit(EXIT_FAILURE);
	}

	if(atexit(exit_handler3) != 0)
	{
		perror("atexit");
		exit(EXIT_FAILURE);
	}
	
	FILE *fp = fopen(argv[1],"w");
	if(fp == NULL)
	{
		perror("fopen");
		exit(EXIT_FAILURE);
	}

	fprintf(fp, "hello world");
	
	if(!strcmp(argv[2],"return"))
	{
		return 0;
	}
	else if(!strcmp(argv[2],"_exit"))
	{
		_exit(EXIT_SUCCESS);
	}
	else if(!strcmp(argv[2],"exit"))
	{
		exit(EXIT_FAILURE);
	}
	else
	{
		fprintf(stderr,"usage: %s [filepath] [exit_type:exit|_exit|return]\n",argv[0]);
		exit(EXIT_FAILURE);
	}
}

image-20240903104328682image-20240903104430466image-20240903104514721

通过执行结果可以发现,在通过exitreturn作为进程的终止方式时,程序中向内核登记的终止函数都会被执行。而且它们的执行顺序以栈的方式进行执行,先登记的后执行,后登记的先执行。并且也成功地创建了文件,并向文件中写入了数据。而_exit这个函数在它是一个系统调用函数,这里调用_exit函数并没有去调用进程终止函数,并且也没有将缓存里的数据清空,写入到文件里。这一点需要注意,以后在使用进程终止方式的时候要选择恰当的终止方式,要不然它的结果可能和预期的结果不一样。

在代码中,向文件写入数据的方式属于全缓存,全缓存将数据写入到文件里有三种情况:1.当全缓存满的时候会将数据全部写入到文件里;2.当使用fflush强制清空缓存也会将数据写入到文件里;3.当使用fclose函数关闭文件的时候也会将缓存里的内容写入到文件里。(具体有关缓存的知识可以查看:标准C的IO缓存类型)这里通过exitreturn作为进程的终止方式时,标准I/O库会检查所有打开的文件描述符,并自动将缓存区的数据写入到文件中去。也就是说fprintf函数并不是直接将数据写入到文件里,而是先写入到缓存里,最后当使用fclose函数关闭文件指针的时候将缓存的内容写入到文件里。而当选择_exit作为进程的终止方式时,由于_exit函数是一个底层的系统调用,它直接终止进程而不会执行一些高级的终止处理,包括调用注册的终止处理函数和清空缓存写入文件。

进程启动和退出流程图

image-20240903112212674

如图所示:介绍一下进程的启动和退出的流程

  1. 首先在main函数调用之前,内核会调用一个启动例程,也就是这里的C start template来负责将从命令行传入的参数的个数和参数的内容进行收集然后传给main函数中的argc argv;然后将环境变量$PATH $SHELL等构建成一个环境表,这个也会传给main函数;然后会向内核登记终止函数,例如程序里的atexit登记的三个进程终止函数。最后它会去调用main函数。
  2. main函数又会去调用其他的函数,在这个过程中,main函数和其他被调用的函数可以调用系统调用函数_exit或_Exit来终止进程的运行,由于_exit_Exit都是系统调用,所以它并不会执行一些高级的处理。
  3. 如果main函数和其他被调用的函数执行了exit标准C库函数,那么它会执行一些高级操作例如以栈的运行方式去调用之前向内核登记的终止函数和清空缓存将数据写入到文件等操作。exit函数在底层仍然是通过调用_exit或_Exit来实现进程的退出。

查看系统中的进程

使用ps指令可以查看当前系统中的一些进程的信息,后边加不同的参数选项能够显示不同的信息。

使用ps -ef | more来查看当前系统中的信息

image-20240903153448537

这里的UID指的是当前运行这个进程的用户的名字,例如这里的root超级用户或者后边的普通用户;这里的PID是进程的唯一标识符,简称进程编号;PPID指的是它的父进程;STIME指的是它启动的时间;TTY指的是它运行的终端;TIME指的是运行它这个进程所花费的时间;CMD指的是此进程运行的指令。

使用ps -aux|more来查看进程占据的资源信息

image-20240903154219924

这里的USER就是进程的属主,也就是运行这个的用户;%CPU就是它占据CPU的百分比,%MEM是占据内存的百分比,STAT就是当前进程的状态信息。

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

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

相关文章

【机器学习】OpenCV入门与基础知识

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 OpenCV入门与基础知识简介安装与环境配置WindowsLinuxmacOS 核心数据结构MatSca…

Linux:进程状态和优先级

一、进程状态 1.1 操作系统学科&#xff08;运行、阻塞、挂起&#xff09; 为了弄明白正在运行的进程是什么意思&#xff0c;我们需要知道进程的不同状态 大多数操作系统都遵循以下原则 1.1.1 运行状态 因为有一个调度器需要确保CPU的资源被合理使用&#xff0c;所以需要维护…

【C++】学完c语言后的c++基础知识补充!(命名空间、输入和输出、缺省函数、函数重载、引用、内联函数代替宏、nullptr代替NULL)

一. 命名空间 1. 定义 出现的意义&#xff1a;解决各种函数、关键词和类的名称冲突问题。 定义方式&#xff1a;namespace 命名空间的名字 { } &#xff08;注意&#xff01;}后面不加&#xff1b;&#xff09; namespace 是关键词命名空间的…

CenterNet官方代码—目标检测模型推理部分解析与项目启动

CenterNet模型推理部分解析 CenterNet官方代码环境部署 CenterNet作为2019年CVPR推出的论文&#xff0c;论文中给出了官方代码所在的github仓库地址。https://github.com/xingyizhou/CenterNet。 整个代码的代码量并不是特别大&#xff0c;但整个项目的难点在于使用了老版本的…

横向移动-WMI

什么是WMI? WMI是基于 Web 的企业管理 (WBEM) 的 Windows 实现&#xff0c;WBEM 是跨设备访问管理信息的企业标准。 WBEM&#xff08;Web-Based Enterprise Management&#xff09;是一个开放标准&#xff0c;用于跨平台和跨设备的管理信息访问。WMI&#xff08;Windows Mana…

VMware Fusion虚拟机Mac版 安装Win10系统教程

Mac分享吧 文章目录 Win10安装完成&#xff0c;软件打开效果一、VMware安装Windows10虚拟机1️⃣&#xff1a;准备镜像2️⃣&#xff1a;创建虚拟机3️⃣&#xff1a;虚拟机设置4️⃣&#xff1a;安装虚拟机&#xff08;步骤和Win11安装步骤类似&#xff0c;此处相同步骤处没换…

C++从入门到起飞之——继承下篇(万字详解) 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、派⽣类的默认成员函数 1.1 四个常⻅默认成员函数 1.2 实现⼀个不能被继承的类 ​编辑 2. 继承与友…

词嵌入(二):基于上下文窗口的静态词嵌入(从NNLM、CW模型谈到基于层次Softmax、负采样的Word2Vec模型)

文章目录 一、经典神经语言模型&#xff08;A Neural Probabilistic Language Model&#xff09;二、C&W模型 (Collobert and Weston, 2008)2.1 文章背景2.2 模型架构&#xff08;词向量的表示&#xff09;2.2.1 Lookup-Table Layer&#xff08;查找表&#xff09;2.2.2 TD…

STM32关于keil使用过程中遇到的问题

1.设备管理器STlink驱动确认安装完成&#xff0c;但是keil里一直识别不到&#xff0c;换下载器也没用 &#xff08;1&#xff09;问题描述 我的问题是这样产生的&#xff1a;之前用标准库开发STM32的时候&#xff0c;STLink能够正常使用&#xff0c;然后使用HAL库开发的时候出…

仓储管理系统的设计与实现SSM框架

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

electron react离线使用monaco-editor

目录 1.搭建一个 electron-vite 项目 2.安装monaco-editor/react和monaco-editor 3.引入并做monaco-editor离线配置 4.react中使用 5.完整代码示例 6.monaco-editor离线配置官方说明 7.测试 1.搭建一个 electron-vite 项目 pnpm create quick-start/electron 参考链接…

React学习day06-异步操作、ReactRouter的概念及简单使用

13、续 &#xff08;8&#xff09;异步状态操作 1&#xff09;在子仓库中 ①创建仓库 ②解构需要的方法 ③安装axios ④封装并导出请求 ⑤在reducer中为newsList赋值 ⑥获取并导出reducer函数 2&#xff09;在入口文件index.js中&#xff0c;注入 3&#xff09;在App.js中&a…

Vue.js入门系列(二十九):深入理解编程式路由导航、路由组件缓存与路由守卫

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

爬虫--翻页tips

免责声明&#xff1a;本文仅做分享&#xff01; 伪线程 from DrissionPage import ChromiumPage import timepage ChromiumPage() page.get("https://you.ctrip.com/sight/taian746.html") # 初始化 第0页 index_page 0# 翻页点击函数 sleep def page_turn():page…

【Linux修行路】网络套接字编程——UDP

目录 ⛳️推荐 前言 六、Udp Server 端代码 6.1 socket——创建套接字 6.2 bind——将套接字与一个 IP 和端口号进行绑定 6.3 recvfrom——从服务器的套接字里读取数据 6.4 sendto——向指定套接字中发送数据 6.5 绑定 ip 和端口号时的注意事项 6.5.1 云服务器禁止直接…

C++复习day12

IO流 一、C语言的输入和输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键 盘)读取数据&#xff0c;并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。 注意宽度输出和精度输出控制。C语言借助了…

【C++】多态and多态原理

目录 一、多态的概念 二、多态的定义及实现 &#x1f31f;多态的构成条件 &#x1f31f;虚函数 &#x1f31f;虚函数的重写 &#x1f320;小贴士&#xff1a; &#x1f31f;C11 override 和 final &#x1f31f;重载、重写&#xff08;覆盖&#xff09;、重定义&#xf…

POD内的容器之间的资源共享

概述 摘要&#xff1a;本文通过实践描述并验证了pod内容器如何实现网络、文件、PID、UTC、mount的共享。 pod实战之容器内资源共享与隔离 container容器之间的共享实战 从实际场景说起&#xff1a;有2个容器nginx与wordpress分别运行了紧密耦合且需要共享资源的应用程序。我…

英语学习交流平台|基于java的英语学习交流平台系统小程序(源码+数据库+文档)

英语学习交流平台系统小程序 目录 基于java的英语学习交流平台系统小程序 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&…

基于SpringBoot的校园社团活动管理系统设计与实现

文未可获取一份本项目的java源码和数据库参考。 一、设计&#xff08;论文&#xff09;研究背景与意义 在当今的社会&#xff0c;可以说是信息技术的发展时代&#xff0c;在社会的方方面面无不涉及到各种信息的处理。[1]信息是人们对客观世界的具体描述&#xff0c;是人们进行…