进程创建、进程等待、进程终止、进程替换、模拟shell

一:进程创建
fork创建进程
在https://mp.csdn.net/postedit/81710318博客中简单介绍了用fork创建进程,
用fork创建进程时,子进程复制的是父进程的PCB,包括子进程复制了父进程的虚拟地址空间和页表,所以子进程的数据和父进程的数据虚拟地址相同,由于页表映射,这时子进程和父进程物理内存也相同,但是当子进程的数据发生改变时,系统会为子进程的数据重新分配物理内存空间,这时两者的物理内存不同,也就意味着子进程数据的改变不会影响父进程数据(写时拷贝)。
这里写图片描述
在fork之前,父进程单独执行,fork之后,父子进程分流执行。
这里写图片描述
fork常规用法:
子进程复制父进程,由于fork返回值不同分流后,父子进程可以同时执行不同的代码段。如:父进程等待客户端请求,生成子进程来处理请求;
一个进程要执行一个不同的程序。如子进程从fork返回后,可以调用exec函数。
fork调用失败的原因:
系统有太多的进程,而且规定了用户可以启动的进程数,但实际用户的进程数超过了限制,就会造成fork调用失败。
vfork创建进程:
这里写图片描述
vfork用于创建一个子进程,子进程和父进程共享一个虚拟地址空间,而fork父子进程有独立的虚拟地址空间;
vfork保证子进程先执行,当子进程调用exit或exec之后父进程才可能被调度执行。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 int main()
  5 {
  6         int pid=-1;
  7         int val=100;
  8         pid=vfork();
  9        printf("-----%d\n",getpid());
 10         if(pid<0)
 11                 exit(0);
 12         else if(pid==0)
 13         {
 14                val=200;
 15                 printf("this id child!! %d\n",getpid());
 16              // exit(0);
 17         }
 18         else
 19         {
 20                 printf("this is parent!!%d %d\n",getpid(),val);
 21         }
 22         while(1)
 23         {
 24                 printf("this is public!! %d\n",getpid());
 25                 sleep(1);
 26         }
 27 }

这里写图片描述
当子进程exit退出后:父进程开始执行
这里写图片描述
子进程修改了父进程的值,因为子进程在父进程的地址空间运行。
vfork :创建一个子进程
子进程没有退出或者运行其他程序之前,父进程阻塞在vfork处不返回。
二:进程终止
正常退出:
从main函数return :

  1 #include<stdio.h>
  2 
  3 int main()
  4 {
  5         int i=1;
  6         if(i>0)
  7                 return 1;
  8         else
  9                 return -1;
 10 }

这里写图片描述
可以用$?查看程序的退出码;只有在 main函数return才会退出进程,在main函数中return 跟调用exit效果一样
exit:

#include<stdlib.h>
void exit(int status);//参数 status定义了进程的终止状态,父进程通过wait来获取status
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main()
  4 {
  5     printf("hello ,world");
  6     exit(12);
  7 }

这里写图片描述
exit退出调用_exi。
_exit

#include<unistd.h>
void _exit(int status);
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5     printf("hello ,world");
  6     _exit(12);
  7 }

这里写图片描述
exit: 在进程任意位置都可以退出进程,在退出进程释放资源之前会刷新缓冲区即将缓冲区内容输出,做很多操作;
_exit : 粗暴的退出进程,什么都不干就释放资源,e_exit释放前不会刷新缓冲区,即不会打印;
注:换行相当于刷新缓冲区。
查看错误信息:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<errno.h>
  5 #include<string.h>
  6 
  7 int main()
  8 {
  9         int i=0;
 10         for(i=0;i<256;i++)
 11         {
 12                 printf("%s\n",strerror(i));//strerron 将错误编号转换为字符串的错误信息
 13         }
 14         return 0;
 15 }      

这里写图片描述
注:没有截完整。
异常退出:ctrl+c
三:进程等待
进程等待必要性:避免产生僵尸进程。
如果子进程退出,父进程没有获取子进程退出状态可能会造成僵尸进程,进而造成内存资源泄漏;进程一旦成为僵尸状态,kill -9也无法杀死这个僵尸进程,因为无法杀死一个已经死去的进程;子进程是否运行完成我们需要知道,这时,父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
进程等待的方式:
wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
//返回值:成功:返回被等待进程pid;失败返回-1
//参数:获取子进程退出状态,不关心则可以设置为空
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<errno.h>
  5 #include<string.h>
  6 int main()
  7 {
  8         int pid=-1;
  9         pid=fork();
 10         if(pid<0)
 11                 exit(0);
 12         else if(pid==0)
 13         {
 14                 exit(0);
 15                 sleep(5);
 16         }
 17         //wait (int *status)
 18         //功能:等待任意子进程的退出(阻塞式调用,
 19         //如果没有子进程退出就一直等待,不返回直至子进程退出)
 20         // status:用于获取子进程退出状态,如果不关心置空
 21         wait(NULL);
 22         while(1)
 23         {
 24                 printf("llalalalal\n");
 25                 sleep(1);
 26         }       
 27 }       

