写时拷贝
对于父子进程来说,代码是共享的,数据是各自私有的。(其中代码是共享的,是整个进程中的代码都是共享的(并不是fork之后才共享),但是有的时候由于if /if else/else 进行了分流,导致父子进程所执行了不同的代码块,但是其实在子进程中依旧是拥有父进程那一块的代码的,但不执行
程序=代码(逻辑)+ 数据,代码存在内存中的代码段,被设置为了只读属性,父子进程都各自私有一份代码,OS内存空间的浪费(因为不能修改,看到的都是一样的内容,没有必要私有)
数据私有(其实也是相对的,当父子进程的数据都没有发生修改的时候,数据是共有的)
修改子进程的数据,父进程相对于的数据也发生了修改,这种情况是不允许的。违反进程之间独立性。
在没有修改前数据段被设置为只读状态,父子进程谁先写入,就会报错,OS就会去处理错误,OS以写时拷贝的方式向需要修改的那个数据段写入数据,目的:节省空间、时间。
写时拷贝意义:数据是很多的,不是所有的数据都立马要使用,且不是所有的数据都需要进行拷贝。但是如果立马要独立,就需要将数据全部拷贝,把本来可以在后面拷贝的,甚至不需要拷贝的数据,都拷贝了
写时拷贝的本质:修改页表和物理地址之间的映射关系,
数据10M,有1M要发生改变,写时拷贝时,拷贝1M。
终止
终止:释放进程创建时建的那套数据结构+代码、数据
main函数退出时,return的数字:进程的退出码,而0在函数设计中,一般代表的是正确,非零则是错误的(当退出码是1,2,3,4等的时候,每一个退出码的数字则代表了一种错误的情况)
exit:任何地方调用,终止整个进程;exit(C的库函数)刷新缓冲区
eixt(参数):参数=退出码
return:终止函数(只有在main函数中代表进程终止,在函数里面代表函数返回),不会杀死整个进程。
_exit(系统调用)不把缓冲区数据刷新出来,进去之后直接杀死进程
父子进程中,通常让子进程先退出,父进程很容易对子进程进行管理(垃圾回收),子进程被创建出来的目的是去处理业务,需要父进程帮我们拿到子进程执行的结果。
进程退出:释放PCB、释放内存空间,僵尸状态:PCB被保留
int main(){
pid_t id = fork();
if (id == 0){
while (1){
sleep(1);
cout << "child.. ." << endl;
break;
}exit(0);
}
else{
while (1){
sleep(1);
cout << "father ...." << endl; break;
}
}
cout << " hello world" << endl;
}
结果:father … . .
hello world
等待wait
进程等待:父进程通过wait等系统调用,等待子进程状态的一种现象,这个等待是必须的
目的:1.防止子进程发生僵尸问题,产生内存泄漏;2.读取子进程状态。
父进程没有回收子进程的情况,子进程一直在资源浪费,子进程已经死(Z状态)就不要占着资源了:
#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
using namespace std;
int main()
{ pid_t id = fork();
if(id < 0){
cerr<< "error"<<endl;
}
else if(id == 0){
int count = 0;
while(1){
sleep(1);
cout<< "child... :"<<count<<endl;
count++;
if(count >= 3){
break;
}
}
exit(0);
}
else{
while(1){
sleep(1);
cout<<"father..."<<endl;
}
}
cout << "hello world"<<endl;
}
[s@VM-8-3-centos lesson_12]$ ./myproc
father. . .
child.. . :0
father. . .
child. . . :1
father. . .
child. . .:2
father...
father. . .
father. ..
father. ..
父进程等待子进程,并回收:
int main()
{
pid_t id = fork();
if (id == 0){
int count = 0;
while (1){
sleep(1);
cout <<"child...: " << count << endl;
if (count >= 15){
break;
}
count++;
}
exit(0);
}
else{
int count = 0;
while (1){
sleep(1);
cout << "father ...: " << count << endl;
if (count == 20){
wait(NULL);
//阻塞方式等待,0:阻塞方式:不给结果则不返回,一直监视着等待,中间没有间隔
}
count++;
}
}
释放子进程状态
while :; do ps axj | head -1 && ps axj | grep myproc|grep -v grep; sleep 1;echo"###" ; done
##########
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2298 4353 4353 2298 pts/0 4353 S+ 1005 0:00 ./ myproc
4353 4354 4353 2298 pts/0 4353 z+ 1005 0:00 [myproc]
<defunct>
##########
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2298 4353 4353 2298 pts/0 4353 S+ 1005 0:00 ./ myproc
程序替换
程序替换不换外面的壳子,只换里面的,不创建新进程。
fork创建子进程目的:1.代码共享:子进程执行父进程代码的一部分(富二代子承父业);2.程序替换:子进程执行和父进程完全不同的事情(富二代创业)
进程程序替换:
把磁盘上的数据和代码覆盖式的以写时拷贝的方式写入物理内存,相当于直接把进程全部替换掉了。卡住:hang住,挂了=宕机。
void myfun( char *arg1,char *arg2,char *arg3);
myfun(a1,a2,a3)//list形式
void myfun(char *arg[]); //no list形式
char *arg[] = {al,a2,a3};
myfun(arg);
exec
exec系列函数能调用系统程序,能调用自己的程序。
一般exel系列函数,不会自己调用,一般是fork,让子进程调用,父进程只需要wait就行,一旦命令本身有问题,就不会波及父进程。
she1l 运行原理:王婆->委派实习生->处理介绍事宜->王婆只要关心结果就行->搞砸了,不会影响王婆
一旦替换成功,接下来进程就开始执行mycmd的代码了,后面的代码,早已经被替换。
用myexe执行(调用)mycmd:
.PHONY:all
all : myexe mycmd
myexe : myexe.c
gcc - o $@ $^
mycmd:mycmd.c
gcc - o $@ $^
.PHONY:clean
clean :
rm - f myexe mycmd
myexe.c:
#include<stdio.h>
#include<unistd.h>
int main(int argc, char* argv[], char* env[])
{
printf("begin\n");//会被打印出来
execl("./mycmd", "./mycmd", NULL);
printf("running\n");//不会被打印出来
return 0;
}
mycmd.c:
#include<stdio.h>
int main()
{ int i = 0;
int sum = 0;
for (; i <= 100; i++){
sum += i;
}
printf("result[1~100] : %d\n", sum);
return 0; }
[s@VM-8-3-centos ]$ make
gcc -o myexe myexe.c
gcc -o mycmd mycmd.c
[s@VM-8-3-centos ]
Makefile mycmd mycmd.c myexe myexe.c
[s@VM-8-3-centos ]$ ./myexe
begin
result[1-100]:5050
[s@VM-8-3-centos ]$ ./mycmd
result[1-100]: 5050
制作简易shell
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define SIZE 256
#define NUM 16
int main()
{
char cmd[SIZE];
const char* cmd_line = "[temp@VM-8-3-centos lesson_15]# ";
while(1){
cmd[0] = 0; //字符串以'\0'结束,把第一个位置设置为'\0',后面的字符都是无效
printf("%s",cmd_line);
fgets(cmd,SIZE,stdin);
cmd[strlen(cmd)-1] = '\0';
char *args[NUM];
args[0] = strtok(cmd," ");
int i = 1;
do{
args[i] = strtok(NULL," ");
if(args[i] == NULL){
break;
}
++i;
}while(1);
//创建子进程
pid_t id = fork();
if(id < 0){
perror("fork error\n");
continue;
}
if(id == 0){
//child
execvp(args[0],args);
//子进程替换失败,退出
exit(1);
}
//parent
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret > 0 ){
printf("status exit code : %d\n",(status>>8)&0xff);
}
}
return 0;
}
[s@vM-8-3-centos ]$ ls
Makefile myshell.c
[s@VM-8-3-centos ]$ make
gcc -o myshell myshell.c
[s@vM-8-3-centos ]$ ls
Makefile myshell myshell.c
[s@VM-8-3-centos ]$ ./myshell
[tempavM-8-3-centos ]# ls -a -l
total 28
drwxrwxr-x2 s s 4096 Mar 2218;52
drwx---- -- 26 s s 4096 Mar 2216:55 .
--rw-rw-r--1 s s 67 Mar 22 16:58 Makefile
-rwxrwxr-x1 s s 8816 Mar 22 18:52 myshell
- rw-rw-r- -l s s 1722 Mar 22 18:51 myshell.c
status exit code : 0
[ tempaVM-8-3-centos lesson_15]#ps -a
PID TTY TIME CMD
12887 pts/9ee ;00;00 myshell
12933 pts/9ee ;00 ;00 ps
status exit code : 0
strlen(cmd)-1:fgets在获取字符串的时候,最后将回车敲下之后补了’\0’结束,字符串的最后是以n\0结束的,\n换行 改为\0字符串结束,
strtok() :命令行拆解为多个字符串,然后把每个字符串放在一个对应的字符指针数组里面,访这个数组的下标可以拿到对应的命令行内容
char *fgets(char *s,int size,FILE * stream):
获得字符串,放到缓冲区;缓冲区大小;从哪种流方式获得
continue:跳过剩下的语句,而直接回到最初重新进行程序的判断;break是直接跳出循环体,不再执行