linux 进程(二) --- 进程的创建及相关api
睿丰德科技 专注RFID识别技术和条码识别技术与管理软件的集成项目。质量追溯系统、MES系统、金蝶与条码系统对接、用友与条码系统对接
由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是 新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以多于一个,所有没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID(进程 ID 0总是由交换进程使用,所以一个子进程的进程ID不可能为0)。
子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程拥有的拷贝。父、子进程并共享这些存储部分。如果正文段是只读的,则父、子进程共享正文段。
现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在fork之后经常跟随着exec。作为替代,使用了写时复制(copy-on-write,cow)的技术。这些区域由父、子进程共享,而且内核将他们的存取许可权改变位只读的。如果有进程试图修改这些区域,则内核包异常,典型的是虚存系统中的“页”,做一个拷贝。
实例1:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int glob = 6;
char buf[] = "a write to stdout\n";
int main()
{
int var;
int pid;
var = 88;
if(write(STDOUT_FILENO,buf,sizeof(buf) -1) != sizeof(buf) -1)
{
perror("fail to write");
return -1;
}
printf("before fork\n");
if((pid = fork()) < 0)
{
perror("fail to fork");
return -1;
}else
if(pid == 0)
{
glob ++;
var ++;
}else{
sleep(2);
}
printf("pid = %d,glob = %d,var = %d\n",getpid(),glob,var);
exit(0);
}
运行结果:
从上面可以看出,因为子进程和父进程拥有独立的物理内存空间,所以当子进程对拷贝来的数据做修改的时候,并没有影响到父进程。 注意: 1.一般来说,fork之后父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。 2.从上面可以看到两次的运行结果不一样。我们知道write函数是不带缓存的。因为在fork之前调用write,所以其数据写到标准输出一次。但是,标准 I/O库是带缓存的。如果标准输出连到终端设备,则它是行缓存的,否则它是全缓存的。当以交互方式运行该程序时,只得到printf输出的行一次,其原因是标准输出缓存由新行符刷新。但是当将标准输出重新定向到一个文件时,却得到printf输出行两次。其原因是,在fork之前调用了printf一次,当调用fork时,该行数据仍在缓存中,然后在父进程数据空间复制到子进程中时,该缓存数据也被复制到子进程中。于是那时父、子进程各自有了带该行内容的缓存。在exit之前的第二个printf将其数据添加到现存的缓存中。当每个进程终止时,其缓存中的内容被写到相应文件中。 实例 2: #include <stdio.h> #include <stdlib.h> #include <unistd.h> int glob = 6; int main() { int var; int pid; var = 88; printf("father:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); printf("__________________________________\n"); if((pid = fork()) < 0) { perror("fail to fork"); return -1; }else if(pid == 0) { printf("child var value not change\n:"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); glob ++; var ++; printf("__________________________________\n"); printf("child var value change:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); } exit(0); } 运行结果如下:
从上面可以看出,根据copy-on-write的思想,在子进程中,改变父进程的数据时,会先 复制父进程的数据修然后再改,从而达到子进程对数据的修改不影响父进程。但是我们发现,复制的前后,其值的地址都是一样的。为什么呢?子进程拷贝的时候也拷贝了父进程的虚拟内存"页",这样他们的虚拟地址都一样,但是对应不同的物理内存空间。 二、copy-on-write工作原理 假设进程A创建子进程B,之后进程A和进程B共享A的地址空间,同时该地址空间中的页面全部被标识为写保护。此时B若写address的页面,由于写保护的原因会引起写异常,在异常处理中,内核将address所在的那个写保护页面复制为新的页面,让B的address页表项指向该新的页面,新页面可写。而A的address页表项依然指向那个写保护的页面。然后当B在访问address时就会直接访问新的页面了,不会在访问到哪个写保护的页面。当A试图写address所在的页面时,由于写保护的原因此时也会引起异常,在异常处理中,内核如果发现该页面只有一个拥有进程,此种情况下也就是A,则直接对该页面取消写保护,此后当A再访问address时不会在有写保护错误了。如果此时A又创建子进程C,则该address所在的页面又被设置为写保护,拥有进程A和C,同时其他页面例如PAGEX依然维持写保护,只是拥有进程A、B和C。如果此时A访问PAGEX,则异常处理会创建一个新页面并将PAGEX中的内容复制到该页面,同时A相应 的pte指向该新页面。如果此时C也访问PAGEX,也会复制新页面并且让C对应的pte指向新页面。如果B再访问PAGEX,则由于此时PAGEX只有一个拥有进程B,故不再复制新页面,而是直接取消该页面的写保护,由于B的pte本来就是直接指向该页面,所以无需要在做其它工作。 三、exit和_exit (1)正常终止: (a)在main函数内执行return语句。这等效于调用exit。 (b)调用exit函数 (c)调用_exit系统调用函数 (2)异常终止: (a)调用abort。它产生SIGABRT信号,所以是一种异常终止的一种特列。 (b)当进程接收到某个信号时。例如,进程越出其地址空间访问存储单元,或者除以0,内核就会为该进程产生相应的信号。 注意:不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。 exit和_exit的不同
_exit()函数的作用最为简单:直接进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构; exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是"清理I/O"缓冲。 探究 1._exit() //_exit(0) exit(0) return 0
编译运行结果:
从上面我们看到,test.txt的内容为空.为什么呢?因为标准I/O函数是带缓存的,进行fputs的时候是先向缓存中写的,只有当缓存满的时候才会刷新的缓冲区的。从以上我们发现,当进程退出时,执行_exit()函数并没有刷新缓冲区的数据,而是直接终止进程的。 探究2.exit()
编译运行结果:
从上面我们可以看到,当exit()函数结束进程的时候,对缓存进行了处理,把缓存的数据写到了磁盘文件中。 探究3.return 由读者自己完成,其实return语句用在main函数中,和exit是一样的。但是我们知道,return返回的值是给调用者的,它代表着一个函数的结束。 四、exec函数族 exec.c 调用exec其中的一个函数; gcc exec.c -o exec; ./exec exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段、和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。 可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 何时使用? 当进程认为自己不能再为系统和用户做任何贡献了就可以调用exec函数族中的函数,让自己执行新的程序。 当前目录: 可执行程序A B(1,2,3) 如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样。 execl("./B","B","1","2","3",NULL); char *const envp[] = {"B","1","2","3",NULL} execv("./B",envp);
注意:不管file,第一个参数必须是可执行文件的名字 可执行文件查找方式 表中的前四个函数的查找方式都是指定完整的文件目录路劲,而最后两个函数(以p结尾的函数)可以只给出文件名,系统会自动从环境变量"$PATH"所包含的路径中进行查找。 参数表传递方式 两种方式:一个一个列举和将所有参数通过指针数组传递 一函数名的第5个字母按来区分,字母"l"(list)的表示一个一个列举方式;字母"v"(vector)的表示将所有参数构造成指针数组传递,其语法为char *const argv[] 环境变量的使用 exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。这里,以"e"(Envirment)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量。 使用的区别 可执行文件查找方式 参数表传递方式 环境变量的使用
案例一execl #include <stdio.h> #include <unistd.h> int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execl("/bin/ls","ls",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n"); return 0; } 运行结果如下:
案例二、execlp #include <stdio.h> #include <unistd.h> int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execlp("ls","ls","-l",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n"); return 0; } 运行结果:
案例三、execle #include <stdio.h> #include <stdlib.h> int main(int argc,char *argv[]) { if(getenv("B") == NULL) { printf("fail to getenv B.\n"); }else{ printf("env B = %s.\n",getenv("B")); } if(getenv("C") == NULL) { printf("fail to getenv C.\n"); }else{ printf("env C = %s.\n",getenv("C")); } if(getenv("PATH") == NULL) { printf("fail to getenv PATH.\n"); }else{ printf("env PATH = %s.\n",getenv("PATH")); } return 0; } 运行结果:
#include <unistd.h> int main(int argc,char *argv[]) { printf("start to execle.\n"); char * const envp[] = {"B=hello",NULL}; if(execle("./A.out","A.out",NULL,envp) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n"); return 0; } 运行结果:
案例四:execv #include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> int main() { char * const arg[] = {"ps", "-ef", NULL}; //if (execl("/bin/ps", "ps", "-ef", NULL) < 0) if (execv("/bin/ps" ,arg) < 0) { perror("execl"); exit(-1); } while (1); return 0; } 五、进程的创建vfork()函数 vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空完全复制到子进程中,因为子进程会立即调用exec(或exit)于是也就不会存、访该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中运行。 vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后 父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁) 探究1.vfork()
编译运行:
因为我们知道vfork保证子进程先运行,子进程运行结束后,父进程才开始运行。所以,第一次打印的是子进程的打印的信息,可以看到var值变成了89。子进程结束后,父进程运行,父进程首先打印fork调用返回给他pid的值(就是子进程pid)。以上我们可以看出,vfork创建的子进程和父进程运行的地址空间相同(子进程改变了var 值,父进程中的var值也进行了改变)。 注意:如果子进程中执行的是exec函数,那就是典型的fork的copy-on-wirte。 五、wait和waitpid wait函数:调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。 waitpid函数:功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)。 wait函数 #include <sys/types.h> #include <sys/waith.h> pid_t wait(int *status); 函数参数: status是一个整型指针,指向的对象用来保存子进程退出时的状态。 A.status若为空,表示忽略子进程退出时的状态 B.status若不为空,表示保存子进程退出时的状态 子进程的结束状态可由Linux中一些特定的宏来测定。 案例一、 #include <stdio.h> #include <stdlib.h> int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child exit now.\n"); exit(0); }else{ while(1); } exit(0); } 运行结果:
从以上可以看出,子进程正常退出时,处于僵尸态。这个时候子进程的pid,以及内核栈资源并没有释放,这样是不合理的,我们应该避免僵尸进程。如果父进程先退出呢,子进程又会怎样? #include <stdio.h> #include <stdlib.h> int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child running now - pid : %d.\n",getpid()); while(1); }else{ getchar(); printf("Father exit now - pid : %d.\n",getpid()); exit(0); } }
从上面可以看出,如果父进程先退出,则子进程的父进程的ID号变为1,也就是说当一个子进程的父进程退出时,这个子进程会被init进程自动收养。 案例二、利用wait等待回收处于僵尸态的子进程 #include <stdio.h> #include <stdlib.h> int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child runing now - pid : %d.\n",getpid()); getchar(); printf("child exiting now - pid : %d.\n",getpid()); exit(0); }else{ printf("Father wait zombie now - pid : %d.\n",getpid()); wait(NULL); printf("Father exiting now - pid : %d.\n",getpid()); exit(0); } } 没有输入字符前:
输入字符后:
此时我们没有发现僵尸进程,当子进程退出时,父进程的wait回收了子进程未释放的资源。 案例三、获取进程退出时的状态 #include <stdio.h> #include <stdlib.h> int main() { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } } printf("All child process is exit,father is exit.\n"); exit(0); }
给进程15494发个信号
程序运行结果:
从以上探究可以知道,每当子进程结束后,wait函数就会返回哪个子进程结束的pid。如果没有子进程存在,wait函数就返回-1。 函数返回值: 成功:子进程的进程号 失败:-1 #include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid,int *status,int options); 参数: 1.在父进程中创建两个子进程(A B) 2.A进程打印"child process %d exit",调用exit(2),结束 3.B进程一直运行 注意:父进程调用while(waitpid(-1,&status,WUNTRACED) != -1 )
status:同wait options: WNOHANG,若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0 WUNTRACED,若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还没报告过,则返回其状态。 0:同wait,阻塞父进程,等待子进程退出。 返回值 正常:结束的子进程的进程号 使用选项WNOHANG且没有子进程结束时:0 调用出错:-1 案例一、 #include <stdio.h> #include <stdlib.h> int main() { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } } printf("All child process is exit,father is exit.\n"); exit(0); } 程序运行结果:
使用ps -aux结果
从以上可以看出,子进程15783退出时,父进程并没有回收它的资源,此时可以看到它处于僵尸态。 由于父进程调用waitpid等待子进程15784退出,此时这个进程还没退出,看可以看到父进程处于可中断的睡眠状态。 我们给子进程15784发个信号,在看看结果
程序运行结果:
可以看到当waitpid指定等待的进程退出时,waitpid立即返回,此时父进程退出。 案例二、 #include <stdio.h> #include <stdlib.h> int main() { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ sleep(2); printf("Father wait child %d exit.\n",pid); while((pid = waitpid(pid,NULL,WNOHANG))) { printf("The process %d is exit.\n",pid); } printf("The process %d is exit.\n",pid); } } exit(0); }
从上面探究我们可以看出,如果有子进程处于僵尸态,waitpid(pid,NULL,WNOHANG)立即处理后返回,如果没有子进程处于僵尸态,此时waitpid(pid,NULL,WNOHANG)也会立即返回,而不阻塞,此时返回值为0。 案例探究三、 #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char *argv[]) { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ sleep(3); printf("create child process : %d.\n",getpid()); setpgid(0,0); //让子进程属于以自己ID作为组的进程组 printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(6).\n",getpid()); }else{ while(pid = waitpid(0,NULL,0)) { printf("Father wait the process %d is exit.\n",pid); } } } exit(0); } 运行结果:
当在父进中创建子进程时,父进程和子进程都在以父进程ID号为组的进程组。以上代码中,有一个子进程改变了自己所在的进程组. 此时waitpid(0,NULL,0);只能处理以父进程ID为组的进程组中的进程,可以看到第一个子进程结束后waitpid函数回收了它未释放的资源,而第二个子进程则处于僵尸态
from:http://blog.chinaunix.net/uid-26833883-id-3222794.htmlRFID管理系统集成商 RFID中间件 条码系统中间层 物联网软件集成
从上面可以看出,因为子进程和父进程拥有独立的物理内存空间,所以当子进程对拷贝来的数据做修改的时候,并没有影响到父进程。 注意: 1.一般来说,fork之后父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。 2.从上面可以看到两次的运行结果不一样。我们知道write函数是不带缓存的。因为在fork之前调用write,所以其数据写到标准输出一次。但是,标准 I/O库是带缓存的。如果标准输出连到终端设备,则它是行缓存的,否则它是全缓存的。当以交互方式运行该程序时,只得到printf输出的行一次,其原因是标准输出缓存由新行符刷新。但是当将标准输出重新定向到一个文件时,却得到printf输出行两次。其原因是,在fork之前调用了printf一次,当调用fork时,该行数据仍在缓存中,然后在父进程数据空间复制到子进程中时,该缓存数据也被复制到子进程中。于是那时父、子进程各自有了带该行内容的缓存。在exit之前的第二个printf将其数据添加到现存的缓存中。当每个进程终止时,其缓存中的内容被写到相应文件中。 实例 2: #include <stdio.h> #include <stdlib.h> #include <unistd.h> int glob = 6; int main() { int var; int pid; var = 88; printf("father:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); printf("__________________________________\n"); if((pid = fork()) < 0) { perror("fail to fork"); return -1; }else if(pid == 0) { printf("child var value not change\n:"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); glob ++; var ++; printf("__________________________________\n"); printf("child var value change:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); } exit(0); } 运行结果如下:
从上面可以看出,根据copy-on-write的思想,在子进程中,改变父进程的数据时,会先 复制父进程的数据修然后再改,从而达到子进程对数据的修改不影响父进程。但是我们发现,复制的前后,其值的地址都是一样的。为什么呢?子进程拷贝的时候也拷贝了父进程的虚拟内存"页",这样他们的虚拟地址都一样,但是对应不同的物理内存空间。 二、copy-on-write工作原理 假设进程A创建子进程B,之后进程A和进程B共享A的地址空间,同时该地址空间中的页面全部被标识为写保护。此时B若写address的页面,由于写保护的原因会引起写异常,在异常处理中,内核将address所在的那个写保护页面复制为新的页面,让B的address页表项指向该新的页面,新页面可写。而A的address页表项依然指向那个写保护的页面。然后当B在访问address时就会直接访问新的页面了,不会在访问到哪个写保护的页面。当A试图写address所在的页面时,由于写保护的原因此时也会引起异常,在异常处理中,内核如果发现该页面只有一个拥有进程,此种情况下也就是A,则直接对该页面取消写保护,此后当A再访问address时不会在有写保护错误了。如果此时A又创建子进程C,则该address所在的页面又被设置为写保护,拥有进程A和C,同时其他页面例如PAGEX依然维持写保护,只是拥有进程A、B和C。如果此时A访问PAGEX,则异常处理会创建一个新页面并将PAGEX中的内容复制到该页面,同时A相应 的pte指向该新页面。如果此时C也访问PAGEX,也会复制新页面并且让C对应的pte指向新页面。如果B再访问PAGEX,则由于此时PAGEX只有一个拥有进程B,故不再复制新页面,而是直接取消该页面的写保护,由于B的pte本来就是直接指向该页面,所以无需要在做其它工作。 三、exit和_exit (1)正常终止: (a)在main函数内执行return语句。这等效于调用exit。 (b)调用exit函数 (c)调用_exit系统调用函数 (2)异常终止: (a)调用abort。它产生SIGABRT信号,所以是一种异常终止的一种特列。 (b)当进程接收到某个信号时。例如,进程越出其地址空间访问存储单元,或者除以0,内核就会为该进程产生相应的信号。 注意:不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。 exit和_exit的不同
_exit()函数的作用最为简单:直接进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构; exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是"清理I/O"缓冲。 探究 1._exit() //_exit(0) exit(0) return 0
编译运行结果:
从上面我们看到,test.txt的内容为空.为什么呢?因为标准I/O函数是带缓存的,进行fputs的时候是先向缓存中写的,只有当缓存满的时候才会刷新的缓冲区的。从以上我们发现,当进程退出时,执行_exit()函数并没有刷新缓冲区的数据,而是直接终止进程的。 探究2.exit()
编译运行结果:
从上面我们可以看到,当exit()函数结束进程的时候,对缓存进行了处理,把缓存的数据写到了磁盘文件中。 探究3.return 由读者自己完成,其实return语句用在main函数中,和exit是一样的。但是我们知道,return返回的值是给调用者的,它代表着一个函数的结束。 四、exec函数族 exec.c 调用exec其中的一个函数; gcc exec.c -o exec; ./exec exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段、和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。 可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 何时使用? 当进程认为自己不能再为系统和用户做任何贡献了就可以调用exec函数族中的函数,让自己执行新的程序。 当前目录: 可执行程序A B(1,2,3) 如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样。 execl("./B","B","1","2","3",NULL); char *const envp[] = {"B","1","2","3",NULL} execv("./B",envp);
注意:不管file,第一个参数必须是可执行文件的名字 可执行文件查找方式 表中的前四个函数的查找方式都是指定完整的文件目录路劲,而最后两个函数(以p结尾的函数)可以只给出文件名,系统会自动从环境变量"$PATH"所包含的路径中进行查找。 参数表传递方式 两种方式:一个一个列举和将所有参数通过指针数组传递 一函数名的第5个字母按来区分,字母"l"(list)的表示一个一个列举方式;字母"v"(vector)的表示将所有参数构造成指针数组传递,其语法为char *const argv[] 环境变量的使用 exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。这里,以"e"(Envirment)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量。 使用的区别 可执行文件查找方式 参数表传递方式 环境变量的使用
案例一execl #include <stdio.h> #include <unistd.h> int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execl("/bin/ls","ls",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n"); return 0; } 运行结果如下:
案例二、execlp #include <stdio.h> #include <unistd.h> int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execlp("ls","ls","-l",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n"); return 0; } 运行结果:
案例三、execle #include <stdio.h> #include <stdlib.h> int main(int argc,char *argv[]) { if(getenv("B") == NULL) { printf("fail to getenv B.\n"); }else{ printf("env B = %s.\n",getenv("B")); } if(getenv("C") == NULL) { printf("fail to getenv C.\n"); }else{ printf("env C = %s.\n",getenv("C")); } if(getenv("PATH") == NULL) { printf("fail to getenv PATH.\n"); }else{ printf("env PATH = %s.\n",getenv("PATH")); } return 0; } 运行结果:
#include <unistd.h> int main(int argc,char *argv[]) { printf("start to execle.\n"); char * const envp[] = {"B=hello",NULL}; if(execle("./A.out","A.out",NULL,envp) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n"); return 0; } 运行结果:
案例四:execv #include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> int main() { char * const arg[] = {"ps", "-ef", NULL}; //if (execl("/bin/ps", "ps", "-ef", NULL) < 0) if (execv("/bin/ps" ,arg) < 0) { perror("execl"); exit(-1); } while (1); return 0; } 五、进程的创建vfork()函数 vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空完全复制到子进程中,因为子进程会立即调用exec(或exit)于是也就不会存、访该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中运行。 vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后 父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁) 探究1.vfork()
编译运行:
因为我们知道vfork保证子进程先运行,子进程运行结束后,父进程才开始运行。所以,第一次打印的是子进程的打印的信息,可以看到var值变成了89。子进程结束后,父进程运行,父进程首先打印fork调用返回给他pid的值(就是子进程pid)。以上我们可以看出,vfork创建的子进程和父进程运行的地址空间相同(子进程改变了var 值,父进程中的var值也进行了改变)。 注意:如果子进程中执行的是exec函数,那就是典型的fork的copy-on-wirte。 五、wait和waitpid wait函数:调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。 waitpid函数:功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)。 wait函数 #include <sys/types.h> #include <sys/waith.h> pid_t wait(int *status); 函数参数: status是一个整型指针,指向的对象用来保存子进程退出时的状态。 A.status若为空,表示忽略子进程退出时的状态 B.status若不为空,表示保存子进程退出时的状态 子进程的结束状态可由Linux中一些特定的宏来测定。 案例一、 #include <stdio.h> #include <stdlib.h> int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child exit now.\n"); exit(0); }else{ while(1); } exit(0); } 运行结果:
从以上可以看出,子进程正常退出时,处于僵尸态。这个时候子进程的pid,以及内核栈资源并没有释放,这样是不合理的,我们应该避免僵尸进程。如果父进程先退出呢,子进程又会怎样? #include <stdio.h> #include <stdlib.h> int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child running now - pid : %d.\n",getpid()); while(1); }else{ getchar(); printf("Father exit now - pid : %d.\n",getpid()); exit(0); } }
从上面可以看出,如果父进程先退出,则子进程的父进程的ID号变为1,也就是说当一个子进程的父进程退出时,这个子进程会被init进程自动收养。 案例二、利用wait等待回收处于僵尸态的子进程 #include <stdio.h> #include <stdlib.h> int main() { int pid; if((pid = fork()) < 0) { perror("Fail to fork"); return -1; }else if(pid == 0){ printf("child runing now - pid : %d.\n",getpid()); getchar(); printf("child exiting now - pid : %d.\n",getpid()); exit(0); }else{ printf("Father wait zombie now - pid : %d.\n",getpid()); wait(NULL); printf("Father exiting now - pid : %d.\n",getpid()); exit(0); } } 没有输入字符前:
输入字符后:
此时我们没有发现僵尸进程,当子进程退出时,父进程的wait回收了子进程未释放的资源。 案例三、获取进程退出时的状态 #include <stdio.h> #include <stdlib.h> int main() { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } } printf("All child process is exit,father is exit.\n"); exit(0); }
给进程15494发个信号
程序运行结果:
从以上探究可以知道,每当子进程结束后,wait函数就会返回哪个子进程结束的pid。如果没有子进程存在,wait函数就返回-1。 函数返回值: 成功:子进程的进程号 失败:-1 #include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid,int *status,int options); 参数: 1.在父进程中创建两个子进程(A B) 2.A进程打印"child process %d exit",调用exit(2),结束 3.B进程一直运行 注意:父进程调用while(waitpid(-1,&status,WUNTRACED) != -1 )
status:同wait options: WNOHANG,若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0 WUNTRACED,若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还没报告过,则返回其状态。 0:同wait,阻塞父进程,等待子进程退出。 返回值 正常:结束的子进程的进程号 使用选项WNOHANG且没有子进程结束时:0 调用出错:-1 案例一、 #include <stdio.h> #include <stdlib.h> int main() { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status)); }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } } printf("All child process is exit,father is exit.\n"); exit(0); } 程序运行结果:
使用ps -aux结果
从以上可以看出,子进程15783退出时,父进程并没有回收它的资源,此时可以看到它处于僵尸态。 由于父进程调用waitpid等待子进程15784退出,此时这个进程还没退出,看可以看到父进程处于可中断的睡眠状态。 我们给子进程15784发个信号,在看看结果
程序运行结果:
可以看到当waitpid指定等待的进程退出时,waitpid立即返回,此时父进程退出。 案例二、 #include <stdio.h> #include <stdlib.h> int main() { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ sleep(2); printf("Father wait child %d exit.\n",pid); while((pid = waitpid(pid,NULL,WNOHANG))) { printf("The process %d is exit.\n",pid); } printf("The process %d is exit.\n",pid); } } exit(0); }
从上面探究我们可以看出,如果有子进程处于僵尸态,waitpid(pid,NULL,WNOHANG)立即处理后返回,如果没有子进程处于僵尸态,此时waitpid(pid,NULL,WNOHANG)也会立即返回,而不阻塞,此时返回值为0。 案例探究三、 #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc,char *argv[]) { int pid; int status; if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{ if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ sleep(3); printf("create child process : %d.\n",getpid()); setpgid(0,0); //让子进程属于以自己ID作为组的进程组 printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(6).\n",getpid()); }else{ while(pid = waitpid(0,NULL,0)) { printf("Father wait the process %d is exit.\n",pid); } } } exit(0); } 运行结果:
当在父进中创建子进程时,父进程和子进程都在以父进程ID号为组的进程组。以上代码中,有一个子进程改变了自己所在的进程组. 此时waitpid(0,NULL,0);只能处理以父进程ID为组的进程组中的进程,可以看到第一个子进程结束后waitpid函数回收了它未释放的资源,而第二个子进程则处于僵尸态
from:http://blog.chinaunix.net/uid-26833883-id-3222794.htmlRFID管理系统集成商 RFID中间件 条码系统中间层 物联网软件集成