[Linux]进程控制

news2024/11/16 23:50:05

🥁作者华丞臧.
📕​​​​专栏:【LINUX】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 一、进程创建
    • 1.1 fork函数
    • 1.2 写时拷贝
    • 1.3 补充
  • 二、进程终止
    • 2.1 进程退出场景
    • 2.2 进程常见退出方法
    • 2.3 _exit && exit
  • 三、进程等待
    • 3.1 进程等待必要性
    • 3.2 进程等待方法
      • 3.2.1 wait()系统调用
      • 3.2.2 waitpid()系统调用
      • 3.2.3 获取子进程status
        • 正常终止
        • 被信号所杀
    • 3.3 进程等待的方式


一、进程创建

1.1 fork函数

在Linux中fork函数是非常重要的函数,它从已存在进程中创建一个新的进程。新进程为子进程,而原进程为父进程。
在这里插入图片描述

#include <unistd.h>

pid_t fork();

//返回值:父进程返回子进程pid,子进程返回0

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

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

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

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

int main( void )
{
 	printf("Before: pid is %d\n", getpid());
 	 pid_t id = fork();
	if ( (id == -1)
		perror("fork()"),exit(1);
 	printf("After:pid is %d, fork return %d\n", 	getpid(), pid);
 	sleep(1);
 	return 0;
} 

在这里插入图片描述
这里看到了三行输出,一行before,两行after。进程19913先打印before消息,然后它有打印after。另一个after
消息有19914打印的。注意到进程19914没有打印before,为什么呢?如下图所示:
在这里插入图片描述
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

fork函数返回值

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

fork常规用法

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

fork调用失败的原因

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

1.2 写时拷贝

通常(一般情况),fork之后所有代码父子进程共享;此时父子进程不写入,数据也是共享的;当任意一方试图写入,这一方便以写时拷贝的方式拷贝一份。如下图:

在这里插入图片描述

为什么要写时拷贝?

  1. 父进程的数据,子进程不一定全用,即便会使用也不一定全部写入,因此可能会浪费内存;
  2. 最理想的情况,可以对会被父子进程修改的数据,进行分离拷贝,不需要修改的共享即可;但是从技术角度实现很复杂;
  3. fork的时候,对所有数据都进行拷贝会增加fork的成本(内存和时间上);
  4. 写时拷贝采用延迟拷贝策略,只有真正使用的时候才会分配给进程,变相提高了内存的使用率。

1.3 补充

CPU当中有一个eip程序计数器(寄存器),eip程序计数器会拷贝给子进程,子进程便从eip所指向的代码出开始执行。

eip程序计数器:保存当前正在执行指令的下一条指令。

为了保证进程的独立性,fork之后,操作系统创建子进程的内核数据结构(task_struct、mm_struct、页表),让子进程继承父进程的代码,通过写时拷贝的方式来进行数据的共享或者独立。

二、进程终止

2.1 进程退出场景

一个进程退出,一定是以下几种情况:

  • 代码运行完毕,结果正确;
  • 代码运行完毕,结果不正确;
  • 代码异常终止。

2.2 进程常见退出方法

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

  1. 从main函数返回;
  2. 调用exit;
  3. _exit(系统调用接口);

异常退出:

  • ctrl+c,信号终止。

2.3 _exit && exit

#include <unistd.h>

void _exit(int status);

参数:status 定义子进程的终止状态,父进程通过wait来获取该值
  • 说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值
    是255。
#include <unistd.h>

void exit(int status);

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

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

在这里插入图片描述

exit和_exit的区别:

exit不仅仅会调用_exit,还会做其它的工作,使用exit函数终止进程会刷新缓冲区;
_exit是系统调用接口,使用_exit系统调用接口不会刷新缓冲区。

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

int main()
{
    printf("hello Linux");
    sleep(3);

    exit(1);
    //_exit(1);
}

在这里插入图片描述

在这里插入图片描述

return退出

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

三、进程等待

3.1 进程等待必要性

  • 子进程退出,退出状态不被回收就会造成僵尸进程的问题,进而造成内存泄漏;
  • 进程一旦进入僵尸进程,就无法被杀死了,因为谁也没办法杀死一个已经死去的进程;
  • 父进程派给子进程的任务完成的如何,父进程需要知道;父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

进程等待的目的:获取子进程的退出状态

3.2 进程等待方法

3.2.1 wait()系统调用

wait()可以等待任意的一个退出的子进程,并回收该子进程,让子进程从Z状态变为X状态。

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

pid_t wait(int*status);

返回值
成功返回被等待进程pid,失败返回-1。

参数
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。

//wait.cpp
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

//using namespace std;

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        //child
        int cnt = 3;
        while(cnt--)
        {
            printf("这是子进程,pid:%d,ppid:%d,id:%d\n", getpid(), getppid(), id);
            sleep(1);
        }

        printf("子进程结束啦,父进程准备回收子进程!!!\n");
        return -1;
    }
    else if(id > 0)
    {
        //parent
        printf("这是父进程,pid:%d,ppid:%d,id:%d\n", getpid(), getppid(), id);
        sleep(5);
        
        int ret = wait(NULL);   //父进程会停在这里等待读取子进程退出信息
        printf("回收子进程成功,子进程状态status:%d\n", ret); //成功则返回子进程pid,失败返回-1
        sleep(3);
    }

    return 0;
}