阻塞:为了完成一个功能发起一个函数调用,如果没有完成这个功能则一直挂起等待功能完成才返回;
非阻塞:为了完成一个功能发起一个函数调用,如果现在不具备完成的条件,则立即返回不等待。
将会等待5秒子进程退出,父进程才开始执行输出(阻塞式调用)。
如果没有子进程:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<errno.h>
  6 int main()
  7 {
  8         if(wait(NULL)<0)//没有子进程
  9         {
 10                 if(errno==ECHILD)
 11                 {
 12                         printf("have no child process\n");
 13                         perror("");
 14                         printf("%s\n",strerror(errno));
 15                 }
 16         }
 17         while(1){
 18                 printf("lalala\n");
 19                sleep(1);
 20                }
 21         return 0;
 22 }

这里写图片描述
waitpid

pid_t waitpid(pid_t pid,int *status,int options)
返回值:
   当正常返回的时候waitpid返回收集到的子进程的进程ID;
参数:
 pid:   pid=-1:等待任意一个子进程。与wait同效。
        pid>0:等待其进程ID与pid相同的子进程,即等待指定子进程退出。
 waitpid默认为阻塞函数,但可以设置为非阻塞函数(利用WNOHANG)
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<wait.h>
  5 int main()
  6 {
  7         int pid=-1;
  8         pid=fork();
  9         if(pid<0)
 10                 exit(-1);
 11         else if(pid==0)
 12         {
 13                 sleep(5);
 14                 exit(0);
 15         }
 16         //pid_t waitpid(pit_t pid,int *status,int options);
 17         //功能:默认等待子进程退出
 18         //pid :
 19         //   -1 等待任意子进程退出
 20         //   >0 等待进程id==pid 的子进程退出
 21         //status: 用于获取子进程的退出码
 22         // options :选项参数
 23         //      WNOHANG :如果没有子进程退出,则立即报错返回,如果有则回收资源,即非阻塞式
 24         //      waitpid(-1,&status,0);阻塞等待任意子进程退出,效果等同wait
 25         //      如果没有子进程退出则返回0,在5秒内没有子进程退出,但在5秒后有子进程退出
 26         while(waitpid(-1,NULL,WNOHANG)==0)
 27         {
 28                 printf("have no child exit!!\n");
 29                 sleep(1);
 30         }
 31         while(1)
 32         {
 33                    printf("wait snow\n");
 34                 sleep(1);
 35         }       
 36         return 0;
 37 }       

这里写图片描述
wait 和waitpid区别:
waitpid可以等待任意进程子进程退出,也可以等待指定子进程退出;waitpid可以设置为非阻塞函数(WNOHANG);
wait是一个阻塞等待式函数,必须等到有一个子进程退出后获取退出状态,释放资源才返回,
进程退出码:
进程退出状态码获取:在wait的参数中存储了子进程的退出原因及退出码,虽然退出状态用了4个字节来获取,但参数中只用了低16位两个字节用于存储这些信息。在低16位中:
正常退出:高8位存储的是子进程的退出码,只有子进程运行完毕(正常退出)退出时才会有,低8位为0;
异常退出:低7位存储子进程引起异常退出的信号值,第8位是core dump转储标志,只有子进程异常退出才会有,高8位为0。
这里写图片描述

      //方案一:用位运算查看退出码和信号值
        if((status & 0x7f)==0)  //低7位为0,说明正常退出,可以查看退出码
        {
                printf("child process exit code:%d\n",status >> 8);
        }
        else  //异常退出
        {
                printf("signal end:%d\n",status & 0x7f);
        }
        //方案二:用宏查看退出码和信号值
        if(WIFEXITED(status))//代表代码运行完毕退出
        {
                printf("child process exit code:%d\n",WEXITSTATUS(status));//用WEXITSTATUS查看退出码
        }
        else if(WIFSIGNALED(status)) //代表信号导致的异常退出
        {
                printf("signal end:%d\n",WTERMSIG(status)); //用WTERMSIG查看异常退出信号值
        }

进程程序替换
用fork创建子进程后执行的是和父进程相同程序,子进程一般要调用exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换(并不是重新创建新进程,所以调用exec该进程的id没有改变),从新程序的启动例程开始执行。

	   //替换相关函数:
       #include <unistd.h>

       extern char **environ;

       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[]);
        execl与execv区别:参数如何赋予(平铺,指针数组)
        execl与execlp区别:
        execl需要告诉操作系统这个程序文件的全路径/bin/ls
        execlp不需要告诉路径,只需要告诉文件名即可,会自动到PATH中的路径下寻找  ls
        execl与execle区别:
        execl  继承于父进程的环境变量
        execle 自己由我们用户来组织环境变量,以一个数组指针来存储 
        返回值:失败 返回-1 
               成功:程序替换,代表运行的代码段已经不是以前的代码段,而是新程序,因此原来的代码 exec以后的代码都不会运行,除非出错,
#include<stdio.h>
#include<unistd.h>

int main()
{
        char* const argv[]={"ls","-l",NULL};
        char *const envp[]={"PATH=/bin:/usr/bin","TERM=console",NULL};
             
        execl("/bin/ls","ls","-l",NULL);

        //带p的,可以使用环境变量PATH,无需写全路径
        execlp("ls","ls","-l",NULL);

        //带e的,需要用户自己组装环境变量
        execle("/bin/ls/","ls","-l",NULL,envp);

        //只需要文件名,但需要自己组装环境变量       
        execvp("ls",argv);

        //全路径,自己组装环境变量,参数用指针数组
        execve("bin/ls",argv,envp);
        
        return 0;
}

shell模拟

扫描二维码关注公众号,回复: 3410736 查看本文章
//模拟shell写一个自己的微型myshell
//功能:  myshell> ls  能够执行各种命令

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>

//1.获取终端输入
//2.解析输入,按空格解析到一个一个命令参数,即参数以空格分开
//3.创建一个子进程
//    在子进程中进行程序替换,让子进程运行命令
//4.父进程等待子进程运行完毕,收尸,获取退出状态码

int argc;
char *argv[32];

int param_parse(char *buff)//进行解析
{
        if(buff==NULL || buff[0] == '\0')
                return -1; 
                char*ptr=buff;
        char *tmp=ptr;
        argc=0;
        //当遇到空格,并且下一个位置不是空格的时候,将空格位置置'\0'
        //不过我们将使用argv[argc]来保存这个字符串的位置
        // ls -l -a
        while((*ptr)!='\0')
        {
                if(*ptr==' '&& *(ptr+1)!=' ')
                {
                        *ptr='\0'; //代表这个字符串结束 如ls 结束
                        argv[argc]=tmp; // 假如argv[0]=ls
                        tmp=ptr+1;
                        argc++;
                }
                ptr++;
        }
        argv[argc]=tmp;//因为到\0退出循环时,没有把最后一个参数放进argv
        argc++;
        argv[argc]=NULL;
}
int exec_cmd()
{
        int pid=0;
        pid=fork();
        if(pid<0)
                return -1;
        else if(pid==0)
        {
                execvp(argv[0],argv);
                perror("execvp");
                exit(EXIT_FAILURE);
        }
        //父进程在这里必须等待子进程退出,来看看子进程为什么退出,是不是出现了错误,通过获取状态码,并>且转换一下退出码所对应的错误信息进行打印
        int statu;
        wait(&statu);
        //判断子进程是代码运行完毕退出
        if(WIFEXITED(statu))
        {   
                if(strcmp(strerror(WEXITSTATUS(statu)),"Success")==0)
                        return 0;
                printf("%s\n",strerror(WEXITSTATUS(statu)));
        }   
        return 0;
}

int main()
{
        while(1)
        {
                printf("myshell> ");
                char buff[1024]={0};
                // %[^\n] 获取数据直至遇见\n
                // %*c 遇到\n停止说明这时缓冲区还有\n,用%*c清空缓冲区,不要剩下数据
                if (scanf("%[^\n]%*c",buff) == 0) { //如果直接输入\n,scanf将获取不到数据返回0,那么缓>冲区就会一直有\n没有清空,所以用getchar()获取\n;
                        getchar();
                        continue;
                }
                if (param_parse(buff) < 0) {
                        continue;
                }
                exec_cmd(buff);
        }
        return 0;
}

这里写图片描述

猜你喜欢

转载自blog.csdn.net/sophia__yu/article/details/81812641