文章目录
一、进程的启动
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()使用
//文件exec1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// int excel(const char *path,const char *arg,...);
int main()
{
printf("before excel\n");
if(execl("./file1","file1",NULL)==-1)//file1为可运行程序(xx.exe)
{
printf("execle failed\n");
perror("why:");
}
printf("after execl\n");
return 0;
}
//文件file1.c
#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()使用
//文件execl_no_path.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);
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*****
//文件exec3.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
// int execlp(char *file,const char *arg,...,NULL);
int main()
{
printf("before execlp\n");
if(execlp("date","date",NULL)==-1)//调用的是Linux系统自带的命令
{
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 execvp(const char *file,const char *argv[]);
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()使用
//文件exec5.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execle(const char *path, const char *arg,..., char * const envp[]);
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;
}
//文件exec_test03.c
#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
//程序设置子进程执行三次后,退出,父进程接到终止信号后运行
2.3 popen函数
#include <stdio.h>
FILE * popen(const char *command , const char *type );
int pclose(FILE *stream);
//pclose与popen是一对,打开了就要有关闭,但这里仅对popen做详细说明。
函数作用:
popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭。popen函数允许一个程序将另外一个程序作为新进程来启动,并可以传递数据或者通过它接受数据。
其内部实现为调用 fork 产生一个子进程,执行一个 shell, 以运行命令来开启一个进程,这个进程必须由 pclose() 函数关闭。
参数说明:
①type参数只能是读或者写中的一种,如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入。
②command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令
返回值
成功:
返回标准I/O流。(FILE*)
失败:
如果调用fork()或pipe()失败,或者不能分配内存将返回NULL。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。
三、总结
1.由于篇幅原因,将在下一篇文章补充僵尸进程和孤儿进程相关概念