在这里插入图片描述
wait()的方案可以解决子进程Z状态,回收其资源,让子进程进入X状态,不过wait()功能较为简单不常用。

3.2.2 waitpid()系统调用

waitpid()可以等待特定的一个子进程的退出,并回收该子进程,让该进程从Z状态变为X状态。

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

  1. pid

    • pid < 0:等待任一个子进程。与wait等效。
    • pid>0:等待其进程ID与pid相等的子进程。
  2. status

    • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  3. options

    • WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
    • 0:阻塞等待。
//waitpid.cpp
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

//using namespace std;

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        //child
        int cnt = 3;
        while(cnt--)
        {
            printf("这是子进程,pid:%d,ppid:%d,id:%d\n", getpid(), getppid(), id);
            sleep(1);
        }

        printf("子进程结束啦,父进程准备回收子进程!!!\n");
        return 1;
    }
    else if(id > 0)
    {
        //parent
        int status = 0;
        printf("这是父进程,pid:%d,ppid:%d,id:%d\n", getpid(), getppid(), id);
        sleep(10);

        pid_t ret = waitpid(id, &status, 0);   //父进程会停在这里等待读取子进程退出信息
        printf("等待子进程成功, ret:%d, 子进程退出码为:%d\n", ret, (status >> 8) & 0xFF); //子进程正常运行结束 
        //printf("子进程终止信号为:%d\n", status & 0x7f);    //子进程异常退出获取终止信号的方法
        sleep(3);
    }

    return 0;
}

在这里插入图片描述

总结

  1. 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息;
  2. 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞;
  3. 如果不存在该子进程,则立即出错返回。

3.2.3 获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
位):

在这里插入图片描述

正常终止

低八位为0,次低八位就是进程的退出状态,获取方法如下:

int status = 0;

pid_t ret = waitpid(id, &status, 0);

//status右移八位,再与上0xFF,即可获取其次低八位
printf("次低八位为:%d\n", (status >> 8) & 0xFF);

其中:

  • id:子进程pid。
  • status:输出型参数。
  • 0:表示waitpid()等待方式为阻塞等待。

被信号所杀

进程被信号所杀,输出的status参数低七位作为终止信号,第八位是core dump标志。

Linux中的信号我们已经见过了,如:

kill -9 [进程pid]

使用 kill 指令可以查看Linux下的所有信号,如下图:
在这里插入图片描述
一个进程正常运行结束是不会被信号杀死的;当一个进程运行出错时,操作系统会提供一个信号将进程杀死,当然用户也可以主动给进程一个信号将进程杀死。

进程被信号所杀死时,就说明该进程运行中出现异常,此时进程并没有运行到结束因此退出码没有意义;进程异常退出也是处于Z状态,父进程需要知道子进程为什么出现异常也需要获取子进程的终止状态。

int status = 0;

pid_t ret = waitpid(id, &status, 0);

printf("子进程终止信号为:%d\n", status & 0x7f);

在这里插入图片描述

3.3 进程等待的方式

进程等待分为两种方式:

  1. 阻塞等待:如果子进程没有退出,父进程一直在等待;
  2. 非阻塞等待:如果子进程没有退出,父进程每过一段时间查看一次子进程是否退出。多次调用非阻塞接口,这个过程称为轮询检测

如何理解父进程阻塞?

父进程的task_struct从运行队列放入到等待队列中,等待子进程退出。

进程等待的方式主要是在waitpid中使用:

pid_t waitpid(pid_t id, int *status, int options);

其中options就是函数中可以用来设置等待方式的参数,options主要有以下两个常用的值:

  • WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的pid。
  • 0:若pid指定的子进程没有退出,则父进程停止在waitpid()函数处等待子进程退出;若子进程退出,则返回子进程pid。

WHOHANG的应用

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <vector>

typedef void (*handle_t)(); //函数指针类型重命名为 handle_t

//方法集
std::vector<handle_t> handles;

void fun1()
{
    printf("我是fun1\n");
}

void fun2()
{
    printf("我是fun2\n");
}

void Load()
{
    //加载方法
    handles.push_back(fun1);
    handles.push_back(fun2);
}

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        //child
        while(1)
        {
            printf("这是子进程,pid:%d,ppid:%d,id:%d\n", getpid(), getppid(), id);
            sleep(2);
        }
        
        return 1;
    }
    else if(id > 0)
    {
        //parent
        int status = 0;
         
        while(1)
        {
        	//多次调用非阻塞接口读取子进程退出信息
            pid_t ret = waitpid(id, &status, WNOHANG);   
            if (ret > 0)
            {
                //等待成功,子进程退出
                printf("等待子进程成功, ret:%d, 子进程退出码为:%d,子进程终止信号为:%d\n", ret, (status >> 8) & 0xFF, status & 0x7f); 
                break;
            }
            else if (ret == 0)
            {
                //等待成功,子进程没有退出
                if(handles.empty())
                {
                    Load();
                }
            
                for(auto f : handles)
                {
                    f(); //回调处理对应的任务
                    sleep(1);
                }
            }
            else
            {
                //等待失败,暂不处理
            }
        }
    }

    return 0;
}

实验现象如下:
其中父进程通过轮询检测的方式读取子进程的退出信息。
在这里插入图片描述

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

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

相关文章

1607_PC汇编语言_汇编语言简介

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 刚刚看了一个小章节&#xff0c;感觉是对8086的汇编做了一个简单的介绍。当然&#xff0c;这里面也有各种机器汇编通用的属性。 1. 等价符号关联的两个表达其实是等…

在线教育-谷粒学院学习笔记(十)

文章目录1 介绍2 登录业务流程3 JWT令牌4 阿里云短信服务5 登录功能6 注册功能7 根据token获取用户信息8 整合首页登录和注册1 介绍 登录实现流程 注册接口 整合JWT整合阿里云短信服务 登录接口 注册、登录的前端实现 2 登录业务流程 单一服务器模式 使用session对象实现 …

仿牛客论坛项目(下)

代码仓库:https://gitee.com/qiuyusy/community 仿牛客论坛项目15.kafka1.阻塞队列2.Kafka入门简介术语解释下载配置命令3.Spring整合Kafka引入依赖配置代码16.系统通知(Kafka)发送系统通知功能(点赞关注评论)1.编写Kafka消息队列事件Event实体类2.编写Kafka生产者3.编写Kafka消…

Spring 源码编绎

本示例基于 spring-framework-5.2.22.RELEASE GradleWapper jdk1.8.0_131编译# 环境准备mavenjdk8idea# 源码下载进入https://github.com/spring-projects/spring-frameworkSpring的源码是发布在github上面的下载最新版发布版源码不要太纠结版本区别&#xff0c;无需刻意保证…

BM5 合并k个已排序的链表

目录 描述 示例1 示例2 思路&#xff1a; 代码&#xff1a; 描述 合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。 示例1 输入&#xff1a;[{1,2,3},{4,5,6,7}] 返回值&#xff1a;{1,2,3,4,5,6,7} 示例2 输入&#xff1a;[{1,2},{1,4,5},{6}] 返回值…

Linux常用命令——rmmod命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) rmmod 从运行的内核中移除指定的内核模块 补充说明 rmmod命令用于从当前运行的内核中移除指定的内核模块。执行rmmod指令&#xff0c;可删除不需要的模块。Linux操作系统的核心具有模块化的特性&#xff0c;应…

Elasticsearch(六)--ES文档的操作(中)---修改文档

一、前言 上篇文章我们了解了ES的插入和批量插入文档的操作&#xff0c;分别通过ES的kibana客户端以及Java高级Rest客户端进行学习&#xff0c;那么本篇则进入到对文档的修改操作&#xff0c;同新增文档&#xff0c;也有更新单条文档和批量更新文档操作&#xff0c;但还多出一…

Day873.普通索引唯一索引的选择 -MySQL实战

