lv5 嵌入式开发-1 进程的创建和回收

2023-09-17 23:54:03

目录

1 进程概念

2 进程内容

3 进程类型

4 进程状态

5 查看进程信息

5.1 相关命令ps top /proc

5.2 相关命令 nice renice 

5.3 相关命令job bg fg 

6 子进程概念

7 子进程创建 – fork

8 父子进程

9 思考

10 进程结束 – exit/_exit

11 进程的回收

11.1 进程回收 – wait

11.2 进程回收 – waitpid


掌握:进程概念、进程包含内容、进制状态、查看进程信息、前后台进程切换、改变进行优先级、创建子进程、父子进程、结束进程

1 进程概念

程序

  • 存放在磁盘上的指令和数据的有序集合(文件)
  • 静态的

进程

  • 执行一个程序所分配的资源的总称
  • 进程是程序的一次执行过程
  • 动态的,包括创建、调度、执行和消亡

2 进程内容

  • BSS段:BSS段通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。 
  • 数据段:数据段通常是指用来存放程序中已初始化的全局变量的一块内存区域
  • 代码段:代码段通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等
  • 堆(heap):堆是用于存放进程运行中被动态分配的内存段,当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
  • 栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
  • 进程控制块(pcb) :进程标识PID 、进程用户 、进程状态、优先级 、文件描述符表

进程放在RAM种,程序放在ROM中(单片机就在flash中)

3 进程类型

  • 交互进程:在shell下启动。以在前台运行,也可以在后台运行 (前台 ./test)
  • 批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
  • 守护进程:和终端无关,一直在后台运行

4 进程状态

运行态:进程正在运行,或者准备运行

等待态:进程在等待一个事件的发生或某种系统资源 (分为可中断和不可中断)

停止态:进程被中止,收到信号后可继续运行

死亡态:已终止的进程,但pcb没有被释放(僵尸态)

5 查看进程信息

5.1 相关命令ps top /proc

ps     查看系统进程快照
top    查看进程动态信息
/proc  查看进程详细信息
ps 命令详细参数:
-e:显示所有进程
-l:长格式显示更加详细的信息
-f 全部列出,通常和其他选项联用

表头

含义

F

进程标志,说明进程的权限,常见的标志有两个:

  • 1:进程可以被复制,但是不能被执行;
  • 4:进程使用超级用户权限;

S

进程状态。进程状态。常见的状态有以下几种:

  1. -D:不可被唤醒的睡眠状态,通常用于 I/O 情况。
  2. -R:该进程正在运行。
  3. -S:该进程处于睡眠状态,可被唤醒。
  4. -T:停止状态,可能是在后台暂停或进程处于除错状态。
  5. -W:内存交互状态(从 2.6 内核开始无效)。
  6. -X:死掉的进程(应该不会出现)。
  7. -Z:僵尸进程。进程已经中止,但是部分程序还在内存当中。
  8. -<:高优先级(以下状态在 BSD 格式中出现)。
  9. -N:低优先级。
  10. -L:被锁入内存。
  11. -s:包含子进程。
  12. -l:多线程(小写 L)。
  13. -+:位于后台。

UID

运行此进程的用户的 ID;

PID

进程的 ID;

PPID

父进程的 ID;

C

该进程的 CPU 使用率,单位是百分比;

PRI

进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行;

NI

进程的优先级,数值越小,该进程越早被执行;

ADDR

该进程在内存的哪个位置;

SZ

该进程占用多大内存;

WCHAN

该进程是否运行。"-"代表正在运行;

TTY

该进程由哪个终端产生;

TIME

该进程占用 CPU 的运算时间,注意不是系统时间;

CMD

产生此进程的命令名;

top    查看进程动态信息

shift +> 后翻页

ps -elf|grep watchdog

shift +< 前翻页

top -p PID  查看某个进程

示例:

ps -elf|grep watchdog
//列出所有进程,找到字符串有watchdog的进程

示例:

ls /proc/

/proc 是一个虚拟文件系统,提供了关于运行中进程和系统内核状态的信息。 

 数字代表某个进程目录

ls /proc/数字

5.2 相关命令 nice renice 

