Linux Fork和exec系列函数
2014-01-15 16:27
246 查看
原文发在github.io博客上。
转载写明出处http://landerlyoung.github.io/blog/2014/01/13/linux-forkhe-execxi-lie-han-shu/linux 中的fork和exec函数是进程相关的两个函数,最早在大二的操作系统课上了解到。今天要写个小东西偶尔用到就研究了一下。
1.fork
fork的功能是创建一个和进程完全一样的子进程。完全的意思是指子进程的堆和栈和父进程是完全相同的。在子进程创建完成时,子进程和父进程共享内存。但是一旦共享的内存区域要被写入时(不管是父进程要写还是子进程要写)这块区域就会从父进程的进程空间复制到子进程,然后再执行写入。这就是通常说的copyon write,目的很明显,就是要节省不必要的内存消耗 。
这一点在安卓的虚拟机孵化进程zygote被使用,zygote在开机时就把所有系统java类的字节码加载到内存,当一个app启动时zygote就fork一下然后fork的子进程去执行app。这样所有app可以调用系统class而整个系统的内存中只有一份系统类,可以很大程度的节省内存, 同时也加快了app的启动。
linux系统中的所有进程都是init进程fork出来的,查看的话可以发现他的pid是1, 是系统内所有进程的父进程(或者祖先进程)。其实init也有个pid为0的父进程,开机完成后就不存在了,本文不涉及这方面。
fork包含在<unistd.h>头文件中, 其原型是:
pid_t fork(void)其中pid_t是进程pid的数据结构,可以被cast成int等类型,当frok成功时在父进程中的返回值是子进程的pid,
子进程中是0, 若没能创建子进程则返回负值。
写个小程序看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h> #include <unistd.h> #include <stdlib.h>//for exit() int main(void) { pid_t pid; printf("parent pid:%d\n", getpid()); if(pid = fork()) { // pid != 0, in parent process printf("in parent child pid:%d\n", pid); exit(0); } //in child process printf("I\'m Child process.\n" "My pid :%d, parent pid:%d\n", getpid(), getppid()); return 0; } |
1 2 3 4 5 6 | young@Y470:~/Desktop$gcc f.c -o f young@Y470:~/Desktop$./f parent pid:8627 in parent child pid:8628 I'm Child process. My pid :8628, parent pid:8627 |
这里在fork之后通过返回值知道自己是父进程还是子进程,然后通过if判断进行流程控制,父、子进程各自执行自己的任务。
2.exec系列函数
刚才说道linux系统内所有进程都是init进程的子孙进程,但是可能会让人不解:“这样的话所有进程不都是一样的吗”。这里我们要用到exec系列函数了。exec系列函数在执行时会首先清空当前进程(调用exec函数的进程)的栈和堆等内存空间。然后创建新的空间。但是进程的pid和父进程等信息不会变。
exec系列函数有一下几个:
1 2 3 4 5 6 | int execl(char const *path, char const *arg0, ...); int execle(char const *path, char const *arg0, ..., char const *envp); int execlp(char const *file, char const *arg0, ...); int execv(char const *path, char const *argv); int execve(char const *path, char const *argv, char const *envp); int execvp(char const *file, char const *argv); |
e - 给函数传入一个环境变量`environment virables`来搜索可执行文件 l - 命令的参数通过函数的参数一一传入`list`方式 p - 使用系统环境变量PATH搜索可执行文件 v - 命令的参数通过一个数组`vector`传入
需要说明的是:
如果使用带
l的函数,即execl、execle、execlp应该在参数列表最后传入一个NULL标记参数完毕。
如果是带v的函数就应该在数组的最后一项设置成NULL标记数组的完毕。
并且arg0(就是第一个参数)通常情况下都是可执行文件自身的名字,否则可能会导致函数调用失败,当然也有特例,为了行文通畅,放到 最后再说。
如果使用带有e的函数那么环境变量数组envp的最后也要有NULL标记数组的结束。
实验证明对于带有e的函数在传入正确的envp的前提下还要写对正确的路径,否则不能执行。
函数返回0表示正常。
写个小程序试试看:
1 2 3 4 5 67 | #include <stdio.h> #include <unistd.h> int main() { printf("process pid: %d\n", getpid()); execlp("./child", "child", NULL); printf("hello\n"); return 0; } |
1 2 3 | young@Y470:~/Desktop$./a process pid: 12369 pid: 12369 |
./child命令,第一行输出是原进程的输出,第二行输出是新进程(不是子进程)child的输出,注意printf没有执行。那是因为exec函数执行是清理了当强进程的内存空间整个进程可以说是直接换成了child进程。这个child是一个小程序,代码如下:
1 2 3 4 5 6 | #include <stdio.h> #include <unistd.h> int main(void) { printf("pid: %d\n", getpid()); return 0; } |
然后我们写个程序测试并说明上述所有exec函数的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2021 | #include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { char c = argv[1][0]; pid_t p; char* m_argv[] = { "echo", "echo", "Hello world", NULL, }; char *envp[] = { "/usr/bin", "/bin", NULL, }; printf("parent pid: %d\n", getpid()); switch (c){ case '1': if (!fork()) { printf("from execl pid: %d\n", getpid()); execl("/bin/echo", "echo", "Hello world", NULL); } break; case '2': if (!fork()) { printf("from execle pid: %d\n", getpid()); execle("/bin/echo", "echo", "Hello world", NULL, envp); } break; case '3': if (!fork()) { printf("from execlp pid: %d\n", getpid()); execlp("echo", "echo", "Hello world", NULL); } break; case '4': if (!fork()) { printf("from execv pid: %d\n", getpid()); execv("/bin/echo", m_argv); } break; case '5': if (!fork()) { printf("from execve pid: %d\n", getpid()); execve("/bin/echo", m_argv, envp); } break; default: if (!fork()) { printf("from execvp pid: %d\n", getpid()); execvp("echo", m_argv); } } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2021 | young@Y470:~/Desktop$for ((i = 1; i < 7; i++));do ./exec_test $i; echo ;done parent pid: 11046 from execl pid: 11047 Hello world parent pid: 11048 from execle pid: 11049 Hello world parent pid: 11050 from execlp pid: 11051 Hello world parent pid: 11052 from execv pid: 11053 echo Hello world parent pid: 11054 from execve pid: 11055 echo Hello world parent pid: 11056 from execvp pid: 11057 echo Hello world |
这里也展示了如何开启一个进程,就是fork之后在子进程执行exec。
3.附录
在上面说到arg0和可执行文件名不一样的情况。比如大家读知道的busybox就这一个例子。在c/c++语言中main函数想使用命令行参数的话就得使用下面的声明方式:
int main(int argc, char *argv[])
在程序执行是,argc至少是1, 所以argv0始终有值,他就是程序调用的可执行文件的名字。比如ls命令他的argv0始终都是“ls“。不妨写个程序测试:
1 2 34 | #include <stdio.h> int main(int argc, char *argv[]) { printf("%s\n", argv[0]); return 0; } |
1 2 3 4 5 67 | young@Y470:~/Desktop$make name cc name.c -o name young@Y470:~/Desktop$./name ./name young@Y470:~/Desktop$ln -s name some young@Y470:~/Desktop$./some ./some young@Y470:~/Desktop$/home/young/Desktop/name /home/young/Desktop/name young@Y470:~/Desktop$../Desktop/name ../Desktop/name |
ln -s busybox ls这样再调用ls时,argv0就是”ls“这样就能知道用户的目的然后执行ls的功能。
最后在写个程序测试一下:
1 2 3 4 5 67 | #include <stdio.h> #include <unistd.h> int main(void) { if(!fork()) { execlp("busybox", "busybox", NULL); } if(!fork()) { //arg0 和可执行文件名不一样 execlp("busybox", "ls", "-l", NULL); } return 0; } |
1 2 3 4 5 67 | young@Y470:~/Desktop/busy$Copyright (C) 1998-2011 Erik Andersen, Rob Landley, Denys Vlasenko and others. Licensed under GPLv2. See source distribution for full notice. Usage: busybox [function] [arguments]... or: busybox --list[-full] or: busybox --install [-s] [DIR] or: function [arguments]... BusyBox is a multi-call binary that combines many common Unix utilities into a single executable. Most people will create a ………… cttyhack, cut, date, dc, -rwxr-xr-x 1 young young 697656 Jan 13 03:29 busybox dd, -rwxr-xr-x 1 young young 6935 Jan 13 03:35 t deallocvt, -rw-r--r-- 1 young young 476 Jan 13 03:35 t.c depmod, df-rw-r--r-- 1 young young 0 Jan 13 03:30 ?? , diff, ………… |
大功告成,没想到好好写一篇博客需要3个小时!!!困死我了,怒睡!
相关文章推荐
- exec系列函数(execl,execlp,execle,execv,execvp)使用
- 启动新进程(fork和exec系列函数实现)
- system与exec系列函数对比
- exec... 系列函数
- 【经典转载】Linux进程学习系列之六 进程控制函数之exec()函数的学习
- exec系列函数
- linux进程编程-替换进程映-exec系列函数(execl,execlp,execle,execv,execvp)使用
- linux系统编程之进程(三):exec系列函数和system函数
- exec系列函数和system函数
- exec系列函数
- exec系列函数执行脚本文件
- exec系列函数(execl,execlp,execle,execv,execvp)的使用介绍
- Linux系统下的 exec系列函数
- linux的子进程调用exec( )系列函数
- linux系统编程之进程(五):exec系列函数(execl,execlp,execle,execv,execvp)使用
- Linux中exec()执行文件系列函数的使用说明
- exec系列函数(待续)
- 关于exec系列函数的文件路径问题及延伸
- fork()和vfork()的区别,signal函数用法,exec()系列函数的用法小结
- 《UNIX环境高级编程》笔记--exec系列函数