通过一个例子来对进程创建fork函数进行理解
#include#include #include int main() { pid_t pid; pid_t pid2; pid = getpid(); printf("Before fork:%dn",pid); fork(); pid2 = getpid(); printf("After fork:%dn",pid2); if(pid == pid2) printf("This is father printn"); else printf("This is children print,pid = %dn",pid2); return 0; }
通过pid的值来判断进程的执行情况,运行后会出现以下界面:
通过分析打印的内容,可以看出,fork执行前pid为55
fork执行后创建新进程后,会先执行父进程的打印函数,然后子进程的打印函数,分别进入两个分支,将信息打印出来。
fork后父进程与子进程都会执行fork之后的代码。
fork编程实战一个现有进程可以调用fork函数创建一个新进程。
由fork创建的新进程被称为子进程,fork函数被调用一次,但返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以一个子进程总是可以调用getppid以获得其父进程的进程ID(进程ID 0 总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父、子进程并不共享这些存储空间部分。父、子进程共享正文段。
由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时复制(Copy-On-Write,COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”。
#includevfork函数创建进程#include #include int main() { pid_t pid; int data; while(1){ printf("Please input a data:n"); scanf("%d",&data); if(data == 1){ pid = fork(); if(pid > 0){ } else if(pid == 0){ while(1){ printf("Do net request,pid=%d",getpid()); sleep(3); } } }else{ printf("wait ,do nothingn"); } } return 0; }
vfork函数也可以创建进程,与fork有什么区别呢?
关键区别一:vfork直接使用父进程存储空间,不拷贝。
关键区别二:vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
练习如下:
#include进程退出 (一)正常退出#include #include #include int main() { pid_t pid; int cnt = 0; pid = vfork(); if(pid > 0){ while(1){ printf("cnt = %dn",cnt); printf("This is father print,pid = %dn",getpid()); sleep(1); } }else if(pid == 0){ while(1){ printf("This is children print,pid = %dn",getpid()); sleep(1); cnt++; if(cnt == 3){ exit(0); } } } return 0; }
1.main函数调用return
2.进程调用exit(),标准C库
3.进程调用_exit()或者__Exit(),属于系统调用
补充:
1.进程最后一个线程返回
2.最后一个线程调用pthread_exit
(二)异常退出1.调用abort
2.当进程收到某些信号时,如ctrl+C
3.最后一个线程对取消(cancellation)请求做出响应
总结 不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit和_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
#include2.子进程退出状态不被收集,变成僵死进程(僵尸进程)。#include #include #include #include int main() { pid_t pid; int cnt = 0; int status=10; pid = fork(); if(pid > 0){ wait(&status); printf("Child quit,child status %dn",WEXITSTATUS(status)); while(1){ printf("cnt = %dn",cnt); printf("This is father print,pid = %dn",getpid()); sleep(1); } }else if(pid == 0){ while(1){ printf("This is children print,pid = %dn",getpid()); sleep(1); cnt++; if(cnt == 5){ exit(3); } } } return 0; }
#include#include #include #include int main() { pid_t pid; int cnt = 0; pid = vfork(); if(pid > 0){ while(1){ printf("cnt = %dn",cnt); printf("This is father print,pid = %dn",getpid()); sleep(1); } }else if(pid == 0){ while(1){ printf("This is children print,pid = %dn",getpid()); sleep(1); cnt++; if(cnt == 3){ exit(0); } } } return 0; }
父进程没有获取子进程的状态信息,子进程变成僵尸进程。
父进程调用wait不会让子进程变成僵尸进程。
相关函数:NAME
wait, waitpid, waitid - wait for process to change state
SYNOPSIS
#include
#include
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
如果其所有子进程都还在运行,则阻塞。
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
如果它没有任何子进程,则立即出错返回。
wait与waitpid的区别wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞。
#include#include #include #include #include int main() { pid_t pid; int cnt = 0; int status=10; pid = fork(); if(pid > 0){ waitpid(pid,&status,WNOHANG);//非阻塞 printf("Child quit,child status %dn",WEXITSTATUS(status)); while(1){ printf("cnt = %dn",cnt); printf("This is father print,pid = %dn",getpid()); sleep(1); } }else if(pid == 0){ while(1){ printf("This is children print,pid = %dn",getpid()); sleep(1); cnt++; if(cnt == 5){ exit(3); } } } return 0; }
需要注意的是,父进程虽然调用了wait,但是在非阻塞等待状态时,子进程会变成僵尸进程。
孤儿进程父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。
(三)exec族函数exec族函数有:execl, execlp, execle, execv, execvp, execvpe
函数原型:
#includeextern char **environ; 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[]);
示例:
#include#include #include int main() { printf("This pro get system date:n"); if(execlp("date","date",NULL,NULL)==-1) { printf("execlp failed:n"); perror("why"); } printf("after execlpn"); return 0; }
#include#include #include int main() { printf("This pro get system date:n"); char *argv[]={"date",NULL,NULL}; if(execvp("date",argv)==-1) { printf("execvp failed:n"); perror("why"); } printf("after execvpn"); return 0; }
以上两个都是获取系统时间的。
如何增加环境变量1.用pwd命令获取当前路径
2.用export PATH=$PATH:(pwd获取到的路径)
3.回车执行
这样环境变量中便会增加一条当前路径的信息。
exec族函数配合fork使用代码示例:
#include#include #include #include #include #include #include #include int main() { pid_t pid; int data=10; while(1){ printf("Please input a datan"); scanf("%d",&data); if(data == 1){ int fdSrc; pid = fork(); if(pid > 0){ wait(NULL); } if(pid == 0){ execl("./changData","changData","config.txt",NULL); } }else{ printf("wait ,do nothingn"); } } return 0; }
其中的changData的代码如下:
#include(四)system函数#include #include #include #include #include #include int main(int argc,char **argv) { int fdSrc; char *readBuf=NULL; if(argc!=2){ printf("参数传递错误n"); exit(-1); } fdSrc=open(argv[1],O_RDWR); int size=lseek(fdSrc,0,SEEK_END); lseek(fdSrc,0,SEEK_SET); readBuf=(char *)malloc(sizeof(char)*size+8); int n_read=read(fdSrc,readBuf,size); char *p=strstr(readBuf,"SCORE="); if(p==NULL){ printf("not found!!!n"); exit(-1); } p=p+strlen("SCORE="); *p = '5'; lseek(fdSrc,0,SEEK_SET); int n_write=write(fdSrc,readBuf,strlen(readBuf)); close(fdSrc); return 0; }
system函数的返回值如下:
成功,则返回进程的状态值;
当sh不能执行时,返回127;
失败返回-1;
与exec不同的是:system还会返回到原程序中执行后边的代码。
(五)popen函数popen函数比system函数在应用中的好处是:
可以获取运行的输出结果。
#include#include #include #include int main() { char ret[1024] = {0}; FILE *fp; fp = popen("ps","r"); int nread = fread(ret,1,1024,fp); printf("read ret %d byte,ret = %sn",nread,ret); return 0; }



