进程创建
fork函数创建子进程
#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0;父进程返回子进程id.出错返回-1
这个函数有两个返回值,一般用if分流
函数实现过程:
进程调用fork,将控制转移到内核,内核开始工作
1.分配内存块和数据块给子进程
2.将父进程的数据和代码拷贝给子进程
3.添加子进程到进程列表当中
4.forkf返回,开始调度器调度
下面是fork函数的简单用法
#include <unistd.h>
#include <sys/types.h>
#include<stdio.h>
int main( )
{
int ret=fork( );
if( ret<0)
{
perror( " fork\n");
return -1;
}
else if( ret==0)
{
printf("i am child :%d, ret:%d\n",getpid(),ret);
}
else
{
printf("i am father:%d ret:%d\n",getpid(),ret);
}
sleep( 1);
return 0;
}
运行结果:
[root@localhost my_shell]# ./a.out
i am father:4259 ret:4260
i am child :4260, ret:0
可以看出:父进程返回子进程id;子进程返回0;
进程终止
进程退出有三种情况:
- 程序跑完了,结果正确
- 程序跑完了,结果不正确
- 程序异常终止
进程常见退出方法:
- 从main函数返回,通过return返回
- 调用exit()函数。终止进程,返回退出码,退出后会进行收尾工作
- 调用_exit()函数,强制终止进程,返回退出码,不进行收尾工作。
_exit函数
#include <stdlib.h>
void _exit(int status);
参数:status定义了进程的终止状态,父进程通过wait函数来获取该状态。
虽然status是int型,但只有低8位可以用。所以执行_exit(-1)时,在终端执行echo $?时返回255.
exit函数
#include <stdlib.h>
void exit(int status);
返回值:没有返回值
该函数执行后会关闭所有打开的流,然后调用_exit()函数
下面是简单运用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main( )
{
printf( " hello,exit\n");
exit( 0);
}
运行结果
[root@localhost my_shell]# ./a.out
hello,exit
调用_exit函数
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main( )
{
printf( " hello,_exit\n");
_exit(0);
}
运行结果
[root@localhost my_shell]# ./a.out
hello,_exit
进程等待
进程等待是指父进程等待子进程,获取子进程的返回状态,回收子进程的过程
等待理由:
- 子进程退出,父进程如果不管不顾,子进程就会变成僵尸进程,从而导致内存泄露
- 子进程一旦变成僵尸进程,就会变得刀枪不入。kill -9 也无法杀死。
- 父进程需要知道派给子进程的任务完成的如何。结果对不对,或者是否正常退出
- 父亲通过进程等待的方式,获得子进程退出信息,回收子进程
等待方法:
调用wait函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
返回值:成功返回被等待进程pid,失败返回-1
参数:输出性参数,获取进程退出状态,不关心可设置成NULL
waitpid函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
返回值:当正常返回时,返回收集到的子进程pid
出错返回-1
下面是简单程序使用
#include<stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
int main( )
{
int pid=fork( ); //创建子进程
if( pid<0)
{
perror( " fork\n");
exit( 1);
}
else if( pid==0)
{
sleep( 20);
printf( " child pid:%d\n",getpid( ));
exit( 10);//子进程睡眠20s再退出
}
else
{
int st;
int ret=wait( &st); //父进程等待子进程退出
if( ret>0&&(st&0x7F)==0 ) //子正常退出
{
printf( " child exit code:%d\n",(st>>8)&0xFF);
}
else if( ret>0)//异常退出
{
printf("signal code:%d\n",st&0x7F);
}
}
return 0;
}
运行结果:
[root@localhost my_shell]# ./a.out
child pid:6485
child exit code:10
阻塞式等待
#include<stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
int main( )
{
int pid=fork( ); //创建子进程
if( pid<0)
{
perror( " fork\n");
exit( 1);
}
else if( pid==0)
{
printf( " child is runing, pid:%d\n",getpid( ));
sleep(5);
exit(257);//子进程睡眠5s再退出
}
else
{
int status=0;
int ret=waitpid(-1,&status,0); //父进程等待子进程退出,阻塞式等待
printf( "father say:this is test for wait\n");
if( WIFEXITED( status )&ret==pid ) //子正常退出
{
printf( "father say:wait child 5s,child return code:%d\n",WEXITSTATUS( status));
}
else if( ret>0)//异常退出
{
printf("child failed return.\n");
}
}
return 0;
}
[root@localhost my_shell]# ./a.out
child is runing, pid:6565
father say:this is test for wait
father say:wait child 5s,child return code:1
从结果可以看出,父进程一直在等待子进程执行完毕,再继续执行自己程序,所以输出时,中间空了五个空行
下面来看下,父进程边等边执行自己的程序,即 非阻塞式等待
非阻塞式等待应用程序
#include<stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
int main( )
{
int pid=fork( ); //创建子进程
if( pid<0)
{
perror( " fork\n");
exit( 1);
}
else if( pid==0)
{
printf( " child is runing, pid:%d\n",getpid( ));
sleep(5);
exit(257);//子进程睡眠5s再退出
}
else
{
int status=0;
pid_t ret=0;
do
{
ret=waitpid(-1,&status,WNOHANG); //父进程等待子进程退出,非阻塞式等待
printf( " child is runing\n");
sleep( 1);
}while( ret==0);
if( WIFEXITED( status )&ret==pid ) //子正常退出
{
printf( "father say:wait child 5s,child return code:%d\n",WEXITSTATUS( status));
}
else if( ret>0)//异常退出
{
printf("wait child failed return.\n");
return 1;
}
}
return 0;
}
运行结果
[root@localhost my_shell]# ./a.out
child is runing
child is runing, pid:6769
child is runing
child is runing
child is runing
child is runing
child is runing
father say:wait child 5s,child return code:1
可以看出,父进程在等的时候,并不停下手中的工作,而是继续执行。这种效率更高。
在执行上述一系列流程后,我们对进程的每一步都有了清晰的认识之后。
下面我们来介绍两个综合函数
popen/system
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen总是和pclose一起出现被使用的。popen() 创建一个管道,通过fork或者invoke一个子进程,然后执行command。返回值在标准IO流中,由于是在管道之中,因此数据流是单向的,command只能产生stdout或者读取stdin,因此type只有两个值:‘w’或‘r’。r表示command从管道中读取数据流,而w表示command的stdout输出到管道中。command无法同时读取和输出。popen返回该FIFO数据流的指针
管道读:先创建一个文件test,然后再test文件内写入“Read pipe successfully !”
#include “stdio.h”
#include “stdlib.h”
int main()
{
FILE *fp;
char buf[200] = {0};
if((fp = popen(“cat test”, “r”)) == NULL) {
perror(“Fail to popen\n”);
exit(1);
}
while(fgets(buf, 200, fp) != NULL) {
printf(“%s”, buf);
}
pclose(fp);
return 0;
}
运行结果:
Read pipe successfully !
管道读:
#include “stdio.h”
#include “stdlib.h”
int main()
{
FILE *fp;
char buf[200] = {0};
if((fp = popen(“cat > test1″, “w”)) == NULL) {
perror(“Fail to popen\n”);
exit(1);
}
fwrite(“Read pipe successfully !”, 1, sizeof(“Read pipe successfully !”), fp);
pclose(fp);
return 0;
}
运行结果
Read pipe successfully !
对于管道读例子已经很清楚,而管道写可能用的地方比较少。而对于写可能更常用的是system函数:
system(“cat “Read pipe successfully!” > test1”)
可以看出,popen可以控制程序的输入或者输出,而system的功能明显要弱一点,比如无法将ls的结果用到程序中。如果不需要使用到程序的I/O数据流,那么system是最方便的。
而且system函数是C89和C99中标准定义的,可以跨平台使用。而popen是Posix 标准函数,可能在某些平台无法使用(windows应该是可以的吧,没做过测试)。
如果上述两个函数还无法满足你的交互需求,那么可以考虑exec函数组了。