fork()函数
如上图所示,调用fork()函数将产生一个子进程(程序与父进程完全一致),其中父进程继续执行,子进程将在fork()的下一行开始执行,当然fork()函数的返回值会被接收,需要用于区分子进程和父进程(两者返回值不同,一个大于0,一个等于0)。fork()函数说明如下所示:
fork()
函数功能:
产生一个子进程
头 文 件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t fork(void);
参数分析:
无
返 回 值:
成功:返回 0 或者大于0的子进程号
失败: -1 错误号码会被设置
注意
虽然子进程是父进程的一份拷贝,但是有些属性并不相同,以下是相同的地方:
1.UID和GID;
2.所有环境变量;
3.进程组ID和会话ID;
4.当前工作路径,除非用chdir()修改过了;
5.打开的文件;
6.信号响应函数;
7.整个内存空间,包括栈、堆、数据段、代码段、标准IO的缓冲区等等。
以下属性是不同的:
1.进程号PID;
2.记录锁。父进程对某文件加了把锁,子进程不会继承这把锁;
3.挂起的信号。这些信号是所谓的“悬而未决”的信号,等待着进程的响应,子进程也不会继承这些信号。
fork函数实验程序
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t id = fork();//执行该语句后,会创建一个进程,相当于这个程序有两个进程了
if (id > 0)//执行父进程的时候调用这个,ID为进程的ID号码
{
printf("我是父进程,我的PID是:%d \t 子进程的PID是:%d!!!\n", getpid(), id);
}
else if (0 == id)//执行子进程的时候调用这个,子进程的ID号码为0
{
printf("我是子进程,我的PID是:%d \t 父进程的PID是:%d!!!\n", getpid(), getppid());
}
else if (id < 0)
{
printf("失败了!!\n");
}
return 0;
}
输出结果:
exec函数簇
在程序中调用了exec函数簇函数后,运行程序时调用此函数的代码会被替换,exec函数簇有六种函数,以不同的方式指定二进制文件来代替其中的代码,都是以exec开头的。
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 execve(const char *path, char *const argv[], char *const envp[]);
注意
1.函数名后缀为l的,意味着其参数以列表(list)的方式提供;
2.函数名后缀为v的,意味着其参数接受二维数组,最后一个参数以NULL作为结束;
3.函数名后缀为p的,意味着以环境变量来查找指定的可执行二进制文件;
4.函数名后缀为e的,意味着可以增加环境变量。
exec函数簇实验程序
execl("/bin/ls", "ls", "-al", "/dev", NULL); //指定二进制文件路径执行ls -al /dev,注意最后一个参数应该以NULL结尾
execlp("ls", "ls", "-al", "/dev", NULL); //默认PATH环境变量执行ls -al /dev
execle("/home/test","test","hello","world",NULL,"PATH=/usr/bin",NULL);//指定二进制文件路径执行test hello world 并且增加了环境变量
char *buf[] = {"hello world", "元旦快乐", NULL};
execv("/home/arno/test", buf);
退出进程
函数exit(status)和_exit(status)
当某个函数直接调用了exit(status)或_exit(status)时,将直接结束此进程,status用于记录进程死亡的原因。如果当主函数执行完或者是调用了return时,后续还会调用了一个exit函数,如下所示,该函数是一个系统调用,他主要用于回收僵尸进程的一些数据,最后该函数将一去不复返。
xxxx
{
....
ret_val = main(argc, argv);
exit(result);
}
注意:exit()和_exit()存在些许差别,如下:
1.如果子进程正常退出,则status一般为 0。
2.如果子进程异常退出,则statuc一般为非 0。
3.exit()退出时,会自动冲洗(flush)标准 IO 总残留的数据到内核,如果进程注册了“退出处理函数”还会自动执行这些函数。而_exit()会直接退出,什么都不处理。
函数wait和waitpid()
wait()
函数功能:
父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出
头 文 件:
#include <sys/wait.h>
函数原型:
pid_t wait(int *stat_loc);
参数分析:
stat_loc:分析子进程退出状态,也就是子进程执行exit(status),中status的值。
返 回 值:
成功: 退出的子进程PID
失败: -1
waitpid()
函数功能:
父进程一旦调用了waitpid就立即阻塞自己,等待进程组为pid的子进程
头 文 件:
#include <sys/wait.h>
函数原型:
pid_t waitpid(pid_t pid, int *stat_loc, int options);
参数分析:
pid:小于-1:等待组 ID 的绝对值为 pid 的进程组中的任一子进程;
-1:等待任一子进程;
0:等待调用者所在进程组中的任一子进程;
大于 0:等待进程组 ID 为 pid 的子进程。
option:WCONTINUED:报告任一从暂停态出来且从未报告过的子进程的状态;
WNOHANG:非阻塞等待;
WUNTRACED:报告任一当前处于暂停态且从未报告过的子进程的状态
返 回 值:
成功: 状态发生改变的子进程PID,当选项options为WNOHANG且执行成功时将返回0
失败: -1
常用处理子进程退出状态的宏
宏 | 含义 |
WIFEXITED(status) | 如果子进程正常退出,则该宏为真 |
WEXITSTATUS(status) | 如果子进程正常退出,则该宏将获取子进程的退出值 |
WIFSIGNALED(status) | 如果子进程被信号杀死,则该宏为真 |
WTERMSIG(status) | 如果子进程被信号杀死,则该宏将获取导致他死亡的信号值 |
WIFSTOPPED(status) | 如果子进程的被信号暂停,且option中WUNTRACED已经被设置时,则该宏为真 |
WSTOPSIG(status) | 如果WIFSTOPPED为真,则该宏将获取导致子进程暂停的信号值 |
WIFCONTINUED(status) | 如果子进程被信号SIGCONT重新置为就绪态,该宏为真 |
测试程序
wait.c
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
int ret = fork();
if (ret > 0)
{
int status;
wait(&status);
if (WIFEXITED(status))
{
printf("子进程正常退出\n");
}
if (WIFSIGNALED(status) )
{
printf("子进程被信号杀死了\n");
}
}
else if (ret == 0)
{
execl("./exit", "exit", "NULL");
}
return 0;
}
exit.c,被wait.c调用的程序
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
printf("Hello, this is child\n");
#ifdef ABORT
abort();
#else
exit(100);
#endif
return 0;
}
输出结果: