【嵌入式开发之并发程序设计】进程的创建和回收

进程概念(程序和进程)

程序和进程

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

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

进程内容

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

进程类型

在Linux系统中,进程是系统运行中的程序,它是资源分配的基本单位。Linux提供了多种类型的进程,但三大主要类型的进程包括: 

进程状态

  • 运行态:进程正在运行,或者准备运行
  • 等待态:进程在等待一个事件的发生或某种系统资源 可中断 不可中断
  • 停止态:进程被中止,收到信号后可继续运行
  • 死亡态:已终止的进程,但pcb没有被释放

查看进程信息命令 

查看系统进程快照:ps命令

ps 命令详细参数:

  • -e:显示所有进程
  • -l:长格式显示更加详细的信息
  • -f: 全部列出,通常和其他选项联用
    • 表头含义
      F

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

      1:进程可以被复制,但是不能被执行;

      4:进程使用超级用户权限;

      S

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

      -D:不可被唤醒的睡眠状态,通常用于 I/O 情况。

      -R:该进程正在运行。

      -S:该进程处于睡眠状态,可被唤醒。

      -T:停止状态,可能是在后台暂停或进程处于除错状态。

      -W:内存交互状态(从 2.6 内核开始无效)。

      -X:死掉的进程(应该不会出现)。

      -Z:僵尸进程。进程已经中止,但是部分程序还在内存当中。

      -<:高优先级(以下状态在 BSD 格式中出现)。

      -N:低优先级。

      -L:被锁入内存。

      -s:包含子进程。

      -l:多线程(小写 L)。

      -+:位于后台。

      UID

      运行此进程的用户的 ID;

      PID

      进程的 ID;

      PPID

      父进程的 ID;

      C

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

      PRI

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

      NI

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

      ADDR

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

      SZ

      该进程占用多大内存;

      WCHAN

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

      TTY

      该进程由哪个终端产生;

      TIME

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

      CMD

      产生此进程的命令名;

      zdj@zdj-ubuntu64:/mnt/hgfs/share/study/Level6/Day1$ ps -elf
      F S UID         PID   PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
      4 S root          1      0  0  80   0 - 56398 -      20:22 ?        00:00:02 /sbin/init splash
      1 S root          2      0  0  80   0 -     0 -      20:22 ?        00:00:00 [kthreadd]
      1 I root          4      2  0  60 -20 -     0 -      20:22 ?        00:00:00 [kworker/0:0H]
      1 I root          6      2  0  60 -20 -     0 -      20:22 ?        00:00:00 [mm_percpu_wq]
      1 S root          7      2  0  80   0 -     0 -      20:22 ?        00:00:00 [ksoftirqd/0]
      1 I root          8      2  0  80   0 -     0 -      20:22 ?        00:00:00 [rcu_sched]
      1 I root          9      2  0  80   0 -     0 -      20:22 ?        00:00:00 [rcu_bh]
      1 S root         10      2  0 -40   - -     0 -      20:22 ?        00:00:00 [migration/0]
      5 S root         11      2  0 -40   - -     0 -      20:22 ?        00:00:00 [watchdog/0]
      1 S root         12      2  0  80   0 -     0 -      20:22 ?        00:00:00 [cpuhp/0]
      1 S root         13      2  0  80   0 -     0 -      20:22 ?        00:00:00 [cpuhp/1]
      5 S root         14      2  0 -40   - -     0 -      20:22 ?        00:00:00 [watchdog/1]
      1 S root         15      2  0 -40   - -     0 -      20:22 ?        00:00:00 [migration/1]
      1 S root         16      2  0  80   0 -     0 -      20:22 ?        00:00:00 [ksoftirqd/1]
      1 I root         18      2  0  60 -20 -     0 -      20:22 ?        00:00:00 [kworker/1:0H]
      1 S root         19      2  0  80   0 -     0 -      20:22 ?        00:00:00 [cpuhp/2]
      5 S root         20      2  0 -40   - -     0 -      20:22 ?        00:00:00 [watchdog/2]
      1 S root         21      2  0 -40   - -     0 -      20:22 ?        00:00:00 [migration/2]
      1 S root         22      2  0  80   0 -     0 -      20:22 ?        00:00:00 [ksoftirqd/2]
      1 I root         24      2  0  60 -20 -     0 -      20:22 ?        00:00:00 [kworker/2:0H]
      1 S root         25      2  0  80   0 -     0 -      20:22 ?        00:00:00 [cpuhp/3]

      查看进程动态信息:top命令

    • top - 22:41:34 up  2:18,  1 user,  load average: 0.00, 0.00, 0.00
      Tasks: 263 total,   1 running, 187 sleeping,   0 stopped,   0 zombie
      %Cpu(s):  0.2 us,  0.3 sy,  0.0 ni, 99.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
      KiB Mem :  4013244 total,  1631044 free,  1066796 used,  1315404 buff/cache
      KiB Swap:  1942896 total,  1942896 free,        0 used.  2687756 avail Mem 
      
         PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                      
        1069 zdj       20   0  502236  95164  47452 S   0.7  2.4   0:05.74 Xorg                                         
        1414 zdj       20   0 3619388 211172  85920 S   0.3  5.3   0:17.66 gnome-shell                                  
        3083 zdj       20   0   45976   4396   3656 R   0.3  0.1   0:00.06 top                                          
           1 root      20   0  225592   9260   6640 S   0.0  0.2   0:02.08 systemd                                      
           2 root      20   0       0      0      0 S   0.0  0.0   0:00.02 kthreadd                                     
           4 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 kworker/0:0H                                 
           6 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 mm_percpu_wq                                 

      shift +> :后翻页

      shift +< :前翻页

      top -p PID:查看某个进程

    • 查看进程动态信息:htop命令

    • htop命令是top的替代命令,比top命令更加直观,第一次运行需要安装,以下两个命令,任选其一运行安装即可:
    • sudo snap install htop  # version 3.3.0, or
      sudo apt  install htop
    • htop命令运行结果:
    • 改变进程优先级:nice命令

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