普通索引&唯一索引的选择 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于普通索引&唯一索引的选择的内容。 假设你在维护一个市民系统&#xff0c;每个人都有一个唯一的身份证号&#xff0c;而且业务代码已经保证了不会写入两个重复的身份证号。 如果市民…

Java基础项目实战--大学生求职招聘信息网站系统

Java基础项目实战–大学生求职招聘信息网站系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系…

基于php的婚纱商城管理系统

摘要网络技术给生活带来了十分的便利。所以把婚纱商城管理系统与现在网络相结合。在婚纱商城发展的整个过程中&#xff0c;婚纱信息管理担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类管理系统程序也在不断改进。本课题所设计的婚纱商城管理系统&#xff0…

自动驾驶介绍、应用、前景

自动驾驶介绍、应用、前景1 介绍1.1 定义1.2 作用1.3 发展历程1.4 分类23年初竞争格局1.5 顾虑1.6 前景2 产业链现状2.1 芯片2.2 仿真3 技术路线3.1 是否交互3.1.1 单车智能3.1.2 车路协同3.2 主传感器区分3.2.1 纯视觉3.2.2 混合传感器3.3 前装还是后装3.3.1 前装3.3.2 后装4 …

基于java SSM医药住院管理系统设计和实现

基于java SSM医药住院管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 …

宇隆光电冲刺上交所上市:毛利率持续下滑,收入极其依赖京东方

近日&#xff0c;重庆宇隆光电科技股份有限公司&#xff08;下称“宇隆光电”&#xff09;预披露招股书&#xff0c;准备在上海证券交易所主板上市。 本次冲刺上市&#xff0c;宇隆光电计划募资15亿元&#xff0c;其中7亿元用于OLED控制板及液晶模组控制板和精密模切生产基地项…

Spring Cloud_服务监控hystrixDashboard

目录一、概述二、仪表盘90011.新建Module2.POM3.YML4.主函数5.Provider微服务提供类都需要监控依赖配置6.启动仪表盘三、断路器演示1.修改cloud-provider-hystrix-payment80012.监控测试代码链接 https://github.com/lidonglin-bit/cloud 一、概述 除了隔离依赖服务的调用以外…

背景图片大小设置 解决背景图多张铺满盒子 背景图和背景颜色混用

目录更多的样式透明度鼠标样式&#xff1a;cursor盒子隐藏背景图和img元素的区别涉及的css属性1. background-image:url(“”)2. background-repeat3. background-size解决图片多张铺满盒子的问题4. background-position5. background-attachment6. 背景图和背景颜色混用7. 速写…

Jackson使用详细介绍

Jackson使用详细介绍一 . Jackson 介绍二. Jackson Maven 依赖三. ObjectMapper 对象映射器四. Jackson JSON 基本操作1. Jackson JSON 序列化2. Jackson JSON 反序列化3. JSON 转 List4. JSON 转 Map5. Jackson 忽略字段6. Jackson 日期格式化Date 类型LocalDateTime 类型时间…

动态内存分配

目录 一、内存使用方式 &#xff08;一&#xff09;一个c/c编译的程序占用的内存分为以下几个部分 二、malloc &#xff08;一&#xff09;malloc 1. 举例&#xff1a;malloc(4) 2. 如何理解malloc(size(Var_T)*N) 3. 举例 &#xff08;二&#xff09;静态、全局指针…

【数据结构与算法】队列-模拟实现队列以及设计循环队列

文章目录队列的概念链表实现栈设计循环队列总结队列的概念 队列是一种特殊的线性表&#xff0c;特殊之处在于它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&#xff0c;和栈一样&#xff0c;…

图的搜索(DFS、BFS)

图的搜索&#xff08;图的遍历&#xff09;是指从图的任一顶点出发&#xff0c;访问图的所有顶点&#xff0c;且每个顶点只访问一次。 深度优先搜索 DFS概念&#xff1a; 深度优先搜索 (Depth-First Search&#xff0c;DFS)是从某个顶点v1出发对图进行搜素&#xff0c;每一步…

第八章 面向对象编程(中级)

一、访问修饰符&#xff08;P279&#xff09; 1. 基本介绍 java提供四种访问控制修饰符号&#xff0c;用于控制方法和属性&#xff08;成员变量&#xff09;的访问权限&#xff08;范围&#xff09;&#xff1a; &#xff08;1&#xff09;公开级别&#xff1a;用 public 修饰…