Linux--进程控制(1)

文章衔接:

Linux--环境变量-CSDN博客

Linux--地址空间-CSDN博客

目录

1.进程创建

 2.进程的终止

2.1想明白:终止是在做什么? 

2.2进程终止的三种情况 

 2.3 进程如何终止

3.进程等待 (wait/waitpid)


1.进程创建

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

#include <unistd.h>
pid_t fork(void);

返回值:子进程中返回0,父进程返回子进程id,出错返回-1。

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

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

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

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

运行结果:

        这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示

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

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

fork常规用法

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

fork也可能创建失败:1.系统中有太多的进程  2.实际用户的进程数超过了限制


 2.进程的终止


2.1想明白:终止是在做什么? 

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

创建进程首先就是要创建相关管理的数据结构,然后将代码和数据加载进来。

终止的本质:释放曾经的代码和数据所占的空间,释放内核数据结构。task_struct是要被延期处理的,因为进程有一个z(僵尸)状态需要被维护。


2.2进程终止的三种情况 

关于退出码:

先来看一段代码:

运行结果:

解释:我们在写C语言程序的时候, 我们习惯的在结束位置写上:return 0,这个操作实际上是将退出码返回给父进程,这个程序的父进程就是bash。$?是bash中储存错误码的变量,他会接收最近的一个子进程退出的退出码(echo是内建命令,可以直接打印bash内部的变量数据),退出码0:表示运行成功,!0:表示失败,1,2,3,4,5,6......不同非0 的值,一方面表示失败,一方面表示失败的原因(都有对应的错位描述)。

下面这个函数就是将错误码转化为错误信息的

再看一段带代码:

运行结果:我们可以看到一共有133条错误信息。父进程bash得到子进程的退出码,就可以知道子进程退出的情况,是成功还是失败,失败的原因是什么。

当然我们也是可以自定义退出码的,请看下面的一段程序:

我们使用自定义的方式,来控制我们的返回信息(0:成功  1:除到0了  2 mod 0 了)

// 自定义枚举常量
enum
{
    Success = 0,
    Div_Zero,
    Mod_Zero,
};
int exit_code = Success;
const char *CodeToErrString(int code)
{
    switch(code)
    {
        case Success:
            return "Success";
        case Div_Zero:
            return "div zero!";
        case Mod_Zero:
            return "mod zero!";
        default:
            return "unknow error!";
    }
}
int Div(int x, int y)
{
    if( 0 == y )
    {
        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;
}

运行结果:第一个用例符合规范测试成功,第二个用除到0了不符合规范,报错。


我们通过上面的例子知道了进程终止的2种情况:

a.代码跑完,结果正确 。

b.代码跑完结果不正确 上面的两种情况,我们都可以通过进程的退出码决定!

接下来我们来看第三种情况:

c.代码执行时出现异常,提前退出了  ----操作系统发现你的进程做了不该做的事情,OS杀了进程(一旦出现异常,退出码就没有意义了)

那如果出现异常了,原因是什么?进程出现异常,本质因为进程收到了OS发给进程的信号,我们通过信号是多少,就可以判断我的进程为什么异常了!!!

例如野指针问题:

报错:段错误,OS提前终止进程。

段错误实际上就是OS给进程发送了kill的11号信号

演示:让程序持续的打印自己pid

运行程序几秒后,给16928 执行kill -11号指令,程序报段错误了。

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

PCD(task_struct)是要被延期处理的,因为进程有一个z(僵尸)状态需要被维护,当进程退出时,进程会把退出码和退出信号(exit_code&&exit_signal)写入到PCD(task_struct)去的,让父进程读取。


 2.3 进程如何终止

a. main函数return,表示进程终止(非main函数,函数结束,但不是进程结束)

b. 代码调用exit函数--引起一个正常的进程终止exit内部的参数就等于main函数中的退出码

在任意位置调用exit,都表示进程直接终止

运行结果:

c. _exit函数:终止一个调用进程,但这个函数是与

运行结果:在目前来看,作用似乎和exit相同

接下来就看一看和exit不一样的地方:

在睡眠2 的时候printf早就执行完了,将打印的信息存在了缓存区中

通过下面的对比我们发现:exit会帮我们我们冲刷缓冲区,但_exit不会

在这里我们重新说明一下:exit是c语言的库函数,_exit是系统调用。所谓的缓冲区不在OS/_exit中,exit实际上调用的就是_exit,所以缓冲区只能在_exit之上。(结论:目前我们所说的缓冲区,不是内核缓冲区!)


3.进程等待 (wait/waitpid)

什么是等待?

任何进程,在退出情况下,一般必须要被父进程等待!

进程在退出的时候,如果父进程不管不顾,退出的进程就会变为,状态Z(僵尸状态),最终会导致内存泄漏

为什么要等待?

1.父进程通过等待,解决进程退出的僵尸问题,回收系统资源(一定要考虑的)

2.获取子进程的退出信息,知道子进程的退出原因(可选的)

如何等待?

这里就要引入两个函数:wait/waitpid(系统调用)

wait:等待一个子进程状态发生变化,等待成功时,会返回子进程的pid(等待父进程中,任意一个子进程退出)

接下来看一段代码:

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

void ChildRun()
{
    
    int cnt = 5;
    while (cnt)
    {
        printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
        sleep(1);
        cnt--;
     
    }
}

int main()
{
    printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());

    pid_t id = fork();
    if (id == 0)
    {
        ChildRun();
        printf("child quit ...\n");
        exit(0);
    }
    sleep(10);
    // fahter
    pid_t rid = wait(NULL);

    if (rid > 0)
    {
        printf("wait success, rid: %d\n", rid);
    }
    else
    {
        printf("wait failed !\n");
    }
    sleep(3);
    printf("father quit...\n");


}

