IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux篇10进程控制 -> 正文阅读

[系统运维]Linux篇10进程控制

1.进程创建

fork()函数为什么要给子进程返回0,给父进程返回子进程的pid

父子进程立场:父进程不需要标识,子进程需要标识,因为父进程可以有多个子进程,而子进程只有一个父进程。子进程是要执行任务的,父进程需要通过子进程的pid来区分子进程,而子进程不需要

如何理解fork有两个返回值?

在fork()还没有return pid的时候,子进程就已经创建好了,return pid被执行了两次。

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

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中(也就是fork()还没有返回子进程就已经被创建出来了)
  • fork返回,开始调度器调度

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

写时拷贝

? 通常,父子代码共享,父子不在写入时,数据也是共享的。当任意一方试图写入,便以写时拷贝的方式各自一份副本。这里所说的共享,是指父子进程对应的页表指向的是同一块物理内存。

为什么要写实拷贝呢?

  • 进程具有独立性

  • 那么为什么不在创建子进程的时候就分开呢?

    因为子进程未必使用父进程的所有数据,直接将父进程的数据给子进程拷贝一份,而子进程又不用,这岂不是资源浪费。所以,在子进程需要修改数据的时候,再进行写时拷贝。这叫做按需分配。另外,写实拷贝也做到了延时分配,要知道,一个子进程创建出来未必要立刻调度,等进程调度的时候在把空间给他即可,可以高效使用任何内存空间

2.进程终止

进程退出的场景有如下三种:

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

为什么main函数要有返回值,返回值给了谁?

  • 当你运行程序形成进程的时候,你是想让该进程完成某种任务,并且你需要知道任务完成的结果。main函数的返回值是进程退出码。0代表成功。!0代表失败,这是人为定义的。这个返回值是给操作系统。我们通过进程退出码来判断任务的完成情况。

我们可以使用echo $? 指令打印出最近一次进程退出时的退出码

image-20220629215611849

如果退出码不是0,那么失败的原因有多种。每一种退出码都有对应的字符串含义,帮助用户确认,任务失败的原因。下面我们利用下面代码测试一下Linux下不同退出码代表的含义

 #include <stdio.h>
  2 #include <string.h>
  3 int main()
  4 {
  5   int i = 0;
  6   for(i = 0;i < 150; i++)                     
  7   {
  8     printf("%d: %s\n", i, strerror(i));
  9   }
 10   return 0;
 11 }

运行结果如下,共134条错误码

image-20220629220524001

image-20220629220615819

进程退出的常见方法:

  1. 从main返回

    只有main函数中的return代表进程退出

  2. 调用exit

  3. _exit

    对于exit和_exit,在代码的任何地方,调用exit都代表进程退出!他们的差别是,exit会释放进程曾经占用的资源,比如缓冲区。而_exit直接终止进程,不会做任何的收尾工作!

    为了更好地了解这一点,我们来用下面这段代码测试一下

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
      printf("hello world");
      sleep(3);
     // exit(0);
      _exit(0);                                                 
      return 0;
    }
     
    

    这段代码如果使用exit,在进程终止之后会将缓冲区中的hello world打印到屏幕上,而_exit不会

进程如果是异常退出了,那么退出码将没有任何意义。

进程终止了,操作系统做了什么呢?

  • 释放曾经申请的数据结构,释放曾经申请的内存,从各种队列等数据结构中移除

3.进程等待

  • 进程等待通常是由父进程完成!为什么要有进程等待呢?原因如下:

    • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
    • 父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
    • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

    进程等待的方法

    • wait方法

      #include<sys/types.h>
      #include<sys/wait.h>
      pid_t wait(int*status);
      返回值:
      成功返回被等待进程pid,失败返回-1。
      参数:
      输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
      
        1 #include <stdio.h>                                                                                
        2 #include <string.h>
        3 #include <unistd.h>
        4 #include <stdlib.h>
        5 #include <sys/types.h>
        6 #include <sys/wait.h>
        7 
        8 int main()
        9 {
       10   pid_t id = fork();
       11   if(id == 0)
       12   {
       13     //child
       14     int count = 0;
       15     while(count < 10)
       16     {
       17       printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
       18       count++;
       19       sleep(1);
       20     }
       21     exit(0);
       22   }
       23   else 
       24   {
       25     //father
       26     printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
       27     pid_t ret = wait(NULL);
       28     if(ret >= 0)
       29     {
       30       printf("wait child success! %d\n", ret);
       31     }
       32     printf("father runing...\n");                                                                                                                        
       33     sleep(10);
       34   }
       35   return 0;
       36 }
      
      
      

      子进程运行期间,父进程wait的时候,父进程就是在单纯的等子进程退出——阻塞等待

      关于阻塞和非阻塞:

      ? 阻塞就是单纯的一直等,非阻塞也是等,不过不会因为条件不满足而卡住。

      父子进程谁先运行不确定,但是wait之后,大部分都是子进程先退出,父进程读取子进程退出信息,父进程才退出

      进程等待成功,不意味着子进程运行成功。想要知道子进程是否运行成功,需要看子进程的退出码

    • 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

      • 是一个整数,我们只关注该整数的低16位

        image-20220630060415676

      进程异常的时候,本质是进程运行的时候出现了某种错误,导致进程收到信号

4.进程程序替换

  • 替换原理

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

  • 六种exec开头的替换函数

    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[]);
    int execve(const char *path, char *const argv[], char *const envp[]);
    

    l – list v – vector p:有p自己动搜索环境变量PATH e:表示自己维护环境变量

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

    下面这段代码是对前5个函数的测试

    其实前五个函数的底层都是第六个函数

    #include <stdio.h>                                                                                                                                     
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <stdlib.h>
    int main()
    {
      pid_t id = fork();
      if(id == 0)
      {
        printf("I am a process\n");
        sleep(2);
       // execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);
       // execlp("ls", "ls", "-a", "-i", "-l", NULL); 
        // char* myargv[] = {
        // "ls",
        // "-a",
        // "-i",
        // "-l",
        //  NULL
        // };
        execv("/usr/bin/ls", myargv);
       // execvp("ls", myargv);   
         char* myenv[] = {
           "MYENV=HAHAHA",
           NULL
         };
         execle("./mycmd", "mycmd", NULL, myenv);
         exit(11);
      }
    
      int status = 0;
      pid_t ret = waitpid(id, &status, 0);
      if(ret > 0)
      {
        printf("wait success\n");                                                                                                                          
        printf("signal: %d\n", status & 0x7F);
        printf("exit code: %d\n", WEXITSTATUS(status));
      }
    
      return 0;
    }
    

5.简易shell

#include <stdio.h>                                                                                                                                     
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#define LEN 1024
#define NUM 32
int main()
{
  char* myarg[NUM];
  char cmd[LEN];
  while(1)
  {
   printf("[ls@VM-20-7-centos shell]$ ");
   fgets(cmd, LEN, stdin);
   //把字符串拆开 得到一个一个的子串
   cmd[strlen(cmd)-1] = '\0';

   myarg[0] = strtok(cmd, " ");
   int i = 1;
   while(myarg[i] = strtok(NULL, " "))
   {
     i++;
   }

   pid_t id = fork();
   if(id == 0)
   {
     //child
     execvp(myarg[0], myarg);
     exit(11);
   }

   int status = 0;
   pid_t ret = waitpid(id, &status, 0);
   if(ret > 0)
   {
     printf("exit code: %d\n", WEXITSTATUS(status));
   }
  }
  return 0;
}   
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-07-04 23:22:23  更:2022-07-04 23:25:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 12:51:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码