Linux系统:进程控制

news2024/11/16 7:28:33

文章目录

  • 1 创建进程
  • 2 进程终止
    • 2.1 进程退出情况
    • 2.2 进程终止的常见方式
      • 2.2.1 return语句
      • 2.2.2 exit()函数
      • 2.2.3 _exit()函数
  • 3进程等待
    • 3.1 进程等待的重要性
    • 3.2 进程等待的方法
      • 3.2.1 wait()方法
      • 3.2.2 waitpid()方法
  • 4 进程替换
    • 4.1 替换原理
    • 4.2 替换函数

1 创建进程

fork()函数:从已经存在的进程中创建一个新进程,新进程为子进程,原来的进程为父进程。
进程调用fork函数之后,当控制转移到内核中的fork代码后,
内核做:
①分配新的内存块和内存数据结构给子进程。
②将父进程部分数据结构内容拷贝至子进程。
③添加子进程到系统进程列表当中。
④fork返回,开始调度器调度
在这里插入图片描述

fork之前父进程独立执行,fork之后,父子两个执行流分别执行。fork之后,谁先执行完全由调度器决定。
写时拷贝
通常,父子代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入数据,便以写时拷贝的方式各自一份副本。具体见下图

在这里插入图片描述

写时拷贝的好处:使父子进程得以彻底分离,可以实现进程独立性
写时拷贝,是一种延时申请技术,可以提高整机内存的使用率

2 进程终止

2.1 进程退出情况

进程退出情况
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止

进程运行正确,返回值为0,代表success;
进程运行错误,返回非0值作为错误码,一个错误码对应着一个错误信息
退出码只能确定程序结果运行是否正确,当程序异常退出时,错误码就没有任何意义了。

在这里插入图片描述
自己可以使用这些退出码和含义,当然,也可以自己设计出一套退出方案

2.2 进程终止的常见方式

2.2.1 return语句

只有main函数的return语句才代表进程终止,其它函数的return语句只代表函数结束。
由此可知:main函数的返回值不一定要是0,而是要根据自己的实际情况选择不同的返回值

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{

int number=0;
int sum=0;
for(number=1;number<=100;number++)
{
sum+=number;

}

if(sum==5050)
{
  return 0;//结果正确
}
else{
  return 1;//结果错误
}
}

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{

int number=0;
int sum=0;
for(number=1;number<=100;number++)
{
sum+=number;

}
sum=0;//将结果改为错误的
if(sum==5050)
{
  return 0;//结果正确
}
else{
  return 1;//结果错误
}

在这里插入图片描述

2.2.2 exit()函数

#include<stdio.h>
#include<stdlib.h>
int main()
{


  printf("hello world\n");
  sleep(3);
  exit(0);
}

exit()放在任意位置(主函数或者其他函数)都可以结束进程
参数是退出码,可以是任意整数
exit()在结束进程的同时会刷新缓冲区

#include<stdio.h>
#include<stdlib.h>
int main()
{


  printf("hello world");
  sleep(3);
  exit(22);
}

printf输出打印的时候没有加\n,数据会先在缓冲区,休眠3秒以后,exit()函数会刷新缓冲区,打印出语句

在这里插入图片描述

2.2.3 _exit()函数

和exit()唯一的区别就是_exit()只会终止进程,不会刷新缓冲区

#include<stdio.h>
#include<stdlib.h>
int main()
{


  printf("hello world");
  sleep(3);
  _exit(22);
}

在这里插入图片描述
可以看到没有打印出语句
在这里插入图片描述

3进程等待

3.1 进程等待的重要性

子进程退出,如果父进程什么都不管,就会造成僵尸进程问题,进而会产生内存泄露
另外,进程一旦变成僵尸状态,即使发送kill -9信号,也无法杀掉僵尸进程
所以父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息,进而也可以知道父进程派给子进程的任务完成如何

3.2 进程等待的方法

3.2.1 wait()方法

等待成功,返回所终止子进程的pid;等待失败返回-1

#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);

  }
  else if(id==0)
  {
    while(cnt--)
    {
      printf("I am child\n");
     sleep(1);
    }
    
  }
  else{
    sleep(10);
    printf("I am father\n");
    pid_t ret=wait(NULL);
    if(ret>0)
    {
        printf("等待子进程成功 ,子进程pid:%d\n",ret);
       sleep(3); 
    }
    else{
      printf("等待子进程失败\n");
    }
  }
}

在这里插入图片描述
在这里插入图片描述

3.2.2 waitpid()方法

pid_t waitpid(pid_t pid, int *status, int options)