窗口一:while :; do ps ajx | head -1 && ps ajx | grep mytest  | grep -v grep; sleep 1; done

窗口二:执行该程序

经过测试我们发现:开始5s子进程运行,退出后处于僵尸状态,父进程等待成功后僵尸不在,结束后父进程退出。(等待成功后,解决子进程僵尸问题)
        如果子进程没有退出,父进程就会进行阻塞等待(等待子进程的变化,实际上就是进程阻塞了),在这个案例中,子进程本质就是软件,父进程本质就是在等待某种软件条件就绪。

waitpid方法

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

参数:
        pid:
                Pid=-1,等待任一个子进程。与wait等效。
                Pid>0.等待其进程ID与pid相等的子进程。
        status:
                WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
                WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
        options:
                WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

下图是关于status在32位机器下的使用情况,退出码占最后16比特的前八位,后8为则是留给退出信息的

eg:野指针--段错误(使用位操作来调控status的参数)

void ChildRun()
{
    int *p = NULL;
    int cnt = 5;
    while(1)
    {
        printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
        sleep(1);
        cnt--;
        *p = 100;
    }
}

int main()
{
    printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());

    pid_t id = fork();
    if(id == 0)
    {
        // child
        ChildRun();
        printf("child quit ...\n");
        exit(123);
    }
    sleep(7);
    // fahter
    //pid_t rid = wait(NULL);
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        printf("wait success, rid: %d\n", rid);
    }
    else
    {
        printf("wait failed !\n");
    }
    sleep(3);
    printf("father quit, status: %d, child quit code : %d, child quit signal: %d\n", status, (status>>8)&0xFF, status & 0x7F);
}

运行结果:kill的11信号,表示段错误,报错正确。

eg:使用宏直接调控status的参数

WIFEXITED判断进程是不是正常结束的,如果想单独知道退出信号还是需要位操作去调控; WEXITSTATUS在WIFEXITED为真的情况下,提取进程的退出码。

进程正常退出,获取退出码

在上面我们介绍了阻塞等待( 如果子进程没有退出,父进程就会进行阻塞等待(等待子进程的变化,实际上就是进程阻塞了),在这个案例中,子进程本质就是软件,父进程本质就是在等待某种软件条件就绪。

接下来我们来了解一下非阻塞等待

        非阻塞等待是一种在编程中使用的机制,允许一个进程或线程在等待某个事件(例如子进程的结束、数据的到达或其他类型的信号)发生时,不会被阻塞或挂起,而是可以继续执行其他任务。当然你可以在一个循环中多次的对子进程进行检测,如果还没好就执行其它的任务,直到子进程准备就绪。非阻塞等待的时候+循环 = 非阻塞轮询(在这种情况下,父进程就可以去进行其它的事情了)

         waitpid函数的第三个参数options:用于修改waitpid的行为。
为了实现非阻塞等待,我们需要使用WNOHANG这个选项。当设置了WNOHANG时,waitpid会立即返回,无论子进程是否结束。如果子进程已经结束,waitpid会返回子进程的PID;如果子进程还没有结束,waitpid会返回0;如果出错,会返回-1。

代码演示:

void ChildRun()
{

    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
        sleep(1);
        cnt--;
  
    }
}

int main()
{
    printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());

    pid_t id = fork();
    if(id == 0)
    {
        // child
        ChildRun();
        printf("child quit ...\n");
        exit(123);
    }
   
    // father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, WNOHANG); // non block
        if(rid == 0)//如果返回子为0说明子进程未就绪,父进程可以执行自己的任务
        {
            sleep(1);//每1秒检测一次
            printf("child is running, father check next time!\n");
            //DoOtherThing();
        }
        else if(rid > 0)
        {
            if(WIFEXITED(status))
            {
                printf("child quit success, child exit code : %d\n", WEXITSTATUS(status));
            }
            else
            {
                printf("child quit unnormal!\n");
            }
            break;
        }
        else
        {
            printf("waitpid failed!\n");
            break;
        }
    }

运行结果:

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

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

相关文章

vue中form表单中select下拉v-model绑定有数值,但下拉框不显示值的情况

vue中form表单中select下拉v-model绑定有数值&#xff0c;但下拉框不显示值的情况 场景复现&#xff1a; 我将后端获取的数据手动赋值值给select的下拉v-model绑定对象对应的值&#xff0c;但在前端下拉框不显示我赋值的通过v-model给的值&#xff0c;通过控制台打印v-mode的值…

Linux常用监控命令(笔试面试常考)

1.、free命令 [rootRocky8-node1 ~]# free -htotal used free shared buff/cache available Mem: 1.7Gi 1.1Gi 69Mi 31Mi 554Mi 436Mi Swap: 2.0Gi 258Mi 1.7Gi free命令是Linux系统中用…

Java实战:确定给定日期是一年的第几天

本次实战&#xff0c;我们将探讨如何确定给定日期是一年中的第几天。为此&#xff0c;我们提供了三种不同的方法&#xff0c;每种方法都有其独特的实现方式和适用场景。 方法一&#xff1a;不使用数组 这种方法通过Scanner类获取用户的输入&#xff0c;包括年份、月份和日期。…

致远互联 OA fileUpload.do 文件上传漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 致远OA是一款企业级办公自动化软件&#xff0c;提供…

使用selenium时出现element click intercepted报错的解决办法

win10&#xff0c;python3.8.10。 selenium版本如下&#xff08;用pip38 show selenium查看&#xff09;&#xff1a; 在定位中&#xff0c;定位了一个按钮&#xff08;特点&#xff1a;button下还有span然后才是文本&#xff09;&#xff0c;代码如下&#xff1a; from sele…

科技论文网站:中国科技论文在线

文章目录 1. Intro2. Main3. Cons Evaluation彩蛋&#xff1a;科学素质 这是作者最后一次发这种级别的科普文章 1. Intro 中国科技论文在线是经教育部批准&#xff0c;由教育部科技发展中心主办&#xff0c; 利用现代信息技术手段&#xff0c;打破传统出版物的概念&#xff0c…

苍穹外卖day8(2)用户下单、微信支付

文章目录 前言一、用户下单1. 业务流程2. 接口设计3. 数据库设计3.1 订单表orders3.2 订单明细表 order_detail 4. 代码实现 二、订单支付 前言 用户下单 因为订单信息中包含了其他业务中的数据&#xff0c;在逻辑处理中涉及了多个其他业务&#xff0c;比如要判断地址簿、购物…

基于springboot,vue停车管理系统

目录 项目介绍: 图片展示 运行环境 获取方式 项目介绍: 权限划分&#xff1a;用户和管理员 用户&#xff1a; 具有登录&#xff0c;注册&#xff0c;退出登录的功能 首页&#xff1a;展示一个欢饮页面 个人中心&#xff1a;展示关于个人的信息&#xff0c;以及停车信息…

Unreal Engine创建Plugin

打开UE工程&#xff0c;点击编辑&#xff0c;选择插件 点击“新插件”按钮&#xff0c;选择“空白选项”填入插件名字"MultiPlayerPlugin"&#xff0c;填入插件作者、描述&#xff0c;点击“创建插件”按钮打开C工程&#xff0c;即可看到插件目录&#xff0c;编译C工…

