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

2023-10-27

目录

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用于进程回收

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Qt 嵌入式触摸屏 QMouseEvents 在收到 MouseButtonRelease 之前未收到

    我在带有触摸屏的小型 ARM 嵌入式 Linux 设备上使用 Qt 4 8 3 我的触摸屏配置了 tslib 并对其进行了校准 因此 etc 中有一个 pointcal 文件 我的触摸事件的位置工作得很好 但无论如何我都会在鼠标按下或鼠标释
  • 如何在 Vim 中突出显示 Bash 脚本?

    我的 Vim 编辑器自动突出显示 PHP 文件 vim file php HTML 文件 vim file html 等等 但是当我输入 vim file在里面写一个Bash脚本 它不会突出显示它 我如何告诉 Vim 将其突出显示为 Bas
  • 应用程序中两个不同版本的库

    考虑一个场景 其中有两个不同版本的共享库 考虑 A 1 so 链接到 B so A 2 so 链接到 C so 现在 B so 和 C so 都链接到 d exe 当 B so 想要调用 A 1 so 中的函数时 它最终会调用 A 2 so
  • Linux中如何避免sleep调用因信号而中断?

    我在 Linux 中使用实时信号来通知串行端口中新数据的到达 不幸的是 这会导致睡眠呼叫在有信号时被中断 有人知道避免这种行为的方法吗 我尝试使用常规信号 SIGUSR1 但我不断得到相同的行为 来自 nanosleep 联机帮助页 nan
  • 如何在C(Linux utf8终端)中打印“盒子抽屉”Unicode字符?

    我正在尝试显示 方框图范围 2500 257F 中的 Unicode 字符 它应该是标准 utf8 Unicode 标准 版本 6 2 我根本做不到 我首先尝试使用旧的 ASCII 字符 但 Linux 终端以 utf8 显示 并且没有显示
  • 使用脚本检查 git 分支是否领先于另一个分支

    I have branch1 and branch2我想要某种 git branch1 isahead branch2 这将显示如果branch1已承诺branch2没有 也可能指定这些提交 我无法检查差异原因branch2 is在之前br
  • Vagrant 遇到问题 - “404 - 未找到”

    我正在尝试使用 Vagrant 制作一个 LAMP 盒子 有人告诉我它使用起来非常简单 我对网络和虚拟机完全陌生 对 Linux Ubuntu 的经验也很少 我目前已尝试按照官方文档页面上的教程进行操作 http docs vagrantu
  • 在 scapy 中通过物理环回发送数据包

    我最近发现了 Scapy 它看起来很棒 我正在尝试查看 NIC 上物理环回模块 存根上的简单流量 但是 Scapy sniff 没有给出任何结果 我正在做的发送数据包是 payload data 10 snf sniff filter ic
  • bash 将输出重定向到文件,但结果不完整

    重定向命令输出的问题已经被问过很多次了 但是我有一个奇怪的行为 我使用的是 bash shell debian 版本 4 3 30 1 release 并尝试将输出重定向到文件 但并非所有内容都记录在文件中 我尝试运行的 bin 文件是 l
  • Ruby:在 Ubuntu 上安装 rmagick

    我正在尝试在 Ubuntu 10 04 上安装 RMagick 看起来here https stackoverflow com questions 1482823 is there an easy way to install rmagic
  • 链接错误:命令行中缺少 DSO

    我对 Linux 使用 Ubuntu 14 04 LTS 64 位 相当陌生 来自 Windows 并且正在尝试移植我现有的 CUDA 项目 当通过链接时 usr local cuda bin nvcc arch compute 30 co
  • 使用 libusb 输出不正确

    我用libusb编写了一个程序 我怀疑输出是否正确 因为所有条目都显示相同的供应商和产品 ID 以下是代码 include
  • 嵌入清单文件以要求具有 mingw32 的管理员执行级别

    我正在 ubuntu 下使用 i586 mingw32msvc 交叉编译应用程序 我很难理解如何嵌入清单文件以要求 mingw32 具有管理员执行级别 对于我的例子 我使用了这个hello c int main return 0 这个资源文
  • 在 docker 中重定向命令输出

    我想为我的服务器做一些简单的日志记录 它是一个在 Docker 容器中运行的小型 Flask 应用程序 这是 Dockerfile Dockerfile FROM dreen flask MAINTAINER dreen WORKDIR s
  • 在内核代码中查找函数的最佳方法[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我开始浏览内核代码 遇到的一件事是如何跟踪函数调用 结构定义等 有没有一种好的方法可以快速跳转到函数定义并退出 我尝试过 Source N
  • 仅使用containerd(不使用Docker)修剪容器镜像

    如果我刚刚containerd安装在 Linux 系统上 即 Docker 是not安装 如何删除未使用的容器映像以节省磁盘空间 Docker 就是这么方便docker system prune https docs docker com
  • 执行命令而不将其保留在历史记录中[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 在进行软件开发时 经常需要在命令行命令中包含机密信息 典型示例是将项目部署到服务器的凭据设置为环境变量 当我不想将某些命令存储在命令历史记
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String
  • ALSA:snd_pcm_writei 调用时缓冲区不足

    当运行我最近从灰烬中带回来的旧程序时 我遇到了缓冲区不足的情况 该程序将原始声音文件完全加载到内存中 2100 字节长 525 帧 并准备 ALSA 进行输出 44 1khz 2 通道 有符号 16 位 if err snd pcm set
  • 为什么 Linux 没有 DirectX API?

    在考虑现代显卡的 Windows 系统上 DirectX API 的驱动程序端实现时 我想知道为什么此实现在非 Windows 系统 尤其是 Linux 上不可用 由于明显缺乏此功能 我只能假设有一个我无视的充分理由 但在我的原始理解中 我

随机推荐