组成说明
返回值①等待成功&&子进程退出:返回子进程的pid ②等待失败:返回-1
参数pid①设置为指定进程的pid,代表等待某个指定的进程,②若设置为-1,代表等待任意的进程
参数status是输出型参数,获得进程退出的结果
参数optionoption为等待方式,①设置为0代表默认行为,即阻塞等待 ②设置为WNOHANG,代表非阻塞等待

参数status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充
如果传递为NULL,表示不关心子进程的退出状态信息
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
status不能简单的当做整形来看待,可以当做位图来看待

在这里插入图片描述

下标[0,7)的7个比特位代表终止信号,如果收到了信号,说明进程是异常终止,此时便不需要关注退出码
下标[8,16)这8个比特位代表退出码,如果是正常退出,此时信号为0,需要关注退出码
下标为7的比特位代表core dump标志

正常退出的情况

#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);

  }
  else if(id==0)
  {
    while(cnt--)
    {
      printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
     sleep(1);
    }
    exit(20);
  }
  else{
    sleep(10);
    printf("I am father\n");
   // pid_t ret=wait(NULL);
    int status=0;
    pid_t ret=waitpid(id,&status,0);//父进程的id就是子进程的pid
    if(ret>0)
    {
        printf("等待子进程成功 ,子进程pid:%d,子进程收到的信号编号:%d,子进程退出码:%d\n",ret,status&0x7F,(status>>8)&0xFF);
       sleep(3); 
    }
    else{
      printf("等待子进程失败\n");
    }
  }
}

在这里插入图片描述
异常退出的情况

#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);

  }
  else if(id==0)
  {
    while(cnt--)
    {
      printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
     sleep(1);
    }
    int *p=NULL;
    *p=100;//野指针
    exit(20);
  }
  else{
    sleep(10);
    printf("I am father\n");
   // pid_t ret=wait(NULL);
    int status=0;
    pid_t ret=waitpid(id,&status,0);//父进程的id就是子进程的pid
    if(ret>0)
    {
        printf("等待子进程成功 ,子进程pid:%d,子进程收到的信号编号:%d,子进程退出码:%d\n",ret,status&0x7F,(status>>8)&0xFF);
       sleep(3); 
    }
    else{
      printf("等待子进程失败\n");
    }
  }
}

在这里插入图片描述

在这里插入图片描述
可以看到 11号信号为SIGSEGV

SIG是信号名的通用前缀

SEGV为segmentation violation 即存储器区段错误,无效的内存引用

参数option
阻塞等待:在子进程执行任务期间,父进程一直处于阻塞队列,直到子进程退出
非阻塞等待:不断的轮询检测子进程的状态,若子进程没有退出,父进程可以执行自己的代码
此时返回值就分为三种情况
等待成功&&子进程退出:返回子进程的pid
等待成功&&子进程未退出:返回0(非阻塞等待)
等待失败:返回-1

非阻塞等待案例

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

typedef void (*handler_t)();//函数指针类型
    std::vector<handler_t>handlers;
void fun_one()
{
  printf("这是一个临时任务1\n");
}
void fun_two()
{
  printf("这是一个临时任务2\n");
}
void Load()//父进程闲了想要执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法
{
handlers.push_back(fun_one);
handlers.push_back(fun_two);
}
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);

  }
  else if(id==0)
  {
    while(cnt)
    {
      printf("我是子进程:%d\n",cnt--);
     sleep(1);
    }
    
    exit(20);
  }
  else{
         int quit=0;
         while(!quit)
         {
           int status=0;
           pid_t res=waitpid(-1,&status,WNOHANG); //以非阻塞方式等待
           if(res>0)
           {
             //等待成功&&子进程退出
             printf("等待子进程成功,退出码:%d\n",WEXITSTATUS(status));
             quit=1;
           }
           else if(res==0)
           {
             //等待成功&&子进程未退出
             printf("子进程还在运行中,父进程可以等一等\n");
              if(handlers.empty())
              {
                Load();
              }
                for(auto iter:handlers)
                {
                
                  iter();
                 }
            }
           else{
             printf("wait失败\n");
             quit=1;
           }
           sleep(1);
         }
  }
}

对于获取子进程退出结果的参数status,除了使用位操作,还可以使用系统定义好的宏
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

在这里插入图片描述

4 进程替换

4.1 替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数,以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

简单来说:用exec()函数进行程序替换,就是将新的磁盘上的程序加载到内存,并和当前进程的页表,重新建立映射

4.2 替换函数

①int execl(const char *path, const char *arg, …);
②int execlp(const char *file, const char *arg, …);
③int execle(const char *path, const char *arg, …,char *const envp[]);
④int execv(const char *path, char *const argv[]);
⑤int execvp(const char *file, char *const argv[]);