nice   按用户指定的优先级运行进程

nice [-n NI值] 命令
NI 范围是 -20~19。数值越大优先级越低
普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。
普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。
只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。

renice   改变正在运行进程的优先级

renice [优先级] PID

示例: 

nice -n 10 command     //将启动一个新的 shell 进程,并将它的优先级设置为 10。
renice -n 5 -p 1234    //将把进程 ID 为 1234 的进程的优先级设置为 5。

5.3 相关命令job bg fg 

示例:

创建一个循环程序,编译执行

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

int main(int argc, char * argv[])
{
	while(1)
	{
		sleep(1);
	}
	return 0;
}

后台运行两种方式: 

  • 此时控制台输出,在 Linux 控制端,按下 CTRL+Z 的组合键可以将当前正在运行的前台进程挂起,并放入后台,同时返回一个进程挂起的提示信息。这个被挂起的进程会停止执行,但它的状态不会被清除,直到被唤醒或终止。
  • ./a.out &  把test程序后台运行

此时查看进程状态,可以发现状态为T,代表停止。

linux@linux:~/Desktop$ ps -elf|grep a.out
0 T linux     2869  2784  0  80   0 -   506 signal 09:46 pts/1    00:00:00 ./a.out
0 S linux     3226  2784  0  80   0 -  1171 pipe_w 10:18 pts/1    00:00:00 grep --color=auto a.out


//T代表已经a.out程序已经停止
jobs   查看后台进程
bg     将挂起的进程在后台运行
fg      把后台运行的进程放到前台运行,接序号

示例: 

linux@linux:~/Desktop$ jobs
[1]+  Stopped                 ./a.out
linux@linux:~/Desktop$ fg 1
./a.out


linux@linux:~/Desktop$ bg 1
[1]+ ./a.out &
linux@linux:~/Desktop$ ps -elf|grep a.out
0 S linux     2869  2784  0  80   0 -   506 hrtime 09:46 pts/1    00:00:00 ./a.out
0 S linux     3329  2784  0  80   0 -  1171 pipe_w 10:32 pts/1    00:00:00 grep --color=auto a.out
linux@linux:~/Desktop$ jobs
[1]+  Running                 ./a.out &

6 子进程概念

子进程为由另外一个进程(对应称之为父进程)所创建的进程

除了系统启动时的第一个进程(通常是 init 或 systemd或0号进程)外,所有其他进程都是由父进程创建的。这种父子进程的关系形成了一个进程树或进程层级结构。

7 子进程创建 – fork

#include  <unistd.h>
pid_t  fork(void);
  • 创建新的进程,失败时返回-1
  • 成功时父进程返回子进程的进程号,子进程返回0
  • 通过fork的返回值区分父进程和子进程

示例:

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

int main(int argc,char **argv)
{
	pid_t pid;
	printf("before fork\n");
	pid = fork();
	printf("after fork\n");
	printf("pid=%d\n",(int)pid);

}


linux@linux:~/Desktop$ ./a.out 
before fork
after fork
pid=3607                //父打印子进程号
linux@linux:~/Desktop$ 
after fork
pid=0                   //子进程打印0

注意:

1 子进程只执行fork之后的代码

2.父子进程执行顺序是操作系统决定的。

如果需要控制谁先执行,需要加一些延时 

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

int main(int argc,char **argv){
    
    pid_t pid;
    printf("before fork\n");
    pid = fork();
    if(pid>0){
       printf("This is father process\n");
       printf("pid=%d\n",(int)pid);
       printf("father after fork\n");
   }else if(pid==0){
       printf("This is child process\n");
       printf("pid=%d\n",(int)pid);
       printf("child after fork\n");
    }else if(pid<0){
       perror("fork");
       return 0;
    }
   // printf("pid=%d\n",(int)pid);
  //  printf("after fork\n");
  

}

8 父子进程

子进程继承了父进程的内容(从fork以后执行)

父子进程有独立的地址空间,互不影响

若父进程先结束 : 

        -子进程成为孤儿进程,被init进程收养  

        -子进程变成后台进程

若子进程先结束:

       - 父进程如果没有及时回收,子进程变成僵尸进程

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

int main(int argc,char **argv){
    
    pid_t pid;
    printf("before fork\n");
    pid = fork();
    if(pid>0){
       printf("This is father process\n");
       printf("pid=%d\n",(int)pid);
       printf("father after fork\n");
       while(1){
         sleep(1);
         printf("father sleep\n");
       }
   }else if(pid==0){
       printf("This is child process\n");
       printf("pid=%d\n",(int)pid);
       printf("child after fork\n");
       while(1){
          sleep(1);
          printf("child sleep\n");
       }
    }else if(pid<0){
       perror("fork");
       return 0;
    }
   // printf("pid=%d\n",(int)pid);
  //  printf("after fork\n");
  

}

 

kill -9 分别演示父进程和子进程先结束的状态。 

 1代表子进程被init收养了

 0代表僵尸进程

9 思考

子进程从何处开始运行?

答:子进程从fork()函数的调用处开始运行。

父子进程谁先执行?

答:父子进程的执行顺序是不确定的,取决于操作系统的调度算法。在一般情况下,父进程和子进程是并发执行的,可能会交替执行,也可能先执行父进程再执行子进程。

父进程能否多次调用fork? 子进程呢?

答:父进程可以多次调用fork()函数创建多个子进程。每次调用fork()函数都会返回两次,在父进程中返回子进程的进程ID(PID),在子进程中返回0。这样可以通过判断返回值来区分父进程和子进程,并根据需要执行不同的代码逻辑。

子进程也可以调用fork()函数创建自己的子进程,从而形成进程的层次结构,但需要注意,子进程创建的子进程与原始父进程无关。

示例:是否创建了5个进程?

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

int main(){
    pid_t pid;
    int i;    
    for(i=0;i<5;i++){
        pid = fork();
        if(pid<0){
            perror("fork");
            return 0;
        }else if(pid==0){
            printf("child process\n");
            sleep(5);
        }else{
            printf("Father process\n");
            sleep(5);
        }
    }

    sleep(100);


}

实际子进程在fork,父进程也在fork 

 

示例:只让父进程fork

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

int main(){
    pid_t pid;
    int i;    
    for(i=0;i<5;i++){
        pid = fork();
        if(pid<0){
            perror("fork");
            return 0;
        }else if(pid==0){
            printf("child process\n");
            sleep(5);
            break;
        }else{
            printf("Father process\n");
            sleep(5);
        }
    }

    sleep(100);


}

 

 

示例:只让子进程fork

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

int main(){
    pid_t pid;
    int i;    
    for(i=0;i<5;i++){
        pid = fork();
        if(pid<0){
            perror("fork");
            return 0;
        }else if(pid==0){
            printf("child process\n");
            sleep(5);
        }else{
            printf("Father process\n");
            sleep(5);
            break;
        }
    }

    sleep(100);


}

 

10 进程结束 – exit/_exit

#include <stdlib.h> 
#include  <unistd.h>
void  exit(int  status);
void  _exit(int  status);
void  _Exit(int  status); //与小写一样

结束当前的进程并将status返回

exit结束进程时会刷新(流)缓冲区(区别)

示例1:

主函数(main函数)在结束时默认会调用exit()函数。exit()函数用于正常终止程序,并返回一个退出状态给操作系统。不当主函数执行完毕或遇到return语句时,会自动隐式地调用exit()函数。这会导致程序退出,并将返回值作为退出状态码传递给操作系统。

#include <stdio.h>
#include <stdlib.h>  //exit 和_exit引用不一样

int main(void) {
    printf(“this process will exit”);
    exit(0);
    printf(“never  be  displayed”);  //不打印
}


$ ./a.out  
this process will be exit   //加不加exit始终会打印

 

示例2:

#include <stdio.h>
#include <stdlib.h>
  
int main(void) {
    printf(“using exit…\n”);
    printf(“This is the end”);
    exit(0);
}

$ ./a.out  
using exit…
This is the end

示例3:不刷新缓存区的_exit()

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

int main(int argc,char**argv){

   printf("hello world");   //不打印
   _exit(0);
   printf("after exit");    //不打印
   return 0;
}


//什么都不打印

11 进程的回收

子进程结束时由父进程回收

孤儿进程由init进程回收

若没有及时回收会出现僵尸进程

11.1 进程回收 – wait

#include <sys/wait.h>

pid_t wait(int *status); 
  • 成功时返回回收的子进程的进程号;失败时返回EOF
  • 若子进程没有结束,父进程一直阻塞
  • 若有多个子进程,哪个先结束就先回收
  • status 指定保存子进程返回值和结束方式的地址
  • status为NULL表示直接释放子进程PCB,不接收返回值

示例:

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

int main(int argc, char** argv){

   pid_t pid;
   pid_t rpid;
   pid = fork();
   int status;
   if(pid<0){
      perror("fork");
      return 0;
   }
   else if(pid == 0){
       sleep(10);
       printf("child will exit\n");
       exit(2);

   }else if(pid >0){
       rpid = wait(&status);
       printf("Get child status=%x\n",WEXITSTATUS(status));
   }

}

 返回值是多个内容组成的标志位,需要调用宏定义打印返回值

 

查看是否有僵尸进程 

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

int main(int argc, char** argv){

   pid_t pid;
   pid_t rpid;
   pid = fork();
   int status;
   if(pid<0){
      perror("fork");
      return 0;
   }
   else if(pid == 0){
       sleep(10);
       printf("child will exit\n");
       exit(2);

   }else if(pid >0){
       rpid = wait(&status);
       sleep(20);
       printf("Get child status=%x\n",WEXITSTATUS(status));
   }

   while(1){
     sleep(1);
   }

}

 如果去除wait,产生了僵尸进程

进程返回值和结束方式

子进程通过exit / _exit / return 返回某个值(0-255)

父进程调用wait(&status) 回收

  • WIFEXITED(status)       判断子进程是否正常结束
  • WEXITSTATUS(status)  获取子进程返回值
  • WIFSIGNALED(status)  判断子进程是否被信号结束
  • WTERMSIG(status)       获取结束子进程的信号类型

11.2 进程回收 – waitpid

#include  <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int option);
  • 成功时返回回收的子进程的pid或0;失败时返回EOF  
  • pid可用于指定回收哪个子进程或任意子进程  
  • status指定用于保存子进程返回值和结束方式的地址  
  • option指定回收方式,0 或 WNOHANG

参数:

pid

  • pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
  • pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
  • pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
  • pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options

options提了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用 

WNOHANG :若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0

WUNTRACED: 返回终止子进程信息和因信号停止的子进程信息

 wait(wait_stat) 等价于waitpid(-1,wait_stat,0) 

示例:

waitpid(pid, &status, 0);

waitpid(pid, &status, WNOHANG);

waitpid(-1, &status, 0);

waitpid(-1, &status, WNOHANG);
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char** argv){

   pid_t pid;
   pid_t rpid;
   pid = fork();
   int status;
   if(pid<0){
      perror("fork");
      return 0;
   }
   else if(pid == 0){
       sleep(10);
       printf("child will exit\n");
       exit(2);

   }else if(pid >0){
       rpid = wait(&status);
       sleep(20);
       //waitpid(-1,&status,WNOHANG);  //只有一个进程,-1和参数pid效果一样
       waitpid(pid,&status,0);
       printf("Get child status=%x\n",WEXITSTATUS(status));
   }

   while(1){
     sleep(1);
   }

}

 

示例2

waitpid早就执行了,但是exit还会执行,注意这种情况可以考虑waitpid前也加延时函数,晚于exit用于进程回收

更多推荐

WebGL 计算平行光、环境光下的漫反射光颜色

