前言:
好久不见呀^^,失踪了几天,今天继续回来写文了;这几天一直在研究野生狗奶:
这一次我们要讲的是进程控制,但是在讲进程控制之前,我们需要先讲讲两个模块,一个是虚拟内存,一个是命令行参数和环境变量;讲完这两个模块之后,我们再进入进程控制的四个子问题。
正文:
demo1:命令行参数
概念:
程序在命令行执行时,在程序名后面跟的一串字符串,就是命令行参数,供程序内部读取使用
其中:
C语言中对应的两个参数分别是:
int main(int argc,char*argv[])
argc:参数个数,整形,包含程序本身,至少为1;
argv:字符串指针数组,保存每一个参数字符串
argv[0]:程序本身名字
argv[1]:第一个命令行参数
argv[2]:第二个命令行参数
。。。
argv[argc]:NULL,作为数组结束的标记
演示:
[tsx@VM-0-10-centos 811]$ ls -l -a
total 8
drwxrwxr-x 2 tsx tsx 4096 Aug 11 10:09 .
drwx—— 9 tsx tsx 4096 Aug 11 10:09 ..
-rw-rw-r– 1 tsx tsx 0 Aug 11 10:14 haha.c
我在shell中输入ls -a -l ,它会被shell当成一个字符串,shell将其以空格分隔,拆分为一个一个子串,分别放入argv数组中
我们这边写了一个演示代码:
#include<stdio.h>
2 int main(int argc,char* argv[])
3 {
4 for(int i=0;i<argc;i++)
5 {
6 printf("我是argv[%d],我是:%s\\n",i,argv[i]);
7 }
8 return 0;
9 }
然后我直接运行它:
[tsx@VM-0-10-centos 811]$ ./haha
我是argv[0],我是:./haha
我在后面写一些参数:
[tsx@VM-0-10-centos 811]$ ./haha hehe xixi
我是argv[0],我是:./haha
我是argv[1],我是:hehe
我是argv[2],我是:xixi
注意:
我在上面写的代码:
[tsx@VM-0-10-centos 811]$ gcc -o haha haha.c
haha.c: In function ‘main’:
haha.c:4:3: error: ‘for’ loop initial declarations are only allowed in C99 mode
for(int i=0;i<argc;i++)
^
haha.c:4:3: note: use option -std=c99 or -std=gnu99 to compile your code
[tsx@VM-0-10-centos 811]$ gcc -o haha haha.c -std=c99
[tsx@VM-0-10-centos 811]$ ./haha
我直接编译的话,会报错的,原因:
C89(老标准):变量必须写在代码块最开头,不能写在for()括号里面
该这样写:
int i;
for(i=0; i<argc; i++)
C99 及以后标准:允许直接在for( ; ; )的第一个位置定义变量:
这样写:
for(int i=0;i<argc;i++)
所以有两种解决办法,一是按老标准写:

第二:
gcc -o haha haha.c -std=c99
让编译器使用c99标准
demo2:环境变量
引言:
进程运行时,其内部会有两张表:
一张表是命令行参数表,一张表是环境变量表
它们存放的位置,一会我们讲虚拟地址的时候细说
我们今天讲环境变量,主要就是要认识PATH
概念:
环境变量⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数
如何看到:
在shell直接获取:
①
[tsx@VM-0-10-centos 811]$ env
XDG_SESSION_ID=55445
HOSTNAME=VM-0-10-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=111.52.127.5 26318 22
SSH_TTY=/dev/pts/0
USER=tsx
LD_LIBRARY_PATH=:/home/tsx/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/tsx
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tsx/.local/bin:/home/tsx/bin
GOPROXY=https://mirrors.tencent.com/go,direct
PWD=/home/tsx/811
LANG=en_US.utf8
SHLVL=1
HOME=/home/tsx
LOGNAME=tsx
SSH_CONNECTION=111.52.127.5 26318 10.2.0.10 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; history -a; printf "\\033]0;%s@%s:%s\\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1001
HISTTIMEFORMAT=%F %T
_=/usr/bin/env
OLDPWD=/home/tsx
只需输入env,即可获得所有环境变量;
②
[tsx@VM-0-10-centos 811]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tsx/.local/bin:/home/tsx/bin
打印单个环境变量,用echo $变量名
用语言获取:(我们暂时默认C语言)
①:main函数传参:
#include<stdio.h>
int main(int argc,char* argv[],char*env[])
{
for(int i=0;env[i];i++)
{
printf("%s\\n",env[i]);
}
return 0;
}
我们可以得到所有的环境变量:
[tsx@VM-0-10-centos 811]$ vim get.c
[tsx@VM-0-10-centos 811]$ gcc -o get get.c -std=c99
[tsx@VM-0-10-centos 811]$ ./get
XDG_SESSION_ID=55445
HOSTNAME=VM-0-10-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=111.52.127.5 26318 22
SSH_TTY=/dev/pts/0
USER=tsx
LD_LIBRARY_PATH=:/home/tsx/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/tsx
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tsx/.local/bin:/home/tsx/bin
GOPROXY=https://mirrors.tencent.com/go,direct
PWD=/home/tsx/811
LANG=en_US.utf8
SHLVL=1
HOME=/home/tsx
LOGNAME=tsx
SSH_CONNECTION=111.52.127.5 26318 10.2.0.10 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; history -a; printf "\\033]0;%s@%s:%s\\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1001
HISTTIMEFORMAT=%F %T
_=./get
OLDPWD=/home/tsx
与上文的env方法打出来也是一致的
②:通过第三方变量environ获取:
int main()
{
extern char** environ;
for(int i=0;environ[i];i++)
{
printf("%s\\n",environ[i]);
}
return 0;
}
这个变量实际上在c标准库中定义,所以我们用extern即可声明;
结果:
[tsx@VM-0-10-centos 811]$ cat get.c
#include<stdio.h>
int main()
{
extern char** environ;
for(int i=0;environ[i];i++)
{
printf("%s\\n",environ[i]);
}
return 0;
}
[tsx@VM-0-10-centos 811]$ ./get
XDG_SESSION_ID=55445
HOSTNAME=VM-0-10-centos
TERM=xterm
SHELL=/bin/bash
HISTSIZE=3000
SSH_CLIENT=111.52.127.5 26318 22
SSH_TTY=/dev/pts/0
USER=tsx
LD_LIBRARY_PATH=:/home/tsx/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/tsx
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tsx/.local/bin:/home/tsx/bin
GOPROXY=https://mirrors.tencent.com/go,direct
PWD=/home/tsx/811
LANG=en_US.utf8
SHLVL=1
HOME=/home/tsx
LOGNAME=tsx
SSH_CONNECTION=111.52.127.5 26318 10.2.0.10 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
PROMPT_COMMAND=history -a; history -a; printf "\\033]0;%s@%s:%s\\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
XDG_RUNTIME_DIR=/run/user/1001
HISTTIMEFORMAT=%F %T
_=./get
OLDPWD=/home/tsx
也与上文一致;
③:使用系统调用getenv
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("PATH:%s",getenv("PATH"));
return 0;
}
只需在getenv中输入想要的查询的环境变量,就可以打出:
[tsx@VM-0-10-centos 811]$ vim get.c
[tsx@VM-0-10-centos 811]$ gcc -o get get.c -std=c99
[tsx@VM-0-10-centos 811]$ ./get
PATH:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tsx/.local/bin:/home/tsx/bin
如何创建环境变量:
这时候,我们发现:
这环境变量,不就是大写的单词,后面等于…….
所以我直接写
[tsx@VM-0-10-centos 811]$ HEHE="11111"
[tsx@VM-0-10-centos 811]$ echo $HEHE
11111
(null)[tsx@VM-0-10-centos 811]$ cat get.c
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("%s",getenv("HEHE"));
return 0;
}
[tsx@VM-0-10-centos 811]$ make clean
rm -rf get
[tsx@VM-0-10-centos 811]$ make
gcc -o get get.c -std=c99
[tsx@VM-0-10-centos 811]$ ./get
但我发现:我在自己写的程序里面打不出来,在环境变量里面也找不到,这时候,我们就要引入环境变量和本地变量的概念了:
为了方便理解,我画了一张图:

但当我们用了export之后:
(null)[tsx@VM-0-10-centos 811]$ export HEHE
[tsx@VM-0-10-centos 811]$ vim get.c
[tsx@VM-0-10-centos 811]$ make
gcc -o get get.c -std=c99
[tsx@VM-0-10-centos 811]$ ./get
11111
就可以打出来了
而如果我们用unset:
[tsx@VM-0-10-centos 811]$ unset HEHE
[tsx@VM-0-10-centos 811]$ ./get
(null)
就可以取消环境变量
加深理解:
我们在运行自己写的程序的时候,为什么要加 ./ ?
原因是,shell在查找这个二进制文件时,若你没有指定路径,就会到PATH中的路径下找,找不到就不执行了,而我加上./ 就是指定当前路径下查找,就可以找到了,那如果,我把当前路径加到PATH中呢?
(null)[tsx@VM-0-10-centos 811]$ pwd
/home/tsx/811
[tsx@VM-0-10-centos 811]$ PATH=$PATH:/home/tsx/811
[tsx@VM-0-10-centos 811]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/tsx/.local/bin:/home/tsx/bin:/home/tsx/811
[tsx@VM-0-10-centos 811]$ get
(null)[tsx@VM-0-10-centos 811]$
这时候,我们就发现:
即使不加./,也可以运行咯
demo3:虚拟地址
引入:
我们在学习C语言的时候,总听说什么栈,堆,数据段,代码段,一堆东西,但我们在那时候并没有给出解释,所以我们今天在讲虚拟地址之前,我们先来讲讲,程序地址空间;
程序地址空间:
我画了一张图,来帮助理解:

