更多知识点:linux目录索引
进程创建———fork()
函数: pid_t fork(void)
说明:
父进程创建子进程后,父子进程交替运行
运行过程中,子进程死亡,父进程存在,子进程称为僵尸进程,且僵尸进程不可人为杀死,必须等到父进程结束后,才会将其回收
运行过程中,父进程死亡,子进程存在,子进程称为孤儿进程,由1号进程托管,死亡后由1号进程负责回收
fork()函数返回值有两个,父进程返回子进程的id,子进程返回0,出错返回-1;关于父子进程返回值的问题可点链接关于fork()父子进程返回值的问题
僵尸进程的创建
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{
pid_t pid = fork();//创建进程,返回值-1,0,大于0
int i = 0;
if(pid == 0)//子进程
{
while(i < 5)
{
printf("子进程运行%d \n",i);
sleep(1);
++i;
}
exit(0);//子进程运行5秒退出,成为僵尸进程
}
while( i < 15)
{
printf("父进程运行%d \n",i);
sleep(1);
++i;
}
return 0;
}
效果:
孤儿进程的创建
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{
pid_t pid = fork();//创建进程,返回值-1,0,大于0
int i = 0;
if(pid == 0)//子进程
{
while(i < 15)
{
printf("子进程运行%d \n",i);
sleep(1);
++i;
}
exit(0);
}
while( i < 5) //父进程运行5秒退出,子进程为孤儿进程,被1号进程托管
{
printf("父进程运行%d \n",i);
sleep(1);
++i;
}
return 0;
}
效果:
进程创建————vfork()
函数: pid_t vfork()
说明:
vfork创建后,父进程被阻塞,直到子进程运行完毕后,父进程才开始运行
父进程和子进程共享代码段,数据段(一切内存空间)
vfork创建进程,就算写时,也不进行拷贝
vfork创建的进程,必须使用exit 或者exec 结束进程
一个例子:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid = vfork();
int i=0;
if(pid==0)//子进程运行5秒
{
for(;i<5;++i)
{
printf("子进程运行:%d\n",i);
sleep(1);
}
exit(0);
}
for(i=0;i<5;++i)
{
printf("父进程运行:%d\n",i);
sleep(1);
}
return 0;
}
结果:
进程等待
函数: pid_t wait( int* status );
说明:
当子进程死亡,变为僵尸进程,父进程调用wait 回收僵尸进程
回收成功,返回值为被回收的子进程id,失败返回-1
如果一个父进程有多个子进程,其中一个子进程死亡,(成为僵尸进程),wait进行返回
调用一次wait只能回收一个子进程
例子:多进程同时运行
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid = fork();
if(pid == 0)//创建子进程
{
if(fork() > 0) //子进程创建孙子进程后退出
{
exit(0);
}
while(1)//孙子进程一直运行
{
printf("孙子进程\n");
sleep(1);
}
}
else
{
wait(NULL);//父进程等待回收死亡的子进程(僵尸进程)
while(1)
{
printf("爷爷\n");//父进程一直运行
sleep(1);
}
}
return 0;
}
//这段代码实现的功能是:
//父进程创建子进程,子进程创建孙子进程
//杀死子进程,父进程中的wait函数处理掉僵尸进程
//父进程和孙子进程仍存活,并发运行,只不过此时孙子进程属于孤儿进程
效果:
函数: pid_t waitpid(pid_t,int* status,int options)
函数说明:
waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束
如果在调用waitpid()时子进程已经结束, 则waitpid()会立即返回子进程结束状态值.
子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一块返回. 如果不在意结束状态值, 则参数status 可以设成NULL.。
如果执行成功则返回子进程识别码(PID), 如果有错误发生则返回-1. 失败原因存于errno 中.
参数pid 为欲等待的子进程识别码, 其他数值意义如下:
pid<-1 等待进程组识别码为pid 绝对值的任何子进程.
pid=-1 等待任何子进程, 相当于wait().
pid=0 等待进程组识别码与目前进程相同的任何子进程.
pid>0 等待任何子进程识别码为pid 的子进程.
- 参数option 可以为0 或下面的OR 组合:
WNOHANG:如果pid指定的子进程没有结束,则waitpid()返回0,不予等待,若正常结束,返回该子进程的id。
WUNTRACED:如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会.。子进程的结束状态返回后存于status, 底下有几个宏可判别结束情况
WIFEXITED(status):如果子进程正常结束则为非真。
WEXITSTATUS(status):取得子进程exit()返回的结束代码, 一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status):如果子进程是因为信号而结束则此宏值为真。
WTERMSIG(status):取得子进程因信号而中止的信号代码, 一般会先用
WIFSIGNALED 来判断后才使用此宏.。
WIFSTOPPED(status):如果子进程处于暂停执行情况则此宏值为真. 一般只有使用
WUNTRACED时才会有此情况。
WSTOPSIG(status):取得引发子进程暂停的信号代码, 一般会先用WIFSTOPPED 来判断后才使用此宏。
进程终止
销毁进程的过程:
释放资源、文件等
记录运行过的信息(例如:哪些程序曾经运行过)
将进程的状态置为僵尸状态
转存储调度:将CPU让给所要使用的进程
进程终止的方法
正常退出:
main 函数退出
调用exit
调用_exit
执行退出处理函数
刷新缓存
异常退出
Ctrl+c
about
两个函数
_exit 函数
void _exit(int status);
参数:status定义了进程的终止状态,父进程通过wait来获取该值
注意:虽然status是int,但是仅有低8位可以被父进程所用。所以exit(-1)时,在终端执行$?发现返回值为255
exit函数
void exit(int status);
二者区别:
_exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。
exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是”清理I/O缓冲”。
fork、execvp、popen和system的比较
fork创建子进程,以及对写时拷贝的解释
fork() 一个程序一调用fork函数,系统就为一个新的进程准备了前述三个段:
首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。
现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。
事实上,目前大多数的unix系统在实现上并没有作真正的copy。一般的,CPU都是以“页”为单位分配空间的,象INTEL的CPU,其一页在通常情况下是4K字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,fork函数复制这两个段,只是“逻辑”上的,并非“物理”上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区 别,系统就将有区别的“页”从物理上也分开。系统在空间上的开销就可以达到最小。
exec修改子进程
对于exec系列函数 一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。不过exec类函数中有的还允许继承环境变量之类的信息,这个通过exec系列函数中的一部分函数的参数可以得到。
popen
对于popen函数,他会通过command参数重新启动shell命令,并建立两个进程间的管道通信.
详见:popen函数_Linux C 中文函数手册
system
对于system函数,它也会重新启动shell命令,当执行完毕后,程序会继续system下一行代码执行.
详见:system函数_Linux C 中文函数手册
注:以上内容摘自:popen system fork exec等函数的区别