首页 > 系统相关 >linux进程的控制

linux进程的控制

时间:2024-11-10 23:16:16浏览次数:3  
标签:fork 控制 pid exit linux 进程 返回值 include

我们已经学习完了Linux进程的概念,进程的存储空间等等问题,接下来就是学习如何使用进程和管理进程


文章目录

目录

文章目录

前言

一、进程的创建

1、fork函数初识

2、fork函数返回值

3、写时拷贝

4、fork常规用法

5、fork调用失败的原因

二、进程终止

1.进程退出场景

2、进程常见退出方法

3._exit函数

4、exit函数

三、进程等待

1、进程等待必要性

2、进程等待的wait方法

3、进程等待的waitpid方法

4、获取子进程status

 四、阻塞等待

五、非阻塞等待

代码

​编辑


前言

进程的概念

进程的状态

环境变量

进程地址空间


一、进程的创建

我们之前在进程的概念时,为了认识进程,我们曾经用过fork函数,我们知道fork可以创建进程

1、fork函数初识

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

通过man函数查看fork的相关返回值和功能

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

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

分配新的内存块和内核数据结构给子进程

将父进程部分数据结构内容拷贝至子进程

添加子进程到系统进程列表当中

fork返回,开始调度器调度

fork完以后,创建一个子进程,然后子进程写实拷贝,将父进程的代码和数据拷贝一份

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

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

int main()
{
    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。进程8762先打印before消息,然后它有打印after。另一个after 消息有8762打印的。注意到进程8763没有打印before,为什么呢?如下图所示

通过这几张图就能说明,我们fork函数创建了一个子进程,然后子进程对父进程的代码和数据写时拷贝了,然后子进程和父进程一起向下运行,所以After:pid is这句话打印了两次。

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

2、fork函数返回值

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

3、写时拷贝

这里的写时拷贝就要先学习上一篇博客所提出来页表等概念

程序的地址空间

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

4、fork常规用法

一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求。

一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

后面会系统的学习exec等相关函数

5、fork调用失败的原因

系统中有太多的进程

实际用户的进程数超过了限制

二、进程终止

1.进程退出场景

 代码运行完毕,结果正确

 代码运行完毕,结果不正确

代码异常终止

这些都是导致进程终止退出的原因

2、进程常见退出方法

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

1. 从main返回

2. 调用exit

3. _exit

异常退出:

ctrl + c,信号终止

那么有一个问题为什么我们的main函数从学习c语言到现在main函数返回值一直都是0,为1,2,3....等等可以吗?

在我们学习了进程的概念我们知道每一个进程都有自己的pid和ppid,子进程的ppid是父进程,父进程的ppid是bash,这是基于不是子进程套子进程的前提下的结论。

所以当main函数的返回值不是0时也没有事,只是bash收到的就是不是0了。

3._exit函数

通过man文档查看_exit

我们看到它的头文件为 unistd.h,有一个参数status,返回值为空。

参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值 是255。(一会我们会在下面详细解释为什么是255,和这个返回值问题)

4、exit函数

通过man文档查看exit

我们看到它的头文件为 stdlib.h,有一个参数status,返回值为空。

参数:status 定义了进程的终止状态,父进程通过wait来获取该值

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

1. 执行用户通过 atexit或on_exit定义的清理函数。

2. 关闭所有打开的流,所有的缓存数据均被写入

3. 调用_exit

上代码演示二者的区别

 

#include<iostream>  
#include<unistd.h>  
#include<stdlib.h>  
using namespace std;  
int main()          
{                     
    cout<<"I am _exit\n";  
    _exit(0);  
    return 0;              
 }  
#include<iostream>  
#include<unistd.h>  
#include<stdlib.h>  
using namespace std;  
int main()          
{                     
    cout<<"I am exit";  
    exit(0);  
    return 0;              
 }  

 运行结果:

问题来了,为什么_exit什么都没有打印就就是了进程,而exit却打印了呢?

我们写进度条和c语言的时候我们知道了打印函数是先加载到缓存区内,最后再一次性打印的,而_exit却什么都没有打印,那么这就和缓冲区有关系了。也就证明了_exit直接退出了进程,而exit是先将执行清理函数,然后将冲缓冲区关闭缓冲区,最后退出进程。

而exit打印了却没没有换行是因为没有加回车换行符。

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

三、进程等待

1、进程等待必要性

之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。

最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。

父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

2、进程等待的wait方法

通过man 3 wait查看wait的功能和头文件,参数,返回值

 头文件sys/wait.h ,返回值pid_t ,参数整型指针

功能:存在于父进程当中,子进程结束后用来接收字节的pid,失败了返回-1。

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

3、进程等待的waitpid方法

通过man 3 waitpid指令查看waitpid的功能和头文件,参数,返回值

 返回值:

当正常返回的时候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。

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退 出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。

如果不存在该子进程,则立即出错返回。

4、获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

如果传递NULL,表示不关心子进程的退出状态信息。

否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

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

0000 0000 0000 0000 0000 0000 0000 0000  我们只用的上这16个字节,前8个比特位是返回的数值,后8个比特位是退出的状态,再前面的exit的参数和返回值中,我们让exit的参数位-1,返回值却是255的原因是因为-1的二进制数为 1111 1111 1111 11111 1111 1111 1111 1111  而255的二进制是0000 0000 0000 0000 0000 0000 1111 1111 ,当我们截取出前8位时,-1的二进制就和255一样了。而参数是整型,所以返回的就是255.

 四、阻塞等待

我们知道了wait和waitpid是在父进程中用来接受子进程退出和返回值的。那么当子进程没有运行结束,父进程都在干什么呢?

代码解释:

#include<sys/wait.h>
#include<iostream>
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
void runchild()
{
    int cnt =5;
    while(cnt)
    {
        cout<<"22222222222222222222 pid:  "<<getpid()<<"ppid:  "<<getppid()<<endl;
        sleep(1);
        cnt--;
    }
}
int main()
{
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");

        return 1;

    }
    else if(id==0)
    {
        //子进程
        runchild();
        exit(0);
    }
    else 
    {
        int cnt=10;
        //父进程
        while(cnt)
        {
            cout<<"11111111111111111111  pid:  "<<getpid()<<"ppid: "<<getppid()<<endl;
        
            sleep(1);
        
             cnt--;
         }  

        pid_t ret= wait(NULL);
        if(ret==0)
        {
            cout<<"0000000000000"<<endl;
        }
        else if(ret>0)
        {
            cout<<"1111111111"<<endl;
        }
        else 
        {
            cout<<"-1"<<endl;

        }
    }
    return 0;
}