函数解释
这些函数如果调用成功则加载新的程序,从启动代码开始执行,不再返回。
如果调用出错则返回-1。
所以exec函数只有出错的返回值而没有成功的返回值。

命名理解
①l(list) : 表示参数采用列表
②v(vector) : 参数用数组
③p(path) : 有p自动搜索环境变量PATH
④e(env) : 表示自己维护环境变量

常用的exec函数举例

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
# define NUM 16
const char* myfile="./mycmd";
int main()
{
  pid_t id=fork();
  if(id==0)
  {
   // char* _argv[NUM]={"ls","-a","-l",NULL};
   char* _argv[NUM]={"mycmd","-a",NULL};
    printf("子进程开始运行,子进程pid:%d\n",getpid());
    sleep(3);
//    execl("/usr/bin/ls","ls","-a","-l",NULL);//参数使用列表的方式执行ls -a -l命令
//     execv("/usr/bin/ls",_argv);//参数使用数组的方式执行ls -a -l命令
   //  execlp(myfile,"mycmd","-b",NULL);//自动在环境变量中找命令
  //     execlp(myfile,"mycmd","-a",NULL);
//      execvp(myfile,_argv);
      execlp("java","java","hello",NULL);//在自己的程序里面又去调用java程序
    exit(1);
  }
  else{
    printf("父进程开始运行,父进程pid:%d\n",getpid());
    int status=0;
    pid_t res=waitpid(-1,&status,0);//阻塞等待
    if(res>0)
    {
      printf("wait success,exit code:%d\n",WEXITSTATUS(status));
    }
  }
  return 0;
}
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc,char* argv[])//mycmd -a或mycmd -b
{
  if(argc!=2)
  {
    printf("can not execute\n");
    exit(1);
  }
  if(strcmp(argv[1],"-a")==0)
  {
    printf("hello a!\n");
  }
  else if(strcmp(argv[1],"-b")==0)
  {
    printf("hello b!\n");
  }
  else{
    printf("default!\n");
  }
  return 0;
}

在这里插入图片描述

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

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

相关文章

mediapipe 手势节点识别自动控制音量

参考&#xff1a;https://www.computervision.zone/topic/volumehandcontrol-py/ 主函数&#xff1a; VolumeHandControl.py import cv2 import time import numpy as np import HandTrackingModule as htm import math from ctypes import cast, POINTER from comtypes imp…

[问题解决] ubuntu 18.04 GPU驱动安装

删除当前显卡驱动[参考] sudo apt-get purge nvidia* 查看推荐驱动 sudo ubuntu-drivers devices 安装对应驱动 sudo apt install nvidia-driver-530 验证安装是否成功&#xff1a;nvidia-smi

「一本通 3.2 例 3」架设电话线

题目大意 在加权无向图上求出一条从 号结点到 号结点的路径&#xff0c;使路径上第 大的边权尽量小。 思路 由于是一次性的&#xff0c;且这题数据极小&#xff0c;考虑 正常情况下是来更新数组的&#xff0c;不过这次是更新 表示第个节点&#xff0c;&#xff08;可以…

springboot引入外部sdk,以及在maven中配置,以及连同sdk打包

目录 1 安置sdk 2 配置maven配置文件 3 刷新maven即可 4 打包配置 1 安置sdk 将外部的sdk放在项目的指定目录下&#xff0c;我选择放在resource/sdk的目录下 示例&#xff1a; 2 配置maven配置文件 打开pom.xml&#xff0c;新增以下的依赖配置 具体如下所示&#xff1a; …

C# OpenCvSharp 透视变换(图像摆正)Demo

效果 Demo下载 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; using OpenCvSharp; using OpenCvSha…

王爽《汇编语言》期末考试题库(附答案)

单选题 第一章 PC机的最小信息单位是&#xff08; &#xff09;。 A. bit B. 字节 C. 字长 D. 字 A PC机的最小信息单位是比特(bit)&#xff0c;常用来表示一位二进制数字&#xff08;0或1&#xff09;。字节(byte)是计算机中常用的数据单位&#xff0c;一个字…

vscode中ModuleNotFoundError: No module named ‘torch‘解决方法

文章目录 遇到的问题解决方法参考 遇到的问题 使用vscode训练模型&#xff0c;没有使用远程服务器&#xff0c;使用本地运行代码&#xff0c;显示“ModuleNotFoundError: No module named ‘torch’” 解决方法 这是因为没有选择合适的python解释器。如何选择正确的解释器呢&…

【HDFS实战】HDFS上的数据均衡

HDFS上的数据均衡简介 文章目录 HDFS上的数据均衡简介重新平衡数据节点之间的数据块相关命令 重新平衡DN内磁盘间的数据相关命令PlanExecuteQueryCancelReport 相关配置调试 HDFS上的balance目前有两类&#xff1a; Balancer&#xff1a;节点之间的balanceDisk Balancer&#x…