其实重要的内容就都在这张图中了,所以我们就直入主题了
承上启下:
我们写一个程序:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//子进程
printf("我是子进程,我的id值是%d,我的id地址是%p\\n",id,&id);
}
else
{
printf("我是父进程,我的id是%d,我的id地址是%p\\n",id,&id);
}
return 0;
}
最后结果是:
[tsx@VM-0-10-centos 811]$ ./get
我是父进程,我的id是19629,我的id地址是0x7fff36c7f4bc
我是子进程,我的id值是0,我的id地址是0x7fff36c7f4bc
很奇怪:我们两个id值不一样,但我们的地址一样!!!!
难道说:我们的地址是假的?
没错!
虚拟地址:
这是我给大家画的一张图

其中task_struct,我们每一个进程都有一个,其中存放着一个mm_struct* 的指针,而在mm_struct中,划分着每一个区的开始与结束,并且还有一张页表的指针,通过页表,我们可以建立虚拟地址和物理内存的关联(哈希映射)

上面的是父进程,下面的是子进程,子进程的task_struct在建立的时候,需要拷贝父进程的(只有一小部分是自己造的,比如pid),所以页表中,他们最开始的虚拟地址也是一样的,映射到的物理内存也一样;并且,父进程在创建子进程时,其数据区中的数据会从可读可写变成只读(页表通过权限来实现),如果我要修改数据区的数据,那么操作系统就知道了,从而在物理内存中,在开辟一段空间,然后重新建设映射关系,从而实现父子进程的完全分离,这就是写时拷贝
如何管理虚拟地址:
这时候,我们想到一个问题,数据区和代码段人家在最开始就已经创建好了,而栈区又是连续的,但堆区不是啊,我们如何对虚拟地址进行管理呢?

每⼀个进程都会有⾃⼰独⽴的 mm_struct ,操作系统将mm_struct组织起来的方式有两种:
①.当虚拟区较少时采取单链表,由mmap指针指向这个链表;
②.当虚拟区间多时采取红⿊树进⾏管理,由mm_rb指向这棵树
linux内核使⽤ vm_area_struct 结构来表⽰⼀个独⽴的虚拟内存区域(VMA),由于每个不同质的虚
拟内存区域功能和内部机制都不同,因此⼀个进程使⽤多个vm_area_struct结构来分别表⽰不同类型的虚拟内存区域。
虚拟地址的意义:
①.物理内存是无序的,通过虚拟地址,可以让我们看来是有序的,实现无序到有序
②.保护物理内存中的合法数据,防止恶意或无意篡改
③.实现进程管理模块和内存管理模块的解耦合
例如我们在new申请空间时,只是先申请了一块虚拟地址,但实际的内存中,可以不给我使用,先给别人用,直到我真正使用的时候,才给我开辟
承上启下:
好啦,在学习完上面三个小demo后,我们就要真正踏入进程的管理了,进程管理又分为进程创建,进程终止,进程等待,进程程序替换,标红的这两项是我们今天要讲的重点哦
进程创建:
我们在这里只说说两种用途:
①. ⼀个⽗进程希望复制⾃⼰,使⽗⼦进程同时执⾏不同的代码段。例如,⽗进程等待客⼾端请求, ⽣成⼦进程来处理请求
②. ⼀个进程要执⾏⼀个不同的程序。例如⼦进程从fork返回后,调⽤exec函数
进程终止:
分为三种情况:
①. 代码运行完毕,结果正确
②.代码运行完毕,结果不正确
③. 代码异常终止
比如我可以这样写
#include<stdio.h>
int main()
{
int a=0;
int b=1;
if(a==b)
{
return 0;
}
else
{
return 1;
}
}
通过$?来打出我的退出码
[tsx@VM-0-10-centos 811]$ ./get
[tsx@VM-0-10-centos 811]$ echo $?
1
但同时,如果我没能来得及return呢?
那么此时退出码就无意义了,此时我们需要的是退出信号,signnumber
这三种情况,分别对应的是:
①:运行完且结果正确:signnumber==0 && 退出码==0
②:运行完但结果不对:signnumber==0 && 退出码!=0;
③:没运行完:signnumber!=0 && 退出码无意义
区分一下exit和return
我们在C语言阶段也用过exit函数,例如exit(1);
我们当时认为,exit和return差不多,都可以终止程序,但今天我们来区分一下
exit:直接终止进程;
return;结束函数;
进程等待:
我们在进程时候讲过,如果我们父进程对子进程不管不顾,那么子进程就会一直保持僵尸进程的状态,即使是kill -9也杀不死,因为已经死了,但还占用着我们的内存,造成内存泄漏,所以我们父进程就需要进行等待;同时!我们父进程创建子进程,必然是让子进程去完成任务的,我们需要知道子进程是否完成好了,所以,我们就需要进程等待;
等待方法:
我们有两种等待方法:
①:wait
头文件:
#include<sys/types.h>
#include<sys/wait.h>
函数:
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL
②:waitpid
函数:
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
①:正常返回时返回子进程pid
②:若设置了WNOHANG,而调用时发现当前没有僵尸状态子进程可收集,返回0
③:调用中出错返回-1;
参数:
①:pid:
pid设为-1,等待随机一个子进程
pid>0 ,等待该pid的子进程
②:status:
输出型参数:
具体我们一会讲
③:options:
默认为0,表示阻塞等待
若设置为WNOHANG,若pid指定的子进程没有结束,那么返回0,不等待。
注意:
①:如果⼦进程已经退出,调⽤wait/waitpid时,wait/waitpid会⽴即返回,并且释放资源,获得⼦
进程退出信息。
②:如果在任意时刻调⽤wait/waitpid,⼦进程存在且正常运⾏,则进程可能阻塞。
③:如果不存在该⼦进程,则⽴即出错返回。
status:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//子进程
exit(1);
}
else{
int status;
wait(&status);
printf("status:%d",status);
return 0;
}
}
我们运行之后:
[tsx@VM-0-10-centos 811]$ ./get
status:256
256?这可和我们想要的不一样,我们要的是进程退出码,按理说应该是1;
这时我们就要看看status内部