ai论文生成神器——快速完成论文任务!

在这个AI写作的时代&#xff0c;大家都在使用AI写作作为论文辅写工具。用过ChatGPT写论文的小伙伴应该都知道&#xff0c;ChatGPT是通过对话或提问形式获取的AI生成内容&#xff0c;提供不了专业的论文写作标准&#xff0c;例如自动生成封面、目录、摘要、参考文献等部分。而专…

面试集中营—ElasticSearch架构篇

一、为什么用ElasticSearch&#xff1f; 1、支持多种数据类型。它可以处理非结构化、数值和地理信息等多种类型的数据&#xff1b; 2、简单的RESTful API。ES提供了一个简单易用的RESTful API&#xff0c;使得它可以从任何编程语言中调用&#xff0c;降低了学习的曲线。 3、近实…

德语口语学习的8种练习方法

简洁明了一点&#xff0c;方便大家理解&#xff0c;我总结了以下8点&#xff1a; 1.模拟对话&#xff1a; 创造实际生活场景&#xff0c;例如购物、问路、餐厅点餐等&#xff0c;并自言自语或者与伙伴一起模拟这些对话。 参加角色扮演活动&#xff0c;通过不同情境练习口语。…

Camtasia2024破解版激活许可证秘钥永久免费使用

Camtasia Studio是一款专业的屏幕录像和视频编辑软件套装&#xff0c;它提供了从屏幕录制到视频编辑、菜单制作、视频播放等一系列功能。以下是对Camtasia Studio及其2024年最新版本的详细介绍。 一、Camtasia Studio概述 Camtasia Studio是一款集屏幕录制、视频剪辑、菜单制…

java:SpringBoot入门

Spring 提供若干子项目,每个项目用于完成特定功能 Spring Boot 可以简化配置并且快速开发 SpringBootWeb快速入门 创建Springboot模块并使用Springweb依赖 在类上添加注解 RestController可以将字符串自动转成json返回数据给页面 再在方法上添加注解 RequestMapping(&…

深入理解GTK、Qt、AWTK:跨平台GUI框架对比

目录标题 GTK特性&#xff1a;优点&#xff1a;缺点&#xff1a; Qt特性&#xff1a;优点&#xff1a;缺点&#xff1a; AWTK特性&#xff1a;优点&#xff1a;缺点&#xff1a; 适用场景 在当今的软件开发领域&#xff0c;图形用户界面&#xff08;GUI&#xff09;的开发是不可…

Log4j日志框架多种日志级别

Log4j日志框架定义了多种日志级别&#xff0c;这些级别按照优先级从高到低排列如下&#xff1a; OFF&#xff1a;这是最高等级的日志级别&#xff0c;用于关闭所有日志记录。FATAL&#xff1a;指出每个严重的错误事件将会导致应用程序的退出。ERROR&#xff1a;表明发生错误事…

python之excel加工处理小案例一则

一、工具用途 工作中&#xff0c;需要对各类excel进行加工处理&#xff0c;当表和字段比较多时&#xff0c;关联条件又有多个&#xff0c;每次通过execl的vlookup之类的关联公式手工可以解决工作需求&#xff0c;但一般耗时较长&#xff0c;且人工统计匹配也存在出错的情况。 …

官方售价299元的自媒体博客资讯类wordpress主题

官方售价299元的自媒体博客资讯类wordpress主题。 自媒体一号是一款由主题巴巴团队原创设计开发的WordPress主题&#xff0c;这款主题页面布局简约大气&#xff0c;设计细节精美考究&#xff0c;内置功能非常强大&#xff0c;通过后台的主题设置面板&#xff0c;你可以轻松自定…

kaggle不显示中文字体

链接&#xff1a;【kaggle】在matplotlib中使用中文字体_kaggle使用中文字体打印图片-CSDN博客 下载字体链接 完整代码&#xff1a; import matplotlib.pyplot as plt import matplotlib.font_manager as font_managerdef plot_df(df, x, y, title"", xlabelDate,…

【Qt常用控件】—— 多元素控件

目录 1.1 List Widget 1.2 Table Widget 1.3 Tree Widget 1.4 小结 Qt 中提供的多元素控件有: QListWidget QListView QTableWidget QTableView QTreeWidget QTreeView xxWidget 和 xxView 之间的区别 以 QTableWidget 和 QTableView 为例&#xff1a; QTableView 是基于…
最新文章