文章目录
一、进程的启动1.1 getpid获取当前进程标识符1.1.1代码演示将在子进程处 演示
1.2启动新进程例1 system()使用1.2.1 system的局限性
1.3 exec系列函数例2 execl()使用例3 execlp()使用例4 excvp()使用例5 execle()使用
二、创建子进程2.1 fork创建子进程例6 getpid()和fork()使用
2.2等待一个进程例7 wait()使用
三、总结
一、进程的启动
1.1 getpid获取当前进程标识符
进程标识符(PID),通常的范围为2~32768,0~1的取值有系统固定的意义,可参考
上篇文章对其的简述
由上图我门,可以看出获取进程标识符
(以下都称为PID
)的函数有两个
pid_t
getpid(void);
pid_t
getppid(void);
所需头文件:
#include<sys/types.h>
#include<unistd.h>
pid_t 是返回值类型,类型为进程标识符类型
通过程序测试,
我们知道了
getpid()返回的是当前进程的PID,而
getppid()返回的是父进程PID
注意:
此函数不会失败,也就是说只要调用这个两个函数中的一个,
都会成功且返回一个PID
1.1.1代码演示将在子进程处 演示
1.2启动新进程
在一个程序内部启动另一个程序,从而创建一个新进程。这个工作一个通过库函数system完成
int system(const char *string
);
所需头文件:
#include<stdlib.h>
返回值:
成功:返回值为该命令的退出码
失败:
若
comand(即string形参
) 为空 返回
0
若子进程不能被创建或者它的状态不能被检测 返回
-1
若无法启动shell启动命令 返回
127
其他错误 都返回
-1
例1 system()使用
1.2.1 system的局限性
system很有用,它也有很大的局限性,因为程序必须等待由system函数启动的进程结束后才能继续,也就是说,不能立刻执行其他任务。 一般来说,使用system函数并非启动其他进程的理想手段,因为它必须使用一个shell来启动需要的程序,所以这对shell的安装情况和使用环境也有很大的以来,所以system函数的使用效率并不高,接下来我们介绍一中更好的调用程序的方法。
1.3 exec系列函数
功能: 在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 函数族: exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
函数原型:
#include <unistd.h>
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
[]);
返回值: exec函数族的函数执行成功后不会返回,调用失败时, 会设置errno并返回-1,然后从原程序的调用点接着往下执行。 参数说明: path:可执行文件的路径名字 arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束 ,可以为系统自带的命令 file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
exec族函数有很多并且相似,可以根据函数中某些字符区分: l : 使用参数列表 p:使用文件名,并从PATH环境进行寻找可执行文件 v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。 e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
下面将exac函数归为带l、带p、带v、带e 四类来说明参数特点。
一、带l的一类exac函数(l表示list),如execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。 以execl函数为例子来说明:
例2 execl()使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("before excel\n");
if(execl("./file1","file1",NULL)==-1)
{
printf("execle failed\n");
perror("why:");
}
printf("after execl\n");
return 0;
}
#include<stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
运行结果:
gec@ubuntu
:/mnt
/hgfs
/vrshare$ gcc exec1
.c
gec@ubuntu
:/mnt
/hgfs
/vrshare$
./a
.out
before excel
Hello world
实验说明: 在exec1.c文件中使用execl()函数,file1.c作为被调用文件,在执行exec1可执行文件后,程序在此函数调用处的后面内容将被覆盖,所以”after execl” 没有在终端被打印出来。
二、带p的一类exac函数,如execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。举个例子,PATH=/bin:/usr/bin
例3 execlp()使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("before execl****\n");
if(execl("ps","ps","-l",NULL) == -1)
{
printf("execl failed!\n");
}
printf("after execl*****\n");
return 0;
}
运行结果:
gec@ubuntu
:/mnt
/hgfs
/vrshare$ gcc execl_no_path
.c
gec@ubuntu
:/mnt
/hgfs
/vrshare$
./a
.out
before execl
****
execl failed
!
after execl
*****
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("before execlp\n");
if(execlp("date","date",NULL)==-1)
{
printf("execle failed\n");
perror("why:");
}
printf("after execlp\n");
return 0;
}
运行结果:
gec@ubuntu
:/mnt
/hgfs
/vrshare$ gcc exec3
.c
gec@ubuntu
:/mnt
/hgfs
/vrshare$
./a
.out
before execlp
Fri Oct
9 23:34:25 PDT
2020
从上面的实验结果可以看出,上面的exaclp函数带p,
所以能通过环境变量PATH查找到可执行文件ps,这样就看清了两者的不同
三、带v不带l的一类exac函数,包括execv、execvp、execve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
例4 excvp()使用
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("before execvp\n");
char *argv
[]={"ps",NULL};
if(execvp("ps",argv
)==-1)
{
printf("execvp failed\n");
perror("why:");
}
printf("after execvp\n");
return 0;
}
运行结果:
gec@ubuntu
:/mnt
/hgfs
/vrshare$ gcc exec4
.c
gec@ubuntu
:/mnt
/hgfs
/vrshare$
./a
.out
before execvp
PID TTY TIME CMD
2343 pts
/6 00:00:00 bash
70519 pts
/6 00:00:00 ps
四、带e的一类exac函数,包括execle、execvpe,可以传递一个指向环境字符串指针数组的指针。 参数例如char *env_init[] = {“AA=aa”,”BB=bb”,NULL}; 带e表示该函数取envp[]数组,而不使用当前环境。
例5 execle()使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char *buf
[] = {"AA=aa","BB=bb",NULL};
int main(void)
{
printf("before execle****\n");
if(execle("./bin/exec_test03","exec_test03",NULL,buf
) == -1)
{
printf("execle failed!\n");
}
printf("after execle*****\n");
return 0;
}
#include <stdio.h>
#include <unistd.h>
extern char** buf
;
int main(int argc
, char *argv
[])
{
int i
;
char **ptr
;
for(ptr
= buf
;*ptr
!= 0; ptr
++)
printf("%s\n",*ptr
);
return 0;
}
运行结果:
gec@ubuntu
:/mnt
/hgfs
/vrshare$ gcc exec5
.c
gec@ubuntu
:/mnt
/hgfs
/vrshare$
./a
.out
before execle
****
AA
=aa
BB
=bb
二、创建子进程
2.1 fork创建子进程
pid_t
fork(void);
所需头文件:
#include<unistd.h>
返回值:
成功:返回值为子进程PID
失败:返回值为
-1
作用
:
创建一个和原进程内容一样的进程,从此处开始执行新进程。
注意:
两个进程是同时进行的!!
例6 getpid()和fork()使用
通过加之限定条件,我们清楚了父进程和子进程!知道了在同时有父进程和子进程时,父进程PID为非负数,子进程PID为0,fork()调用成功返回两次!
2.2等待一个进程
当fork创建一个子进程时,子进程有了它自己的生命周期并且独立于父进程运行,有时我们希望知道什么时候一个子进程结束。例如,有时我们想让输出不乱,那么我们需要父进程等待子进程结束,再结束父进程。通过查库我们得到函数:
pid_t
wait(int *status
);
pid_t
waitpid(pid_t pid
,int *status
,int options
);
status:
用来保存被收集进程退出时的一些状态,它是一个指向
int类型的指针。通常
设为
NULL,原因自查。
pid:进程标识符,进程号
1.pid
>0 只等待进程ID等于pid的子进程,只要指定的子进程未结束waitpid就会一直等下去。
2.pid
=-1 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
3.pid
=0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
4.pid
<-1 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
options:
options提供了一些额外的选项来控制waitpid,具体请查看手册
所需有文件:
#include<sys/types.h>
#include<sys/wati.h>
返回值:
因为篇幅原因,
(这里仅说
wait(NULL)),对于wait其他状态宏和waitpid的返回值
具体请参考:
https
://blog
.csdn
.net
/wyhh_0101
/article
/details
/83933308
成功:
wait(NULL),返回最终结束的子进程的PID
失败:
wait(NULL),返回
-1
函数说明:
1.wait()
父进程阻塞直到子进程结束或者终止。如果该父进程没有子进程
或者它的子进程已经结束,则
wait()函数就会立即返回。
2.waitpid()
它并不一定要等待第一个终止的子进程(可以指定需要等待终止的子进程),
它还有若干选项
(options
),如可提供一个非阻塞版本的
wait()功能,
也能支持作业控制。可以说
wait()函数只是
waitpid()函数的一个特例,
在Linux 内部实现
wait()函数时直接调用的就是
waitpid()函数。
例7 wait()使用
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
pid_t pid
;
int cnt
=0;
int status
=10;
pid
=fork();
if(pid
>0)
{
wait(&status
);
printf("child quit,child status = %d\n",WEXITSTATUS(status
));
while(1)
{
printf("cnt=%d\n",cnt
);
printf("This is father process %d\n",getpid());
sleep(1);
}
}
else if(pid
==0)
{
while(1)
{
printf("This is child process %d\n",getpid());
sleep(1);
cnt
++;
if(cnt
==3)
{
exit(3);
}
}
}
return 0;
}
运行结果:
gec@ubuntu
:/mnt
/hgfs
/vrshare
/eng
/linux_Sys
/day4$
./a
.out
This is child process
70951
This is child process
70951
This is child process
70951
child quit
,child status
= 3
cnt
=0
This is father process
70950
cnt
=0
This is father process
70950
cnt
=0
This is father process
70950
cnt
=0
This is father process
70950
cnt
=0
This is father process
70950
三、总结
1.由于篇幅原因,将在下一篇文章补充僵尸进程和孤儿进程相关概念 2.增强编程能力,光看不行,动手最关键,最后总结出一片文章记录自己的学习过程。