运行结果: 

现象:刚开始父进程和子进程一起运行,当子进程运行完5秒以后,子进程退出,退出值保存起来,父进程还没有运行完成,父进程运行完5秒后,父进程继续向下走,到了wait,去取取出了子进程结束时的pid。因为wait返回值为大于0的数,所以返回成功!

当子进程运行完以后子进程就成僵尸进程了,等待父进程回收,当父进程运行完wait以后将僵尸进程回收。

五、非阻塞等待

代码

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
 #include<stdio.h>
#include<sys/wait.h>
using namespace std;
void runchild()
 {
    int cnt=10;
    while(cnt)
    {
            printf("11111111111111  pid:%d   ppid:%d \n",getpid(),getppid());
     cnt--;
     sleep(1);
    }
 }
int main()
 {
     pid_t id=fork();
    if(id<0)
    {
         perror("fork!!!");
        sleep(1);
     }                                                                                                                 
     else if(id==0)
     {
        //child
        runchild();
       exit(1);
}
     else
     {
        //prent                                                                                                       
        int cnt=3;
        while(cnt)
       {
            printf("22222222222222  pid:%d   ppid:%d \n",getpid(),getppid());
            cnt--;
            sleep(1);
         }
        //int status=0;
        // pid_t ret= waitpid(-1,status,WNOHANG);
        while(1)
         {
         
           int status=0;
              pid_t ret= waitpid(-1,&status,WNOHANG);
        if(ret==id)
             {
                printf("成功返回,返回值为:%d\n ",ret);
               sleep(5);
               break;

             }
              else if(ret<0)
              {
                 sleep(1);
                  perror("wait");
                break;
             }
             else 
           {
                printf("好了吗子进程pid:%d   你爹等你呢pid%d\n",id,getpid());
                  sleep(1);

             }
        }
     
   }
    return 0;
 }