目录光照原理光源类型平行光点光源环境光反射类型漫反射漫反射光颜色计算公式环境反射环境反射光颜色表面的反射光颜色(漫反射和环境反射同时存在时)计算公式平行光下的漫反射根据光线和法线方向计算入射角θ(以便求两者点积:cosθ)归一化法线:表面的朝向一个表面具有两个法向量平面的法向量唯一示例代码——平行光漫反射(Lighte

人工智能在现代科技中的应用和未来发展趋势

人工智能是一种模拟人类智能的技术,包括学习、推理、计算和自动化等方面的能力。在现代科技中,人工智能应用非常广泛,包括以下方面:1.自然语言处理:人工智能可以识别和处理自然语言,使得语音识别、语音合成、文本翻译等变得更加智能。2.图像识别:人工智能可以通过图像识别技术来分析和识别图像中的内容,使得人脸识别、车牌识别、图像

2023 蓝帽杯初赛web&部分取证复现

前言:初赛进线下了,计划着在决赛前突击学习一下取证,但时间还是太紧只看了很多内存取证和手机取证计算机取证和服务器取证没掌握---(不过复赛没考,也算狗运了)目录<1>web-LovePHP(file()函数侧信道攻击)<2>取证(1)APK取证1【APK取证】涉案apk的包名是?[答题格式:com.baid.ccs]2

华为云云耀云服务器L实例评测|redis漏洞回顾 & MySQL数据安全解决 搭建主从集群MySQL & 相关设置

前言最近华为云云耀云服务器L实例上新,也搞了一台来玩,期间遇到过MySQL数据库被攻击的情况,数据丢失,还好我有几份备份,没有造成太大的损失;后来有发现Redis数据库被攻击的情况,加入了redis密码初步解决问题。总之就是各种遭受毒打。。。本篇博客回顾Redis的未授权访问漏洞,介绍MySQL主从集群的搭建,以及相关

短视频搭建矩阵源码--短视频矩阵源码搭建

短视频矩阵系统是一种通过将短视频内容进行分类、管理和展示的系统。它可以将用户上传或者选择的短视频按照不同的特定标签进行分类和管理,用户可以根据自己的兴趣和需求选择观看不同类别的短视频。短视频矩阵源码的开发部署其实并不难,主要依托于抖音平台各种开放权限进行研发,市面上常见的源码功能构建也是大同小异,主要处理还在于细节及产

Delphi - Record记录和变体记录

//Integer类型刚好是4个字节,ShortInt类型是1个字节,但是Windows中内存是4字节分配,//所以这里其实还是4个字节,用SizeOf可以看到这个record的大小是8字节,这样虽然浪//费了空间,但是加快了速度(Windows内存分配中的边界对齐原理)TPerson=recordAge:Intege

深入了解接口测试:Postman 接口测试指南

在现代软件开发生命周期中,接口测试是一个至关重要的部分。使用Postman这一工具,可以轻松地进行接口测试。以下是一份简单的使用教程,帮助你快速上手。安装Postman首先,你需要在电脑上安装Postman。你可以从官网上下载并安装它。安装完成后,你可以打开应用并创建一个新的请求。创建请求在Postman应用中,你可以

OSI模型与数据的封装

1、OSI模型上层||七层模型四层模型||应用层|表示层应用层http/ftp/ssh/ftps|会话层-----------------------------------------------------------------------|·传输层传输层tcp/udp----------------------

推荐几款优秀的项目报表软件

项目报表在项目工作中扮演着重要的角色,它是领导和客户了解项目进况的直接途径。有需求就会有市场,为解决传统报表制作复杂困难的问题,专业报表工具应运而生。一款好用的项目报表软件可以帮助项目团队快速产出项目报表,实现数据可视化,进行高效的项目数据分析。有什么好用的项目报表软件?在琳琅满目的项目报表软件产品中,有什么好用的项目

Linux开发工具之项目自动化构建工具-make/Makefile

make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建makefile带来的好处就是自动化编译,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率下面来看一个实例:在Makefile文件里可以如上图一样g++/gcc编译代码一步到位,也可以如下图一样,一步一步拆

滨州ITSS认证流程,认证条件

ITSS认证流程,认证条件一、ITSS的意义ITSS认证——信息技术服务标准,是在工业和信息化部、国家标准化委的领导和支持下,由ITSS工作组研制的一套IT服务领域的标准库和一套提供IT服务的方法论。ITSS认证-信息技术服务标准是一套成体系和综合配套的信息技术服务标准库,全面规范了IT服务产品及其组成要素,用于指导实

热文推荐