C# WinForm 学习记录

1.为项目添加dll引用 在“解决方案资源管理器”面板中选择项目&#xff0c;单机鼠标右键&#xff0c;弹出菜单中选择“添加”->“引用”打开引用管理器&#xff0c;选择浏览添加自己需要的dll文件即可 2.位移运算符使用技巧 在进行位移运算时&#xff0c;当数值的二进制数…

设计模式学习笔记——你真的学透单例模式了吗

你真的学透单例模式了吗 一、概述 单例模式&#xff08;Singleton Pattern&#xff09;指确保一个类在任何情况下都绝对只有一个实例&#xff0c;并提供一个全局访问点&#xff0c;属于创建型设计模式 二、类图 三、通用写法 public class SingletonTest {public static vo…

使用docker安装redis,修改密码

1、搜索镜像 docker search redis 2、拉取镜像 docker pull redis 3.创建容器 前边是宿主机端口 后面是docker使用的端口 docker run --name redis -p 6379:6379 redis-test --requirepass 123456 这里密码设置为123456 4、如何修改密码 现有的redis创建密码或修改密码…

Python学习——字符串

一、字符串 字符串也是基本数据类型&#xff0c;是一个不可变的字符序列&#xff0c;字符串可以用单引号&#xff0c;双引号&#xff0c;三引号进行定义。 字符串的驻留机制&#xff0c;简而言之&#xff0c;就是相同的字符串只保留一个&#xff0c;后续创建相同字符串的时候&a…

【漏洞案例】云上攻防-记一次打穿云上内网的攻防实战

0x01 外网打点&#xff0c;但在云上 目标单位就给出了一个子域名和一个IP&#xff0c;访问给出的子域名就一个登录框&#xff0c;测试了下忘记密码处有用户名枚举&#xff0c;但登录功能做了登录失败处理&#xff0c;知道用户名也无法进行爆破。 登录时会调用api.target.com域…

CICflowmeter安装使用

项目地址&#xff1a;https://github.com/ahlashkari/CICFlowMeter前置条件&#xff1a;maven&#xff0c;winpcap或其他抓包工具&#xff0c;ideal或 Eclipse 用 ideal 打开项目&#xff0c;发现还需要 jnetpcap 包&#xff0c;但是 maven 仓库好像没有&#xff0c;自己编译 …

思科(Cisco)日志分析工具

作为网络安全管理员&#xff0c;实时监控和分析思科防火墙日志至关重要。对于安全网络&#xff0c;管理员需要&#xff1a; 检测各种网络攻击&#xff0c;并查明攻击的来源和类型&#xff08;思科实时日志查看器&#xff09;。跟踪思科防火墙上未经授权的登录尝试。获取有关活…

PHP OA协同办公管理系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP OA协同办公管理系统 是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 代码下载 https://download.csdn.net/download/qq_41221322/87959360https://do…

wireshark抓包工具常用功能

wireshark工具面板如下图所示&#xff1a; 本文记录我比较常用的功能。如果有大佬还用过其他功能麻烦指点一二。 抓包、查找、过滤、数据分析。 菜单工具栏&#xff1a; 设置时间戳格式&#xff1a; 为了方便自己查看&#xff0c;把时间戳格式设置为自己认为比较好看的格式…

ThinkPHP6 请求

ThinkPHP6 请求 前言一、 获取请求二、request其他方法三, 请求信息总结 前言 什么是请求&#xff0c;就是 G E T 和 _GET和 G​ET和_POST类似的东西 一、 获取请求 获取请求可以使用$_GET、$_POST、$_REQUEST、$_SERVER、$_SESSION、$_COOKIE、$_ENV等系统变量&#xff0c;也可…

Android——快速设置Quick Settings Tile(创建自定义快速设置磁贴)

Android——快速设置Quick Settings Tile&#xff08;创建自定义快速设置磁贴&#xff09; 简介快速设置磁贴的使用场景创建磁贴创建自定义图标创建并声明你的tile服务 管理你的tile服务TileService生命周期选择监听模式Active mode (推荐)Non-active mode Tile状态更新Tile处理…

字节跳动云原生大数据平台运维管理实践

云原生大数据是大数据平台新一代架构和运行形态。随着字节跳动内部业务的快速增长&#xff0c;传统大数据运维平台的劣势开始逐渐暴露&#xff0c;如组件繁多&#xff0c;安装运维复杂&#xff0c;与底层环境过度耦合&#xff1b;对业务方来说缺少开箱即用的日志、监控、告警功…