进程的创建和控制
2018-03-24 18:27
218 查看
我们已经了解了进程的基本概念,今天我们来看下怎样创建和控制进程吧!
1. 进程的创建
(1)fork()函数
在Linux中,fork 是非常重要的系统函数,它从已经存在的进程中创建一个新的进程。新进程为子进程,原进程为父进程。
函数原型:
返回值:子进程返回0,父进程返回子进程的id ,出错返回-1.
我们先利用fork 函数创建一个进程; 1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t pid;
8
9 printf("before: pid is %d\n",getpid());
10
11 pid=fork();
12 if(pid==-1)
13 { perror("fork");
14 exit(1);
15 }
16 printf("after:pid is %d,fork return %d\n" ,getpid(),pid);
17 sleep(1);
18 return 0;
19 }
20
看运行结果:
首先,我们可以看到这里有三行输出,一行before,两行after。before先打印了进程2544的信息,然后after又打印了进程2544的信息,同时也打印了进程2545的信息。这是为什么呢?因为fork之前父进程独立运行,fork之后,父子进程两个执行流分别执行。如下图:
其次,我们可以看到父进程返回的是子进程的id,子进程返回的是0.
fork的常规用法:
(1)一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程处理请求。
(2)一个进程要执行不同的程序,例如:子进程从fork返回后,调用exec函数。
fork调用失败的原因
(1)系统中的进程太多。
(2)实际用户的进程数超出了限制。
注意:通常,使用fork之后,操作系统会给子进程分配PCB, 虚拟地址空间,用户级页表。而父进程和子进程的代码共享,数据也是共享的;但是当任意一方开始写入时,便以写时拷贝的方式各自拥有数据。
(2)vfork()函数
功能:创建一个子进程
返回值:父进程返回子进程的id,子进程返回0;出错返回-1;
我们利用vfork函数创建进程: 5 int glob=100;
6 int main()
7 {
8 pid_t pid;
9 pid=fork();
10 if(pid==-1)
11 { perror("fork");
12 exit(1);
13 }
14 if(pid==0)
15 {
16 sleep(5);
17 glob=200;
18 printf("child glob %d\n",glob);
19 exit(0);
20 }
21 else
22 printf("parent glob %d\n",glob);
23
24 return 0;
25 }
运行结果:
我们可以看到,子进程直接改变了父进程的值,这是因为子进程在父进程的虚拟地址空间运行。
(3)fork 和 vfork 的区别
1)vfork 用于创建一个子进程,而子进程和父进程共享地址空间。fork的子进程具有独立的虚拟地址空间。
2)vfork 保证子进程先运行,在它调用exec或者(exit)之后父进程才可能被调度执行。fork 的子进程和父进程谁先执行完全由调度器决定。
2. 进程终止
(1)进程退出场景
代码运行完毕,结果正确;
代码运行完毕,结果不正确;
代码异常终止。
(2)进程常见退出方法
1)正常终止:
从main函数返回;调用 exit 函数和 _exit 函数 。
2)异常退出:
ctrl + c,信号终止;
3)return 退出:
return 退出是一种常见的退出进程方法,执行return (n)等于执行exit (n),因为调用main 的运行时函数会将 main 的返回值当作exit 的参数。
(3)exit 函数和 _exit 函数exit 函数
函数原型:#include <unistd.h>
void exit( int status);
参数:status 定义了进程的终止状态,父进程通过wait 来获取该值。
(1)为什么要有进程等待呢?
1)在前面讲过,子进程如果退出,父进程不管不顾,子进程就成为僵尸进程,就会造成内存泄漏的问题。
2)而僵尸进程是刀枪不入的,就连kill -9(9号信号)也无能为力,没有办法杀死。
3)父进程派发给子进程的任务是否被完成,父进程需要知道。例如:子进程在运行过程中,结果是对是错,或者是否正常退出。
4)父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。
(2)进程等待的方法wait 方法
函数原型:#include <sys/types.h>
#include <sys/wait.h>
pid_t wait ( int *status);
参数:输出
a8b4
型参数,获取子进程的退出状态,不关心则可以设置为NULL;
返回值:成功返回被等待的进程pid ,失败返回 -1.代码实现wait 1 #include <sys/wait.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <errno.h>
6
7 int main()
8 {
9 pid_t pid;
10 pid =fork();
11 if(pid ==-1)
12 {
13 perror("fork\n");
14 exit(1);
15 }
16
17 if(pid ==0)
18 {
19 sleep(20);
20 exit(10);
21 }
22 else{
23 int st;
}
16
17 if(pid ==0)
18 {
19 sleep(20);
20 exit(10);
21 }
22 else{
23 int st;
24 int ret=wait(&st);
25
26 if(ret>0&&(st&0x7F)==0){
27 printf("child exit code:%d\n",(st>>8)&0xFF);
28 }
29 else if(ret>0)
30 {
31 printf("sig code:%d\n",st&0x7F);
32 }
33 }
34 }
waitpid 方法
函数原型:pid_t waitpid (pid_t pid, int* status,int options);
参数: pid :
pid=-1,等待任一个子进程,与wait 等效;
pid > 0,等待其进程id 与pid 相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程的状态,则为真。(查看进程是否是正常退出的)
WEXITSTATUS(status ):若WIFEXITED非0,提取子进程退出码。(查看进程退出码 )
options:
WNOHANG: 若pid 指定的子进程没有结束,则wait_pid函数返回0,不予以等待。若正常结束,返回该子进程的id。
waitpid代码实现进程阻塞等待方式
4. 进程程序替换
(1) 替换函数
有六种以exec开头的替换函数,统称为exec函数:#include <unistd.h>
int execl (const char* path,const char* arg, ...); // l(list) 表示参数采用列表;
int execlp( const char* path,const char *arg, ...); //p (path) 有 p 自动搜索环境变量PATH;
int execle( const char* path, const char *arg, ... ,char *const envp[ ] ); //e(env) 表示自己维护环境变量;
int execv ( const char* path , char *const argv[ ] );//v( vector ),参数用数组;
int execvp( const char* file , char *const argv[ ] );
int execve ( const char* path , char *const argv[ ] , char *const envp[ ] );
注:这些函数如果调用成功,则加载新的程序从启动代码开始执行,不再返回。
如果调用出错,则返回 -1;
exec 函数只有出错返回值没有成功返回值。
(2)exec 调用举例 int main()
4 {
5 char* const argv[]={"ps","-ef",NULL};
6 char* const envp[]={"PATH=/bin:/usr/bin","TERM=console",NULL};
7
8 execl("/bin/ps","ps","-ef",NULL);
9
10 execlp("ps","ps","-ef",NULL);
11
12 execle("ps","ps","-ef",NULL,envp);
13
14 execv("/bin/ps",argv);
15
16 execvp("ps",argv);
17
18 execve("/bin/ps",argv,envp);
19
20 exit(0);
21 }
1. 进程的创建
(1)fork()函数
在Linux中,fork 是非常重要的系统函数,它从已经存在的进程中创建一个新的进程。新进程为子进程,原进程为父进程。
函数原型:
返回值:子进程返回0,父进程返回子进程的id ,出错返回-1.
我们先利用fork 函数创建一个进程; 1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t pid;
8
9 printf("before: pid is %d\n",getpid());
10
11 pid=fork();
12 if(pid==-1)
13 { perror("fork");
14 exit(1);
15 }
16 printf("after:pid is %d,fork return %d\n" ,getpid(),pid);
17 sleep(1);
18 return 0;
19 }
20
看运行结果:
首先,我们可以看到这里有三行输出,一行before,两行after。before先打印了进程2544的信息,然后after又打印了进程2544的信息,同时也打印了进程2545的信息。这是为什么呢?因为fork之前父进程独立运行,fork之后,父子进程两个执行流分别执行。如下图:
其次,我们可以看到父进程返回的是子进程的id,子进程返回的是0.
fork的常规用法:
(1)一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程处理请求。
(2)一个进程要执行不同的程序,例如:子进程从fork返回后,调用exec函数。
fork调用失败的原因
(1)系统中的进程太多。
(2)实际用户的进程数超出了限制。
注意:通常,使用fork之后,操作系统会给子进程分配PCB, 虚拟地址空间,用户级页表。而父进程和子进程的代码共享,数据也是共享的;但是当任意一方开始写入时,便以写时拷贝的方式各自拥有数据。
(2)vfork()函数
功能:创建一个子进程
返回值:父进程返回子进程的id,子进程返回0;出错返回-1;
我们利用vfork函数创建进程: 5 int glob=100;
6 int main()
7 {
8 pid_t pid;
9 pid=fork();
10 if(pid==-1)
11 { perror("fork");
12 exit(1);
13 }
14 if(pid==0)
15 {
16 sleep(5);
17 glob=200;
18 printf("child glob %d\n",glob);
19 exit(0);
20 }
21 else
22 printf("parent glob %d\n",glob);
23
24 return 0;
25 }
运行结果:
我们可以看到,子进程直接改变了父进程的值,这是因为子进程在父进程的虚拟地址空间运行。
(3)fork 和 vfork 的区别
1)vfork 用于创建一个子进程,而子进程和父进程共享地址空间。fork的子进程具有独立的虚拟地址空间。
2)vfork 保证子进程先运行,在它调用exec或者(exit)之后父进程才可能被调度执行。fork 的子进程和父进程谁先执行完全由调度器决定。
2. 进程终止
(1)进程退出场景
代码运行完毕,结果正确;
代码运行完毕,结果不正确;
代码异常终止。
(2)进程常见退出方法
1)正常终止:
从main函数返回;调用 exit 函数和 _exit 函数 。
2)异常退出:
ctrl + c,信号终止;
3)return 退出:
return 退出是一种常见的退出进程方法,执行return (n)等于执行exit (n),因为调用main 的运行时函数会将 main 的返回值当作exit 的参数。
(3)exit 函数和 _exit 函数exit 函数
函数原型:#include <unistd.h>
void exit( int status);
参数:status 定义了进程的终止状态,父进程通过wait 来获取该值。
_exit 函数 函数原型:#include <unistd.h> void _exit (int status); exit 函数最后也会调用 _exit 函数,但在调用exit 之前,还做了以下工作: 1.执行用户通过atexit 或者on_exit 定义的清理函数; 2.关闭所有打开的流,所有的缓存数据均被写入; 3.调用_exit 函数;3. 进程等待
(1)为什么要有进程等待呢?
1)在前面讲过,子进程如果退出,父进程不管不顾,子进程就成为僵尸进程,就会造成内存泄漏的问题。
2)而僵尸进程是刀枪不入的,就连kill -9(9号信号)也无能为力,没有办法杀死。
3)父进程派发给子进程的任务是否被完成,父进程需要知道。例如:子进程在运行过程中,结果是对是错,或者是否正常退出。
4)父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。
(2)进程等待的方法wait 方法
函数原型:#include <sys/types.h>
#include <sys/wait.h>
pid_t wait ( int *status);
参数:输出
a8b4
型参数,获取子进程的退出状态,不关心则可以设置为NULL;
返回值:成功返回被等待的进程pid ,失败返回 -1.代码实现wait 1 #include <sys/wait.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <errno.h>
6
7 int main()
8 {
9 pid_t pid;
10 pid =fork();
11 if(pid ==-1)
12 {
13 perror("fork\n");
14 exit(1);
15 }
16
17 if(pid ==0)
18 {
19 sleep(20);
20 exit(10);
21 }
22 else{
23 int st;
}
16
17 if(pid ==0)
18 {
19 sleep(20);
20 exit(10);
21 }
22 else{
23 int st;
24 int ret=wait(&st);
25
26 if(ret>0&&(st&0x7F)==0){
27 printf("child exit code:%d\n",(st>>8)&0xFF);
28 }
29 else if(ret>0)
30 {
31 printf("sig code:%d\n",st&0x7F);
32 }
33 }
34 }
运行结果:
waitpid 方法
函数原型:pid_t waitpid (pid_t pid, int* status,int options);
参数: pid :
pid=-1,等待任一个子进程,与wait 等效;
pid > 0,等待其进程id 与pid 相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程的状态,则为真。(查看进程是否是正常退出的)
WEXITSTATUS(status ):若WIFEXITED非0,提取子进程退出码。(查看进程退出码 )
options:
WNOHANG: 若pid 指定的子进程没有结束,则wait_pid函数返回0,不予以等待。若正常结束,返回该子进程的id。
waitpid代码实现进程阻塞等待方式
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 6 int main() 7 { 8 pid_t pid=fork(); 9 if(pid<0) 10 { 11 perror("fork"); 12 return 1; 13 } 14 else if(pid==0){ 15 printf("child is run,pid is :%d\n",getpid()); 16 exit(257);} 17 else{ 18 int status=0; 19 pid_t ret =waitpid(-1,&status,0); 20 printf("this is test for wait\n"); 21 if(WIFEXITED(status)&& ret==pid){ 22 printf("wait child 5s success,child return code is:%d\n",WEXITST ATUS(status)); 23 } 24 else 25 { 26 printf("wait child failed,return\n"); 27 return 1; 28 } 29 } 30 return 0; 31 }运行结果:
4. 进程程序替换
(1) 替换函数
有六种以exec开头的替换函数,统称为exec函数:#include <unistd.h>
int execl (const char* path,const char* arg, ...); // l(list) 表示参数采用列表;
int execlp( const char* path,const char *arg, ...); //p (path) 有 p 自动搜索环境变量PATH;
int execle( const char* path, const char *arg, ... ,char *const envp[ ] ); //e(env) 表示自己维护环境变量;
int execv ( const char* path , char *const argv[ ] );//v( vector ),参数用数组;
int execvp( const char* file , char *const argv[ ] );
int execve ( const char* path , char *const argv[ ] , char *const envp[ ] );
注:这些函数如果调用成功,则加载新的程序从启动代码开始执行,不再返回。
如果调用出错,则返回 -1;
exec 函数只有出错返回值没有成功返回值。
(2)exec 调用举例 int main()
4 {
5 char* const argv[]={"ps","-ef",NULL};
6 char* const envp[]={"PATH=/bin:/usr/bin","TERM=console",NULL};
7
8 execl("/bin/ps","ps","-ef",NULL);
9
10 execlp("ps","ps","-ef",NULL);
11
12 execle("ps","ps","-ef",NULL,envp);
13
14 execv("/bin/ps",argv);
15
16 execvp("ps",argv);
17
18 execve("/bin/ps",argv,envp);
19
20 exit(0);
21 }
相关文章推荐
- 《unix高级环境编程》进程控制——创建进程
- 进程控制:进程的创建、终止、阻塞、唤醒和切换
- 【说解】在shell中通过mkfifo创建命名管道来控制多个进程并发执行
- CreateProcess()函数创建和控制进程
- 利用钩子技术控制进程创建
- 《unix高级环境编程》进程控制——创建进程
- 进程控制(上):进程创建,进程等待,进程终止
- (转)进程控制:进程的创建、终止、阻塞、唤醒和切换
- 进程控制-进程创建(fork、vfork)
- 挂接CreateProcessW实现对进程创建的完全控制
- 第6章 进程控制(2)_创建进程
- 进程控制—进程创建/等待/终止
- 38.windbg-调试技巧(创建进程即断下、r修改控制条件跳转)
- 通过HOOK控制进程的创建
- 进程控制中的进程创建,进程等待,进程程序替换等
- 利用钩子技术控制进程创建
- 利用钩子技术控制进程创建(附源代码)
- 创建子进程控制句柄继承能力
- 操作系统实验参考以上示例程序中建立并发进程的方法,编写一个多进程并发执行程序。父进 程首先创建一个执行ls命令的子进程然后再创建一个执行ps命令的子进程,并控制 ps 命令总在 ls 命令之前执行。
- 进程控制编程之僵尸进程、守护进程创建、进程等待