1.进程创建
fork()为什么有两个返回值?
- (1)fork()会创建子进程,两个进程都会指向return语句,所有有两个返回值。
- (2)fork()之后if和else会同时执行,id有两份,子进程查第一份,父进程查第二份。
- (3)创建一个进程,就等于多了一个PCB,地址空间,页表,映射关系。
- (4)fork()之后,父子进程代码共享,数据以写时拷贝的形式各自私有一份。(速度更快,效率更高,节省资源)
- (5)子进程返回0,父进程返回子进程的pid。
数据区(data seg):也称全局区或者静态区,根据名称我们就应该知道用来存放一些全局的东西,比如我们经常用到的静态变量、全局变量等都会存放到数据区,此区域上的东西都被全局所共享。比如我们可以采取类名.的方式就可以访问到方法,这就是所谓的静态方法,存放到数据区的。
代码区:存放程序编译后可以执行代码的地方。比如执行代码时写的While语句、if条件语句等,都会存放到此。
二.进程终止
1.程序退出的场景
- (1)代码运行完毕,结果正确
- (2)代码运行完毕,结果不正确
- (3)代码异常终止
进程终止就要把这个进程所占的物理内存资源释放掉,页表,地址空间都不要。
2.进程常见退出方法
正常终止:
- (1)从main函数返回
return退出
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做 exit的参数。
只有进程终止的时候,才会刷新缓冲区。
- (2)调用exit
在任何时候调用exit,都表示当前进程退出
#incude<unistd.h>
void exit(int status);
exit后也会调用exit, 但在调用exit之前,还做了其他工作:
- 执行用户通过 at_exit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用_exit
- (3)_exit
#include<unistd,h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
它终止时不会刷新缓冲区
异常终止:
ctrl + c,信号终止
echo $? 查看最近一个进程的退出码。
四.进程等待
1.进程等待的必要性
- (1)子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
- (2)另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
- (3)父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
- (4)父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
2.进程等待的方法
- (1)wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
//进程等待回收子进程的内存资源,避免内存泄漏。
1 #include<iostream>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<sys/wait.h>
5 #include<sys/types.h>
6
7 using namespace std;
8
9
10 int main()
11 {
12 pid_t id = fork();
13 if(id == 0)
14 {
15 //子进程
16 cout << "I am a child...." << endl;
17
18 sleep(5);
19 //sleep(10);
20 exit(1);
21 }
22 else
23 {//父进程
24 wait(NULL);
25 cout << "I am a father ..." << endl;
26 //sleep(20);
27 //wait(NULL);
28
29 //sleep(5);
30
31 }
- (2)waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数: pid: Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) >WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进 程的ID。
options的等待方式有两种:
(1)阻塞式:(pid = 0)调用scanf时,没有输入数据,把进程阻塞了,设置为非r状态,进入等待序列
唤醒:将状态设为r,将PCB放入运行队列中。
非阻塞式:当你要等某个人的时候,不能因为等它,而阻止自己发生的事。
- (1)如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- (2)如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- (3)如果不存在该子进程,则立即出错返回。
3.获取子进程的status
- (1)wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- (2) 如果传递NULL,表示不关心子进程的退出状态信息。
- (3) 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- (4)status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):
当收到信号时,先看是正常还是异常,如果正常就看退出码,如果异常的话就不用看。
37 int main()
38 {
39 pid_t id = fork();
40 if(id == 0)
41 {
42 //child
43 printf("child : pid : %d, ppid : %d \n",getpid(),getppid());
44 sleep(5);
45 exit(13);
46 }
47 else
48 {
49 //father
50 printf("father : pid : %d , ppid : %d\n",getpid(),getppid());
51 int status = 0;
52
53
54 while(1)
55 {
56 int ret = waitpid(id, &status, WNOHANG);
57
58 if(ret < 0)
59 {
60 printf("wait error , wait ret : %d\n",ret);
61 break;
62 }
63 else if(ret > 0)
64 {
65 printf("wait success... : %d\n",ret);
66 printf("exit status ... : %d\n",(status >> 8)&0xff);
67 printf("exit signal ... : %d\n",status & 0x7f);
68 }
69 else
70 {
71 sleep(1);
72 printf("parent wait again\n");
73 }
74 }
75 sleep(5);
76 }
77 return 0;
78 }
四.进程程序替换
1.替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
2.替换函数
include <unistd.h>`
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 execve(const char *path, char *const argv[], char *const envp[]);
3.函数解释
- (1)这些函数如果调用成功则加载新的程序从启动代码开始执行,不在返回。
- (2)如果调用出错则返回-1。
- (3)所以exec函数只有出错的返回值而没有成功的返回值。
4.命名理解
- (1)l(list):表示参数采用列表。
- (2)v(vector):参数用数组。
- (3)p(path):有p自动搜索环境变量PATH。
- (4)e(env):表示自己维护环境变量。
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
execl("/usr/bin/ls", "ls", "-a", "-l", "-i", NULL)
//execl中第一个参数带得是命令的路径。
execlp("top", "top", NULL);
//第一个top是你要执行谁,第二个top是你要怎样执行。
char* argv[] = {"ls", "-a", "-l", "-i", NULL};
execv("/usr/bin/ls", argv);
execvp("ls", argv);
return 0;
}
调用一个接口阻塞了,怎么办?
进程想从键盘里面输入数据,如果不输内容,程序就会在那里卡住,把一个进程卡住,不是进程不运行了,而是把进程设置为非r状态,然后放进等待队列中;如果事件就绪,操作系统就会把进程唤醒。
bash怎么把命令跑起来?
bash读取命令行上的所有命令,先fork()创建子进程,bash等子进程,子进程程序替换,子进程就跑传入的命令。
//简单的shell书写
#include <iostream>
2 #include <unistd.h>
3 #include <cstdio>
4 #include <string>
5 #include <cstring>
6 #include <stdlib.h>
7 #include <sys/wait.h>
8 #include <sys/types.h>
9
10
11 using namespace std;
12 #define NUM 1024
13
14 int main()
15 {
16 char buff[1024] = {0};
17 for(;;)
18 {
19
20 string tips = "[xxx@localhost YYY]# ";
21 cout << tips;
22
23 fgets(buff, sizeof(buff) - 1,stdin);
24 buff[strlen(buff) - 1] = 0;
25
26 //提取数据ls -a -l -o
27 char * argv[NUM];
28 argv[0] = strtok(buff," ");
29 int i = 0;
30 while(argv[i])
31 {
32 i++;
33 argv[i] = strtok(NULL," ");
34 }
35
36 pid_d id = fork();
37 if(id == 0)
38 {
39 //child
40 cout << "child running ...... " << endl;
41 execvp(argv[0],argv);
42 exit(123);
43 }
44 else
45 {
46 int status = 0;
47 waitpid(id,&status,0);
48 cout << "Exit Code " << WEXITSTATUS(status) << endl;
49 }
50
51 }
52 return 0;
53 }