精华内容
下载资源
问答
  • unix外壳和历史特征
    千次阅读
    2019-10-11 21:40:45

    目的:设计一个外壳接口,可以用来接受用户的命令,在一个单独的进程中执行用户命令。

    外壳接口:为用户提供提示符,便于下一个命令的输入。

    实现过程:父进程读取用户命令行的输入,然后创建一个单独的子进程来完成这个命令。

    外壳基本轮廓:以main()函数提供osh>命令符为例:

    #include <stdio.h>
    #include <unistd.h>
    int main(){
    	char *args[MAX_LINE/2 + 1];
    	int should_run = 1;
    
    	while(should_run){
    		printf("osh>"); 
    		fflush(stdout);   
    	}
    	return 0;
    }
    
    

    fork()与exec的使用和区别:

    系统调用fork():创建子进程,父进程和子进程之间共享代码段(建立相同的程序副本),仅通过复制指令、用户数据和系统数据段来创建从现存进程克隆的新进程,该新进程不是从程序初始化得来的,所以旧进程和新进程执行同样的指令。

    系统调用exec():是以新的进程去代替原来的进程,但进程的PID保持不变。也就是exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。

    一般使用方法为:先调用fork(),再使用exec()来达到提高效率的目的。

    exit():终止进程,如写如exit(1)来结束进程。

    系统调用wait():等待子进程的终止。

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <string.h>
    #include <pwd.h>
    #define MAX_LINE &
    
    int main(){
    	char *args[MAX_LINE/2 + 1];
    	int should_run = 1;
    
    	while(should_run){
    		printf("osh>");  //设置提示符为‘osh>‘
    		fflush(stdout);   
    		
    		char c;
    		int i = 0;      
    		do{			
    			char *s = (char*)malloc( 128);
    			scanf("%s",s);
    			c = getchar();
    			args[i++] = s;
    			if(strcmp(args[0],"exit") == 0){should_run = 0;}//用户在提示符后面输入exit后,should_run为0并且终止;
    			if(strcmp(args[i-1],"&") == 0){i--;} //输入& 显示waitpid:success
    
    		}while(c == ' ');
    		
    		args[i] = NULL;
    		pid_t id=fork();	//创建子进程,父进程和子进程之间共享代码段(建立相同的程序副本)
    		if (id < 0){
    			perror("fork");	//抛出异常
    		}
    		if(id == 0)  {
    			execvp(args[0],args);   //exec系列的一种,用于运行新程序
    			exit(1);				//调用exit()来终止程序
    		}
    		else{
    	    	int status = 0;
    			pid_t ret = waitpid(id,&status,0);//等待子进程结束
    			if(ret > 0 && WIFEXITED(status))
    			{}
    			else{perror("waitpid");}
    		}
    	}
    	return 0;
    }

     

    更多相关内容
  • Unix外壳和历史特征

    千次阅读 2021-05-17 11:19:55
    Unix外壳和历史特征 Requirements: Part1 创建子进程 拆分用户输入为多个标记,并将这些标记存到字符串args[]数组中。 用fork()创建一个子进程 子进程调用execvp(char*command,char *params[])函数执行用户...

    Topic:

    Unix外壳和历史特征

     

    Requirements:

    Part1 创建子进程

    1. 拆分用户输入为多个标记,并将这些标记存到字符串args[]数组中。
    2. fork()创建一个子进程
    3. 子进程调用execvp(char*command,char *params[])函数执行用户指定命令
    4. 检查数组最后一个位置是否有&,以便确定是否等待子进程

    Part2 创建历史功能

    1、允许用户访问最近输入的命令,通过这个功能用户最多可以访问10个命令,在osh>后面输入history,用户能够累出历史命令

    2、当用户输入!!时,将会执行最近的历史命令,如果没有历史命令,应当产生消息“No commands in history”

    3、当用户输入!和一个整数n之后,将执行第N个历史命令,如果没有历史命令,应当产生消息“No such command in history”

    Procedure

    1、先要实现将输入的命令分割,这里用args(char* args[])数组来存放分割后的标志,例如ls –al, 每次输入下一个命令之前数组的内容清空,存储形式如下:

      args[0]=ls

      args[1]=-al

     

    do {

                char* s = (char*)malloc(128);

                scanf("%s", s);//输入空格或者回车换行结束

                c = getchar();

                args[i] = s;

                h[num % 10].args[i++] = s;

                if (strcmp(args[0], "exit") == 0) { should_run = 0; }//用户在提示符后面输入exit后,should_run0并且终止;

                if (strcmp(args[i - 1], "&") == 0) { i--; } //输入& 显示waitpidsuccess

            } while (c == ' '); //如果是空格继续循环

            args[i] = NULL; h[num%10].args[i] = NULL;//数组的结束位置

     

    2、调用fork()函数创建子进程,并将args[]数组内的命令给execvp(args[0], args)函数

            pid_t id = fork(); //创建子进程,父进程和子进程之间共享代码段(建立相同的程序副本)

            if (id < 0) {

                perror("fork");    //抛出异常

            }

            if (id == 0) {

                execvp(args[0], args);   //exec系列的一种,用于运行新程序

                exit(1);               //调用exit()来终止程序

            }

            else {

                int status = 0;

                pid_t ret = waitpid(id, &status, 0);//等待子进程结束

                if (ret > 0 && WIFEXITED(status))

                {

                }

                else { perror("waitpid"); }

            }

    fork()与exec的使用和区别:

    系统调用fork():创建子进程,父进程和子进程之间共享代码段(建立相同的程序副本),仅通过复制指令、用户数据和系统数据段来创建从现存进程克隆的新进程,该新进程不是从程序初始化得来的,所以旧进程和新进程执行同样的指令。

    系统调用exec():是以新的进程去代替原来的进程,但进程的PID保持不变。也就是exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。

    一般使用方法为:先调用fork(),再使用exec()来达到提高效率的目的。

    exit():终止进程,如写如exit(1)来结束进程。

    系统调用wait():等待子进程的终止。

    3、创建一个结构体数组h[10],用以存放历史的十个命令

     struct History

    {  

    char* args[MAX_LINE/2+1];

    }h;

    4、每次都将输入的有效命令存入结构体数组h中(数组h是一个循环数组,存满最后一个存储单元后,又从第一个存储单元开始存放,将之前的存储内容覆盖)

    5、用户输入的命令存入数组args[]和h[]后,判断当前输入命令的第一个字符串是否是history、!!或者!+N,若满足这三个条件,执行以下命令:

            if (strcmp(args[0], "history") == 0) {

                int count2=0;

                if (!h[count].args[0]) { printf("No commands in history"); }

                while (h[count].args[count2])

                {

                    while(h[count].args[count2])

                    {

                       printf("%d %s ", count, h[count].args[count2++]);

                    }printf("\n");

                    count2=0;count++;

                }

            }

            if (strcmp(args[0],"!!") == 0) {

                if (!h[num - 1].args[0]) { printf("No such command in history"); }

                else {

                process(num-1); }

            }

            assume(args); //函数assume实现!加数字执行第N条历史记录的功能

    !加数字的历史功能
    最近的历史命令和最近输入的命令


     

    /**
     * Simple shell interface program.
     *
     * Operating System Concepts - Ninth Edition
     * Copyright John Wiley & Sons - 2013
     */
    
    #include <stdio.h>
    #include "unistd.h"
    #include<string.h>
    #include<stdlib.h>
    #include<sys/types.h> /* 提供类型pid_t的定义 */ 
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/wait.h>/*pid_t wait(int *status)*/
    #include <pwd.h>
    
    
    #define MAX_LINE		80 /* 80 chars per line, per command */
    char* args[MAX_LINE / 2 + 1];	/* command line (of 80) has max of 40 arguments */
    int should_run = 1;
    struct History
    {
        char* args[MAX_LINE/2+1];
    };
    History h[10]; int num = 1;
    int assume(char *a[]);
    void process(int i);
    void play(int i)
    {
         int count2=0;
         while(h[i].args[count2++]){
           printf("%s ", h[i].args[count2++]);
         }printf("\n")
    }
    
    int main(void)
    {
        while (should_run) {
            printf("osh>");//设置提示符为OSH
            fflush(stdout);
            int i = 0, count = 1;
            char c;
    
            do {
                char* s = (char*)malloc(128);
                scanf("%s", s);//输入空格或者回车换行结束
                c = getchar();
                args[i] = s;
                h[num % 10].args[i++] = s;
                if (strcmp(args[0], "exit") == 0) { should_run = 0; }//用户在提示符后面输入exit后,should_run为0并且终止;
                if (strcmp(args[i - 1], "&") == 0) { i--; } //输入& 显示waitpid:success
            } while (c == ' '); //如果是空格继续循环
            args[i] = NULL; h[num%10].args[i] = NULL;//数组的结束位置
    
            if (strcmp(args[0], "history") == 0) {
                int count2=0;
                if (!h[count].args[0]) { printf("No commands in history"); }
                while (h[count].args[count2])
                {
                    while(h[count].args[count2])
                    {
                       printf("%d %s ", count, h[count].args[count2++]);
                    }printf("\n");
                    count2=0;count++;
                }
            }
            if (strcmp(args[0],"!!") == 0) {
                if (!h[num - 1].args[0]) { printf("No such command in history"); }
                else { 
                play(num-1);
                //pid_t id = fork();execvp(h[num - 1].args[0], h[num - 1].args);
                process(num); }
            }
            assume(args);
            process(num);
            num++;
        }
        return 0;
    }
    void process(int i)
    {
            pid_t id = fork();	//创建子进程,父进程和子进程之间共享代码段(建立相同的程序副本)
            if (id < 0) {
                perror("fork");	//抛出异常
            }
            if (id == 0) {
                execvp(h[i].args[0],h[i]args);   //exec系列的一种,用于运行新程序
                exit(1);				//调用exit()来终止程序
            }
            else {
                int status = 0;
                pid_t ret = waitpid(id, &status, 0);//等待子进程结束
                if (ret > 0 && WIFEXITED(status))
                {
                }
                else { perror("waitpid"); }
            }  
    }
    void row(int i)
    {
           if (!h[i].args[0]) { printf("No such command in history"); }
           else{play(1);//id = fork();execvp(h[1].args[0], h[1].args); exit(1);
           process(i);
           }
    }
    
    int assume(char *args[])
    {
        pid_t id;
        
        if (strcmp(args[0],"!1")==0) {
           row(1);
        }
        if (strcmp(args[0],"!2")==0) {
           row(2);
           //if (!h[2].args[0]) { printf("No such command in history"); }
           //else{play(2);id = fork();execvp(h[2].args[0], h[2].args); exit(1);}
        }
        if (strcmp(args[0],"!3")==0) {
           row(3);
           //if (!h[3].args[0]) { printf("No such command in history"); }
           //else{play(3);id = fork();execvp(h[3].args[0], h[3].args); exit(1);}
        }
        if (strcmp(args[0],"!4")==0) {
           row(4);
           //if (!h[4].args[0]) { printf("No such command in history"); }
           //else{play(4);id = fork();execvp(h[4].args[0], h[4].args);exit(1);}
        }
        if (strcmp(args[0],"!5")==0) {
           row(5);
           //if (!h[5].args[0]) { printf("No such command in history"); }
           //else{play(5);id = fork();execvp(h[5].args[0], h[5].args); exit(1);}
        }
        if (strcmp(args[0],"!6")==0) {
           row(6);
           //if (!h[6].args[0]) { printf("No such command in history"); }
           //else{play(6);id = fork();execvp(h[6].args[0], h[6].args); exit(1);}
        }
        if (strcmp(args[0],"!7")==0) {
           row(7);
           //if (!h[7].args[0]) { printf("No such command in history"); }
           //else{play(7);id = fork();execvp(h[7].args[0], h[7].args); exit(1);}
        }
        if (strcmp(args[0],"!8")==0) {
           row(8);
           //if (!h[8].args[0]) { printf("No such command in history"); }
           //else{play(8);id = fork();execvp(h[8].args[0], h[8].args); exit(1);}
        }
        if (strcmp(args[0],"!9")==0) {
           row(9);
           //if (!h[9].args[0]) { printf("No such command in history"); }
           //else{play(9);id = fork();execvp(h[9].args[0], h[9].args); exit(1);}
        }
        if (strcmp(args[0],"!10")==0) {
           row(0);
           //if (!h[0].args[0]) { printf("No such command in history"); }
           //else{play(0);id = fork();execvp(h[0].args[0], h[0].args); exit(1);}
        }
        return 0; 
    }
    
    
    

     

    展开全文
  • 操作系统第七版 第三章进程 课后编程项目 操作系统实验二_shell的简单实现。 实现了以下功能: (1)解析用户提交的命令行;...(3)提供历史查询功能。如用户按下Ctr1+C,信号处理器将输出最近的10个命令列表。
  • 实现了unix外壳和历史特征,还有任务列表的线性迭代采用深度优先搜索树的迭代
  • Unix历史和特性

    2019-07-24 22:24:36
    学习内容 今天在宿舍内学习,了解了Unix,学习了Java入门课程,以下是我的学习记录。 ...课堂上老师重点讲解了Linux,对Unix了解还很少,于是特意去了解了一下Unix历史和特性。 Unix的历史...

    Unix Environment

    学习内容

    今天在宿舍内学习,了解了Unix,学习了Java入门课程,以下是我的学习记录。

    Unix学习

    Java学习第三天,入门在学习Unix,今天发现W3Cschool上面只有Linux的教程,没有Unix教程,感觉很迷惑。
    Unix不是号称Linux的父亲吗,为什么连教程都没有?

    Unix的历史

    诞生

    1965年时,贝尔实验室Bell Labs)加入一项由通用电气General Electric)和麻省理工学院MIT)合作的计划;该计划要建立一套多使用者、多任务、多层次(multi-user、multi-processor、multi-level)的MULTICS操作系统。直到1969年,因MULTICS计划的工作进度太慢,该计划被停了下来。当时,Ken Thompson(后被称为UNIX之父)已经有一个称为"星际旅行"的程序在GE-635的机器上跑,但是反应非常慢,正巧被他发现了一部被闲置的PDP-7Digital的主机),Ken ThompsonDernis Ritchie就将"星际旅行"的程序移植到PDP-7上。而这部PDP-7就此在整个计算机历史上留下了芳名。

    MULTICS其实是"Multiplexed Information and Computing Service"的缩写,在1970年时,那部PDP-7却只能支持两个使用者,当时,Brian Kernighan就开玩笑地称他们的系统其实是:“UNiplexed Information and Computing Service”,缩写为"UNICS",后来,大家取其谐音,就称其为"UNIX"了。1970年可称为"UNIX元年"。

    流行

    1971年,Ken Thompson写了充分长篇的申请报告,申请到了一台PDP-11/24的机器。于是Unix第一版出来了。在一台PDP-11/24的机器上完成。这台电脑只有24KB的物理内存和500K磁盘空间。Unix占用了12KB的内存,剩下的一半内存可以支持两用户进行Space Travel的游戏。而著名的fork()系统调用也就是在这时出现的。

    到了1973年的时候,Ken ThompsonDennis Ritchie感到用汇编语言做移植太过于头痛,他们想用高级语言来完成第三版,对于当时完全以汇编语言来开发程序的年代,他们的想法算是相当的疯狂。一开始他们想尝试用Fortran,可是失败了。后来他们用一个叫BCPLBasic Combined Programming Language)的语言开发,他们整合了BCPL形成B语言,后来Dennis Ritchie觉得B语言还是不能满足要求,于是就改良了B语言,这就是大名鼎鼎的C语言。于是,Ken ThompsonDennis Ritchie成功地用C语言重写了Unix的第三版内核。至此,Unix这个操作系统修改、移植相当便利,为Unix日后的普及打下了坚实的基础。而UnixC结合成为一个统一体,CUnix很快成为世界的主导。

    Unix的第一篇文章 “The UNIX Time Sharing System”由Ken ThompsonDennis Ritchie于1974年7月的 The Communications of the ACM发表。这是UNIX与外界的首次接触。结果引起了学术界的广泛兴趣并对其源码索取,所以,Unix第五版就以“仅用于教育目的”的协议,提供给各大学作为教学之用,成为当时操作系统课程中的范例教材。各大学公司开始通过Unix源码对Unix进行了各种各样的改进和扩展。于是,Unix开始广泛流行。

    UNIX特性

    1. UNIX系统是一个多用户,多任务分时操作系统
    2. UNIX的系统结构可分为三部分:操作系统内核(是UNIX系统核心管理和控制中心,在系统启动或常驻内存),系统调用(供程序开发者开发应用程序时调用系统组件,包括进程管理,文件管理,设备状态等),应用程序(包括各种开发工具,编译器,网络通讯处理程序等,所有应用程序都在Shell的管理和控制下为用户服务)。
    3. UNIX系统大部分是由C语言编写的,这使得系统易读,易修改,易移植
    4. UNIX提供了丰富的,精心挑选的系统调用,整个系统的实现十分紧凑,简洁
    5. UNIX提供了功能强大的可编程的Shell语言(外壳语言)作为用户界面具有简洁,高效的特点。
    6. UNIX系统采用树状目录结构,具有良好的安全性,保密性和可维护性
    7. UNIX系统采用进程对换(Swapping)的内存管理机制和请求调页的存储方式,实现了虚拟内存管理,大大提高了内存的使用效率
    8. UNIX系统提供多种通信机制,如:管道通信,软中断通信,消息通信,共享存储器通信,信号灯通信。

    总结

    今天大致了解了Unix操作系统,得知Unix是Linux的“父亲”,Linux类Unix,它们的内核基本相同,命令基本相同,可以按照Linux教程去学习Unix。
    Java语言的学习进度也需要加快了,今天看了Java入门网课,学到一个知识点:
    使用Java数据类型的注意事项:

    1. 整数默认是int类型,小数默认是double类型
    2. long类型超出int范围的要在数值末尾增加“L”或“l”作为标记
    3. float类型要在数值末尾增加“F”或“f”作为标记
    4. char类型可以包含中文字符,但是一定要注意字符集和字符编码。
    展开全文
  • 操作系统实验_项目三Unix_shell的和历史特点.docx
  • 操作系统概念第三章编程答案,包含僵尸进程,管道实现文件复制,Collatz猜想三个编程的源码,内有详细注释,已调试成功,可读性很高。
  • 操作系统第七版 第三章 进程 课后编程练习
  • 1.简单shell: 通过c实现基本的命令行shell操作,实现两个函数,main()setup(). setup读取用户的下一条指令(最多80个字符),然后分解为独立的标记,并执行,用户按ctrl+D后,程序终止. Main函数打印提示符COMMAND-...

    线程间通信,fork(),waitpid(),signal,捕捉信号,用c执行shell命令,共享内存,mmap

    实验要求:

    1.简单shell: 通过c实现基本的命令行shell操作,实现两个函数,main()和setup().

    setup读取用户的下一条指令(最多80个字符),然后分解为独立的标记,并执行,用户按ctrl+D后,程序终止.

        Main函数打印提示符COMMAND->,等待用户输入命令,如果用户命令以” &”结尾,则并发执行,否则,父进程需等待子进程

    2.创建历史特性: 将命令编号,允许用户访问最近10个输入的命令,当用户按下Ctrl+C时,系统列出这些命令,用户输入”r x”可以运行命令,如果x存在,则输出执行最近的以x为前缀的命令,否则,输出执行最近的命令.如果该即将执行的命令是错误命令,输出用户提示,不把这条命令加入历史缓存中.

     

    一.处理Ctrl+C,Ctrl+D信号:这里使用signal()

    signal() 转自百度百科

    表头文件#include<signal.h>
    功 能:设置某一信号的对应动作
    函数原型:void (*signal(int signum,void(* handler)(int)))(int);
    或者:typedef void (*sig_t)( int );
    sig_t signal(int signum,sig_t handler);
    参数说明
    第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
    第二个参数handler描述了与信号关联的动作,它可以取以下三种值:
    (1)一个无返回值的函数地址
    此函数必须在signal()被调用前申明,handler中为这个函数的名字。当接收到一个类型为signum的信号时,就执行handler 所指定的函数。这个函数应有如下形式的定义:
    void func(int sig);
    (2)SIG_IGN
    这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。
    (3)SIG_DFL
    这个符号表示恢复系统对信号的默认处理。
     

    二:用c执行shell命令,有3种方法:

      1.system(),继承环境变量,不安全

      2.popen()建立管道,继承环境变量,不安全

      3.exec族函数+fork()

      这里使用exec+fork

      exec族函数共有6个,功能是执行对应的文件,并且输入附加参数,如果执行成功直接结束,如果不成功会返回-1,接着执行下面的代码,错误信息保存在errno中

      本次使用的是execvp,int execvp(const char *file, char *const argv[]);

      其中file传入shell命令,argv是一个c字符串数组,该数组argv[0]是file,arg[1]....arg[n]是分割开的命令参数,arg[n+1]为空指针NULL,(n是命令参数个数),如"ls -l",则应传入file="ls",argv[3]={"ls","-l",NULL}

     

    exec函数族:转载自:http://blog.csdn.net/aile770339804/article/details/7443921

      #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[]);

      exec函数族装入并运行程序pathname,并将参数arg0(arg1,arg2,argv[],envp[])传递给子程序,出错返回-1。在exec函数族中,后缀l、v、p、e添加到exec后,所指定的函数将具有某种操作能力有后缀:


      其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。


      其实我们留心看一下这6个函数,可以发现前3个函数都是以execl开头的,后3个都是以execv开头的。

      首先来比较前两个函数execv和execl。execv开头的函数是把参数以"char *argv[]"这样的形式传递命令行参数。而execl开头的函数采用了我们更容易习惯的方式,把参数一个一个列出来,然后以一个NULL表示结束,也 可以写成(char *)0。

      其次紧跟着的2个以p结尾的函数execlp和execvp。与其他几个函数相比,除execlp 和execvp之外的4个函数都要求,它们的第1个参数path必须是一个完整的路径,如"/bin/ls";而execlp和execvp的第1个参数 file可以简单到仅仅是一个文件名,如"ls",这两个函数可以自动到环境变量PATH制定的目录里去寻找。

      最后两个函数execle和execve,都使用了char *envp[]来传递环境变量。在全部6个函数中,只有execle和execve需要传递环境变量,其它的4个函数都没有这个参数,这并不意味着它们不 传递环境变量,这4个函数将把默认的环境变量不做任何修改地传给被执行的应用程序。而execle和execve会用指定的环境变量去替代默认的那些。

      最后要强调一点,大家在平时的编程中,如果用到了exec函数族,一定记得要加错误判断语句。因为与其他系统调用比起来,exec很容易受伤,被执行文件的位置,权限等很多因素都能导致该调用的失败。最常见的错误是:

      1. 找不到文件或路径,此时errno被设置为ENOENT;

      2. 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;

      3. 没有对要执行文件的运行权限,此时errno被设置为EACCES。

     

      因为如果成功了execvp会直接退出,所以execvp应该在由fork产生的子进程中运行

     

     fork入门知识 转载自http://blog.csdn.net/jason314/article/details/5640969

         一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
        一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

         我们来看一个例子:

    1. /* 
    2.  *  fork_test.c 
    3.  *  version 1 
    4.  *  Created on: 2010-5-29 
    5.  *      Author: wangth 
    6.  */  
    7. #include <unistd.h>  
    8. #include <stdio.h>   
    9. int main ()   
    10. {   
    11.     pid_t fpid; //fpid表示fork函数返回的值  
    12.     int count=0;  
    13.     fpid=fork();   
    14.     if (fpid < 0)   
    15.         printf("error in fork!");   
    16.     else if (fpid == 0) {  
    17.         printf("i am the child process, my process id is %d/n",getpid());   
    18.         printf("我是爹的儿子/n");//对某些人来说中文看着更直白。  
    19.         count++;  
    20.     }  
    21.     else {  
    22.         printf("i am the parent process, my process id is %d/n",getpid());   
    23.         printf("我是孩子他爹/n");  
    24.         count++;  
    25.     }  
    26.     printf("统计结果是: %d/n",count);  
    27.     return 0;  
    28. }  

         运行结果是:
        i am the child process, my process id is 5574
        我是爹的儿子
        统计结果是: 1
        i am the parent process, my process id is 5573
        我是孩子他爹
        统计结果是: 1
        在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
        为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
        1)在父进程中,fork返回新创建子进程的进程ID;
        2)在子进程中,fork返回0;
        3)如果出现错误,fork返回一个负值;

        在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

        引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
        fork出错可能有两种原因:
        1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
        2)系统内存不足,这时errno的值被设置为ENOMEM。
        创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
        每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
        fork执行完毕后,出现两个进程,

        有人说两个进程的内容完全一样啊,怎么打印的结果不一样啊,那是因为判断条件的原因,上面列举的只是进程的代码和指令,还有变量啊。
        执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量 都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。
        还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。

     

    三.进程间通信

      程序需要记录执行命令是否成功,而exec的结果在fork的子进程内,所以使用mmap匿名映射同一个文件实现共享内存

    mmap()及其相关系统调用--转载自http://fengtong.iteye.com/blog/457090

    mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

    注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

    1、mmap()系统调用形式如下:

    void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
    参 数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行 的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从 被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射 到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始 地址为该值的有效地址。这里不再详细介绍mmap()的参数,读者可参考mmap()手册页获得进一步的信息。

    2、系统调用mmap()用于共享内存的两种方式:

    (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

    	fd=open(name, flag, mode);
    if(fd<0)
    	...
    	


    ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。

    (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区 域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
    对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。

    3、系统调用munmap()

    int munmap( void * addr, size_t len )
    该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

    程序代码:

     

    #include <signal.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <sys/wait.h>
    #include <queue>
    using namespace std;
    #define BUFFER_SIZE 50
    #define MAXLINE 80
    
    char buffer[BUFFER_SIZE];
    struct mes{//用于保存命令的参数,参数个数,是否并发执行,执行后是否错误
        char** args;//参数,arg[0]为命令本身
        int len;//参数个数
        bool bg;//是否并发
        int erno;//执行后是否错误
        mes():args(NULL),len(0),bg(false){}
        mes(char **_args,int _len,bool _bg):\
        args(_args),len(_len),bg(_bg){}
        void show(){//打印命令
            if(erno==-1)cout<<"ERROR:";
            for(int i=0;i<len;i++)cout<<args[i]<<" ";
            if(bg)cout<<"&"<<endl;
            else cout<<endl;
        }
        ~mes(){
            for(int i=0;i<len&&args[i];i++){
                delete[] args[i];
                args[i]=NULL;
            }
            delete[] args;
            args=NULL;
        }
    };
    queue <mes*> rec,quetmp;//rec用于存储最近10条命令,quetmp只是在遍历rec时暂时存储rec中的变量
    static int quelen=0;//rec内变量个数
    static int reclen=0;//已执行命令总条数
    static int srclen=0;//已经读取字符串长度
    bool read2(char * des,char *src,bool init){//从字符串src中读取非空字符串des,失败返回false, 从同一个src字符串读取应该一次性读完, init为true时代表重新读取新的src字符串
        if(init)srclen=0;
        bool fl=false;
        for(;src[srclen];srclen++){
            if(src[srclen]!=' '&&src[srclen]!='\t'&&src[srclen]!='\n'){
                fl=true;
                for(int i=0;src[srclen];srclen++,i++){
                    if(src[srclen]==' '||src[srclen]=='\t'||src[srclen]=='\n')break;
                    des[i]=src[srclen];
                    des[i+1]=0;
                }
                break;
            }
        }
        return fl;
    }
    
    void setup(char inputBuffer[],char*args[],bool background){//执行命令inputBuffer –args[1] –arg[2]…,background为true代表并发
        int len=0;
        for(len=0;len<MAXLINE/2+1&&args[len]!=NULL;len++){}//重新新建args数组,以保证数据形式满足execvp
        char **_args=new char*[len+1];
        for(int i=0;i<len;i++){
            _args[i]=new char[strlen(args[i])+1];
            strcpy(_args[i],args[i]);
        }
        _args[len]=NULL;
        mes* pmes=new mes(_args,len,background);
        int * p_errno=(int*)mmap(NULL,sizeof(int)*2,PROT_READ|PROT_WRITE,\
            MAP_SHARED|MAP_ANONYMOUS,-1,0);//共享内存以便传递子进程的执行结果
    
        int pid=fork();
        if(pid==0){
            int erno=execvp(inputBuffer,_args);
            *p_errno=erno;
            _exit(0);//子进程及时退出
        }
        else{
            if(!background){
                waitpid(pid,NULL,0);
            }
        }
        pmes->erno=*p_errno;
        if(quelen<10){
            rec.push(pmes);quelen++;
        }
        else {
            delete rec.front();
            rec.pop();
            rec.push(pmes);
        }
        reclen++;
    }
    
    
    void handle_SIGINT(int sig){//历史缓存工具
        int ci=reclen-quelen,ind=0;
        mes* ls[10];
        cout<<endl;
        while(!rec.empty()){
            cout<<++ci<<": ";
            rec.front()->show();
            quetmp.push(rec.front());
            ls[ind++]=rec.front();
            rec.pop();
        }
      while(!quetmp.empty()){
        rec.push(quetmp.front());quetmp.pop();
      } cout<<"Exit record input \"q\", repeat command,\ input \"r\" or \"r x\"(x is the prefix of command)"<<endl; char buff[MAXLINE],buff2[MAXLINE]; int exeNum=ind-1; while(true){ fgets(buff,MAXLINE,stdin); read2(buff2,buff,true); if(strcmp(buff2,"q")==0)break; if(strcmp(buff2,"r")!=0){ cout<<"No such command"<<endl; continue; } if(read2(buff2,buff,false)){ for(;exeNum>=0;exeNum--){ bool fl=true; for(int i=0;buff2[i]!=0;i++){ if(buff2[i]!=ls[exeNum]->args[0][i]){ fl=false; break; } } if(fl)break; } if(exeNum<0)cout<<"No such prefix"<<endl; } if(exeNum>=0){ ls[exeNum]->show(); if(ls[exeNum]->erno==-1){ cout<<"It is an error command!"<<endl; } else { setup(ls[exeNum]->args[0],ls[exeNum]->args,ls[exeNum]->bg); } } } cout<<"record has quitted"<<endl; } void handle_SIGTSTP(int sig){//Ctrl+d退出 write(STDOUT_FILENO,buffer,strlen(buffer)); exit(0); } int main(int argc,char * arg[]){ char inputBuffer[MAXLINE],buff[MAXLINE]; bool background; char * args[MAXLINE/2+1]; memset(args,0,sizeof(args)); signal(SIGINT,handle_SIGINT); signal(SIGTSTP,handle_SIGTSTP); while(true){ background=false; cout<<"COMMAND->"; fgets(inputBuffer,MAXLINE,stdin);//读取拆分命令 int len=0; while(read2(buff,inputBuffer,len==0)){ if(args[len])delete[] args[len]; args[len++]=new char[strlen(buff)]; strcpy(args[len-1],buff); } if(args[len])delete[] args[len]; if(len>0&&args[len-1][0]=='&'){ delete[] args[len-1]; args[len-1]=NULL; len--; background=true; } setup(args[0],args,background); for(int i=0;i<len;i++){ delete args[i]; args[i]=NULL; } } return 0; }

     

    转载于:https://www.cnblogs.com/xuesu/p/4382625.html

    展开全文
  • Unix与Linux的历史

    2016-03-06 16:10:36
    1965年以前,计算机的输入复杂并且缓慢,而相对而言,计算机的cpu对输入的数据处理起来却非常快,所以cpu大部分时间是处在空闲...T)的Bell实验室,美国麻省理工学院的人工智能实验室(MIT)美国通用电气公司(GE...
  • 这是一个很小的UNIX shell,用RustC语言实现。它源自Sean Dorward在1990年提出的出色IOCCC提交。该存储库包括原始的IOCCC版本,以及C89中经过现代化处理的反混淆版本,并在Rust中进行了完全重写。跑壳做: cargo ...
  • the homework of BJTU ,about the scheduling algorithm
  • 《 Nihongo》又一弹了英文/NYAGOS是用编程语言GOLua编写的命令行... end 历史记录(Ctrl-P!-标记) 别名像DOSKEY nyagos.alias["g++"]="g++.exe -std=gnu++17 $*" 由Lua函数实现的nyagos.alias["lala"]=functio
  • UNIX历史

    2015-04-28 18:12:00
    完成后,ThompsonRitchie共同在《ACM通信》上发表了首篇UNIX论文,The UNIX Time Sharing System。  三、UNIX系统的发展  而由于AT&T公司受到美国反托拉斯法的诉讼,与联邦政府签署了一份协议,这份协议不...
  • Unix/Linux历史

    2013-08-15 21:53:52
    UNIX历史开始于1969年ken Thompson,Dennis Ritchie(即著名的K&G,C语言的发明人)与一群人在一部PDP-7上进行的一些工作,后来这个系统变成了UNIX。它主要的几个版本为: V1(1971):第一版的UNIX,以PDP-11/20...
  • python-unix-shell

    2021-03-25 16:14:10
    python-unix-shell 外壳特点: 上下箭头可用于浏览命令历史记录 选项卡可用于自动完成文件名 支持重定向,并且管道在命令之间使用空格进行重定向管道示例1: 有效语法=> ls | 厕所使用有效语法=> ls | wc 示例2:...
  • UNIX培训文档.ppt

    2022-06-24 15:22:18
    UNIX培训文档.ppt
  • UNIX培教材活动.ppt

    2022-06-23 09:09:59
    UNIX培教材活动.ppt
  • Unix培训(命令).ppt

    2022-06-23 09:08:43
    Unix培训(命令).ppt
  • :tangerine: Mican Unix Shell Mican是用Rust编写的Unix Shell。 它是一个玩具,是一个简单的外壳实现。 目标是我可以轻松使用的外壳。 如果您在查看mican的代码时获得更好的代码,请提出要求,评论或用英语或日语...
  • Unix的特点

    2012-10-22 16:44:00
    Unix 的特点:( 30 多年过去了,这些东西早已变成经典)   l   Everything (including hardware) is a file 所有的事物(甚至硬件本身)都是一个的文件。 l   Configuration data ...
  • unix shellThere’s more to your shell history than the arrow keys… 除了箭头键外,您的外壳历史还有更多…… If you use a Mac or Linux terminal, chances are you’ve interacted with the shell history. ...
  • fh:文件历史记录 fh类似于 ,按文件记录对文件的更改。 但是,它更为原始。 设计目标: 不支持多用户环境(不锁定等) 在shell脚本中实现 必须使用ed(1) 应该在第7版UNIX上工作或很容易就可以工作 我一直在...
  • 一、UNIX发展过程

    2022-02-16 13:06:02
    一、Unix系统简介 1.Unix系统的背景 1961-1969:史前时代 CTSS(Compatible Time-Sharing System,兼容分时系统),以MIT为首的开发小组,小而简单的实验室原型。 Multics(Multiplexed Information.
  • 一、UNIX 与 Linux 的发展历史 Unix 操作系统是一个强大的多用户,多任务操作系统,支持多种处理器架构,按照操作系统的分类,属于分时操作系统,最早由 Ken Thompson, Dennis Titchie Douglas Mcllroy 于 1969...
  • sudosh2是sudo用户外壳之间的审计层,也可以用作登录外壳。 用户会话将被记录,并且可供系统管理员稍后回放。 sudosh2不会记录命令历史记录,除非通过ssh的command选项远程执行时。 如果您正在寻找仅用于记录...
  • unix 命令行 manLinux man command is the number one helper to get more detailed information about Linux commands. There are different alternatives like infobut man is a de-facto command for help. In ...
  • sesh是用Go编写的简单(基本的)优雅外壳。 它支持以下内容: 混叠 管道I / O重定向 上下箭头键可查看历史记录 标签自动补全 除此之外,它还有两个自定义的内置函数: walk :递归地遍历指定为参数的目录。 ...
  • nyagos, NYAGOS用于 Windows的混合UNIXLike命令行 shell Nihongo又是一个 shell英语/日语日语/日语NYAGOS是用编程语言编写的Windows 命令行外壳unix像 shell像Emacs这样的按键绑定...历史记录( ctrls -mark )别名文
  • UNIX系统基础知识

    2011-04-02 11:00:59
    标准的UNIX系统支持多用户的工作环境。它的使用与传统PC的操作系统有比较大的区别。本课程主要介绍UNIX系统的基本常识一些基本的操作。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,365
精华内容 1,746
关键字:

unix外壳和历史特征