- bit 0 ~ 6:终止信号编号
- bit 7:core dump 标志位(1 = 生成 core 文件,0 = 不生成)
- bit 8 ~ 15:进程退出码(正常退出时有效)
什么时候会触发 core dump?
进程收到致命信号异常崩溃时才可能生成,常见信号:
- SIGSEGV(11):段错误(野指针、越界访问)
- SIGABRT(6):abort () 主动崩溃
- SIGFPE(8):浮点数异常(除 0)
如何获取退出码和终止信号?
退出码(退出状态):(status&0xffff)>>8
终止信号:status&0x7f
常用的两个宏:
WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码(查看进程的退出码)
非阻塞等待例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
// 模拟父进程并行执行的任务
void task_work(void)
{
static int count = 0;
count++;
printf("【父进程执行任务】第%d轮工作\\n", count);
}
int main(void)
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return 1;
}
else if (pid == 0)
{
// 子进程:睡眠5秒后退出
printf("子进程运行中, pid:%d, 将睡眠5秒退出\\n", getpid());
sleep(5);
exit(8); // 退出码设置为8
}
else
{
// 父进程:非阻塞轮询等待子进程
int status = 0;
pid_t ret;
do
{
// WNOHANG:非阻塞,子进程未结束立刻返回0
ret = waitpid(pid, &status, WNOHANG);
if (ret == 0)
{
// 子进程尚未退出,父进程执行自己的业务
task_work();
sleep(1); // 减缓打印频率,方便观察
}
} while (ret == 0);
// 退出循环代表子进程已经退出
if (ret > 0 && WIFEXITED(status))
{
printf("子进程成功回收!子进程退出码:%d\\n", WEXITSTATUS(status));
}
else
{
printf("等待子进程出错\\n");
}
}
return 0;
}
进程替换:
理解:

⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调
⽤⼀种 exec 函数以执⾏另⼀个程序。当进程调⽤⼀种 exec 函数时,该进程的⽤户空间代码和数据
完全被新程序替换,从新程序的启动例程开始执⾏。调⽤ exec 并不创建新进程,所以调⽤ exec 前后
该进程 的 id 并未改变
替换函数:
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 execvpe(const char *file, char *const argv[],char *const envp[]);
事实上,上面六个是库函数,下面一个是系统调用:
int execve(const char *path, char *const argv[], char *const envp[]);
看着很多,其实有迹可循:
①:都以exec开头
②: 后面跟着的是l,p,e这些
区分:
l(list) : 表⽰参数采⽤列表
v(vector) : 参数⽤数组
p(path) : 有 p ⾃动搜索环境变量 PATH
e(env) : 表⽰⾃⼰维护环境变量
注意:
①:这些函数如果调⽤成功则加载新的程序从启动代码开始执⾏,不再返回。
②:如果调⽤出错则返回 -1
③:所以 exec 函数只有出错的返回值⽽没有成功的返回值。
尾:
画图真累,累死了。。。。。。下一次更不知道是什么时候了,不过可以期待一下?
朋友们,午安^^