nice [-n NI值] 命令 NI 范围是 -20~19。数值越大优先级越低

普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。

普通用户只能调高 NI 值,而不能降低。

如原本 NI 值为 0,则只能调整为大于 0。

只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。 

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

renice [优先级] PID

查看后台进程 :jobs命令

jobs:查看后台进程

bg:将挂起的进程在后台运行

fg:把后台运行的进程放到前台运行

快捷键ctrl+z:把运行的前台进程转为后台并停止。

./test & :把test程序后台运行

子进程的概念及创建

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

子进程的创建:fork函数

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

int main(int argc, const 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");
	return 0;
}

创建新的进程,失败时返回-1。

成功时父进程返回子进程的进程号,子进程返回0。

通过fork的返回值区分父进程和子进程,返回值大于0为父进程,等于0为子进程,小于零为出错。

运行结果

$ gcc -o fork_t fork_t.c 
$ ./fork_t
before fork
This is father process
pid = 3451
This is child process
father after fork
pid = 3451
after fork
pid = 0
child after fork
pid = 0
after fork

注意事项

  • 子进程只执行fork之后的代码;
  • 父子进程执行顺序是操作系统决定的,所以输出结果不是按先父进程后子进程的顺序。 

父子进程之间的关系

  • 子进程继承了父进程的内容

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

  • 若父进程先结束:子进程成为孤儿进程,被init进程收养,子进程变成后台进程

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

int main(int argc, const 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;
	}
	return 0;
}

程序运行结果

$ ./fork_t
before fork
This is child process
This is father process
pid = 0
pid = 3517
child after fork
father after fork
child sleep
father sleep
child sleep
father sleep

子进程和父进程都在运行,但是顺序随机。通过ps命令查看进程PID,PID=3608的进程为父进程。

root@zdj-ubuntu64:~# ps -elf|grep fork_t
0 S zdj        3252   2548  0  80   0 - 14794 poll_s 7月22 pts/0   00:00:01 vi fork_t.c
0 S root       3608   3600  0  80   0 -  1129 hrtime 00:53 pts/1    00:00:00 ./fork_t
1 S root       3609   3608  0  80   0 -  1129 hrtime 00:53 pts/1    00:00:00 ./fork_t
0 S root       3611   3591  0  80   0 -  4046 pipe_w 00:54 pts/2    00:00:00 grep --color=auto fork_t

利用kill命令杀掉父进程:

root@zdj-ubuntu64:~# kill -9 3608
root@zdj-ubuntu64:~# ps -elf|grep fork_t
0 S zdj        3252   2548  0  80   0 - 14794 poll_s 7月22 pts/0   00:00:01 vi fork_t.c
1 S root       3609   1033  0  80   0 -  1129 hrtime 00:53 pts/1    00:00:00 ./fork_t
0 S root       3613   3591  0  80   0 -  4046 pipe_w 00:54 pts/2    00:00:00 grep --color=auto fork_t

 杀掉父进程之后,只有子进程运行,且此时无法用Ctrl+C杀死子进程,只能再执行kill命令杀死子进程,因为父进程被杀死后,子进程变成了后台进程。

child sleep
father sleep
Killed
root@zdj-ubuntu64:/mnt/hgfs/share/study/Level6/Day1# child sleep
child sleep
child sleep
child sleep
^C
root@zdj-ubuntu64:/mnt/hgfs/share/study/Level6/Day1# child sleep
child sleep
child sleep
child sleep
  • 若子进程先结束:如果没有及时回收,子进程变成僵尸进程

再次运行程序: 

$ ./fork_t
before fork
This is father process
pid = 3660
This is child process
father after fork
pid = 0
child after fork
child sleep
father sleep
child sleep

父进程(3659)和子进程(3660):

zdj@zdj-ubuntu64:~$ ps -elf|grep fork_t
0 S zdj        3252   2548  0  80   0 - 14794 poll_s 7月22 pts/0   00:00:01 vi fork_t.c
0 S zdj        3659   3650  0  80   0 -  1129 hrtime 01:03 pts/1    00:00:00 ./fork_t
1 S zdj        3660   3659  0  80   0 -  1129 hrtime 01:03 pts/1    00:00:00 ./fork_t
0 S zdj        3662   3630  0  80   0 -  4046 pipe_w 01:03 pts/2    00:00:00 grep --color=auto fork_t

 用kill命令杀死子进程之后,子进程变成僵尸进程:

zdj@zdj-ubuntu64:~$ sudo kill -9 3660
zdj@zdj-ubuntu64:~$ ps -elf|grep fork_t
0 S zdj        3252   2548  0  80   0 - 14794 poll_s 7月22 pts/0   00:00:01 vi fork_t.c
0 S zdj        3659   3650  0  80   0 -  1129 hrtime 01:03 pts/1    00:00:00 ./fork_t
1 Z zdj        3660   3659  0  80   0 -     0 -      01:03 pts/1    00:00:00 [fork_t] <defunct>
0 S zdj        3666   3630  0  80   0 -  4046 pipe_w 01:04 pts/2    00:00:00 grep --color=auto fork_t

fork实例1:用fork函数生成1个父进程和5个子进程

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

int main(int argc, const char *argv[])
{
	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);
		}
	}
	while(1) {
		sleep(1);
	}
	return 0;
}

 运行结果:

$ ./fork2_t
father process
child process
father process
child process
father process
child process
father process
child process
father process
child process

生成的进程:

zdj@zdj-ubuntu64:~$ ps -elf|grep fork2_t
0 S zdj        3694   2548  0  80   0 - 14791 poll_s 01:16 pts/0    00:00:01 vi fork2_t.c
0 S zdj        3870   3650  0  80   0 -  1129 hrtime 01:31 pts/1    00:00:00 ./fork2_t
1 S zdj        3871   3870  0  80   0 -  1129 hrtime 01:31 pts/1    00:00:00 ./fork2_t
1 S zdj        3872   3870  0  80   0 -  1129 hrtime 01:31 pts/1    00:00:00 ./fork2_t
1 S zdj        3873   3870  0  80   0 -  1129 hrtime 01:31 pts/1    00:00:00 ./fork2_t
1 S zdj        3874   3870  0  80   0 -  1129 hrtime 01:31 pts/1    00:00:00 ./fork2_t
1 S zdj        3875   3870  0  80   0 -  1129 hrtime 01:31 pts/1    00:00:00 ./fork2_t
0 R zdj        3879   3630  0  80   0 -  4046 pipe_w 01:32 pts/2    00:00:00 grep --color=auto fork2_t

进程3871,3872,3873,3874,3875都是进程3870的子进程。

进程的结束

进程结束:exit函数

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

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

return 和exit的区别

main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。

_exit()是不刷新(流)缓冲区的。

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

int main(int argc, const char *argv[])
{
	printf("hello world");
	exit(0);
	printf("after exit");

	return 0;//隐式调用exit()
}

运行结果:

$ ./exit
hello world

即使没有加换行符'\n',也会输出第一个printf的内容,exit会刷新(流)缓存区。

main函数中,即使没有调用exit,printf中的内容也会输出,是因为return中会隐式调用exit()。

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

int main(int argc, const char *argv[])
{
	printf("hello world");
	//exit(0);
	//printf("after exit");

	return 0;//隐式调用exit()
}

运行结果

hello world

_exit()不刷新(流)缓存区,所以下面的代码不会有任何输出:

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

int main(int argc, const char *argv[])
{
	printf("hello world");
	_exit(0);

	return 0;
}

进程的回收

  • 子进程结束时由父进程回收
  • 孤儿进程由init进程回收
  • 若没有及时回收会出现僵尸进程

进程回收函数:wait()

  #include <sys/wait.h>
  pid_t wait(int *status); 
 返回值

成功时返回回收的子进程的进程号;失败时返回EOF

若子进程没有结束,父进程一直阻塞

若有多个子进程,哪个先结束就先回收

回收实例

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

int main(int argc, const char *argv[])
{
	pid_t pid;
	pid_t rpid;

	//生成子进程
	pid = fork();

	//status保存子进程返回值和结束方式
	int status;

	if (pid < 0) {
		perror("fork");
		return 0;

		//pid=0的进程为子进程
	} else if (pid == 0) {
		sleep(1);
		printf("child will exit\n");
		exit(2);
	} else if (pid > 0) {
		//status保存子进程的返回值和结束方式
		rpid = wait(&status);
		//WEXITSTATUS(status)获取子进程的返回值
		printf("Get child status = %x\n", WEXITSTATUS(status));
	} 

	while(1) {
		sleep(1);
	}

	return 0;
}

 status 指定保存子进程返回值和结束方式的地址。

 status 为NULL表示直接释放子进程PCB,不接收返回值。

子进程通过exit / _exit / return 返回某个值(0-255),这个实例中是通过exit(2)。

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

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

进程回收函数: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两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用

options含义

WNOHANG

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

WUNTRACED

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

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

  • 27
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个嵌入式新手的成长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值