运行结果: 

现象:当父进程运行结束后,子进程还没有运行结束,父进程没有闲着,而是一直在问子进程结束没。

waitpid有三个参数,第一个参数填-1代表接收任何子进程,第二参数为status的指针;

第三个参数非常重要,代表着是阻塞等待还是非阻塞等待

非阻塞等待参数为:WNOHANG

阻塞等待参数为:0

 


 

进程的创建和进程的基本管理、询问以结束,接下来就是进程的替换!!!

标签:fork,控制,pid,exit,linux,进程,返回值,include
From: https://blog.csdn.net/2301_80687320/article/details/143664638

相关文章

  • Linux 查找命令总结
    在使用linux时,经常需要进行文件查找。五种命令是有区别的。区别:(1)find 根据文件的属性进行查找,如文件名,文件大小,所有者,所属组,是否为空,访问时间,修改时间等。(2)grep根据文件的内容进行查找,会对文件的每一行按照给定的模式(patter)进行匹配查找。(3)which 查看可执行文件......
  • Linux的boot和startup过程
    Linux的启动主要分为两阶段的过程:boot和startup。boot过程在计算机启动后触发,完成代表内核初始化成功并且系统已经启动。之后startup过程接管并将计算机转变为可触发状态。总的来说,主要由接下来的步骤完成:1、BIOSPOST2、Bootloader(GRUB2)3、Kernelinitialization4、Start......
  • Linux下使用makeself制作一键安装包
    Linux下使用makeself制作一键安装包下载makeselfyum-yinstallmakeselfmakeself命令和参数makeself.sh--gzip.<output_file.run>"<display_name>"<startup_script>.表示当前目录,这样makeself将会打包当前目录下的所有文件和子目录。该目录最好使用绝对路径......
  • 【操作系统】4.进程调度算法
    进程调度算法决定了进程在何时、以何种顺序被分配到CPU上执行。不同的调度算法适合不同类型的操作系统和应用需求,以下是一些常用的进程调度算法:1.先来先服务调度(FCFS:First-Come,First-Served)算法原理:按进程到达的先后顺序分配CPU,先到达的进程先被处理。优点:简单易实现,......
  • STM32+TMC2209控制步进电机正反转
    TMC2209是一款由Trinamic公司生产的高性能步进电机驱动器芯片,它支持SPI通信接口,能够实现精准的步进电机控制。本文将详细介绍如何使用STM32微控制器结合TMC2209驱动器来控制步进电机的正反转。TMC2209特点高精度控制:支持步进角为0.9°、1.8°、3.6°等多种细分设置。SPI接......
  • Linux之sed命令详解
    文章目录......
  • 【优化参数】粒子群算法PSO求解三轴稳定航天器姿态控制PD参数优化问题【含Matlab源码
    ......
  • 【读懂Linux】基础IO
      学习编程就得循环渐进,扎实基础,勿在浮沙筑高台   循环渐进Forward-CSDN博客目录 循环渐进Forward-CSDN博客系统文件I/O接口介绍writereadcloselseek,类比C文件相关接口open函数返回值文件描述符fd文件描述符的分配规则重定向使用dup2系统调用FILE......
  • shell脚本在linux无法运行
    shell脚本在linux无法运行在windows写的.sh脚本,直接把文件传到Linux之后运行,报错:$bash./v_1.sh:commandnotfound'/v_1.sh:line4:syntaxerrornearunexpectedtoken`do'/v_1.sh:line4:`do脚本内容是:#shellvirusI#forfilein./infect/*docp$0$fi......
  • Linux下解压命令大全
    文章目录1、tar2、zip3、rar4、gz5、tar.gz和.tgz6、bz27、tar.bz28、bz9、tar.bz10、Z11、lha12、rpm13、debLinux主要根据后缀名,选择解压和打包的命令想了解更多内容,请跟上向导的步伐吧:Eg:mantar1、tar解包:tarxvfFileName.tar打包:tarcvfFileName.tar......