这里我们默认的前提是:已经安装好了虚拟机和可视化操作界面,这里将以一个实例带大家了解我是如何创建shell程序的过程。
图形化界面:CentOS 7
定义 :http://c.biancheng.net/view/706.html
简单概括:
初衷:是一个命令解释器,使用命令行操控操作系统的内核。 但是直接操作内核没必要也不安全,创建一个程序(在linux成为shell)直接调用内核接口,安全方便。
(1) 我们会写许多的程序,把这些 “ 程序名字 ”(叫做shell命令)放到一个文件里面。脚本不需要编译,通过 解释器解释 执行,所以比较慢。
(2) 比如系统定义的 ls命令,我们在控制台输入这个命令就可以查看当前目录下的所有文件。(后文会讲)
再比如我们自定义一个helloworld.c的程序,其实也是一个命令(后文会讲)
有dash和bash两种,但dash没有bash功能全面,所以通常使用bash。 可以通过命令来查看系统中的脚本解释器: ls -l /bin/*sh
以 ls -l /bin/*sh 为例:
[zjh@localhost ~]$ cd demo [zjh@localhost demo]$ ls abc demo.sh a.out demo.txt getsum main.sh readme.txt a.sh module.sh log.txt test.sh main.c
先执行cd demo命令进入 demo 目录,这是我在自己的主目录下创建的文件夹,用来保存教学使用的各种代码和数据。
接着执行 ls 命令,它列出了 demo 目录下的所有文件,并且进行了格式对齐。
[zjh@localhost demo]$ ls -l 总用量 140 -rwxrwxr-x. 1 zjh zjh 8675 4月 2 15:01 a.out -rwxr-xr-x. 1 zjh zjh 116 4月 3 09:24 a.sh -rw-rw-r--. 1 zjh zjh 44 4月 2 16:41 check.sh -rw-r--r--. 1 zjh zjh 399 3月 11 17:12 demo.sh -rw-rw-r--. 1 zjh zjh 4 4月 8 17:56 demo.txt
如果加一个-l选项,则可以看到显示的内容明显增多了。-l是长格式(long list)的意思,也就是显示文件的详细信息。
可以看到,选项的作用是调整命令功能。如果没有选项,那么命令只能执行最基本的功能;而一旦有选项,则能执行更多功能,或者显示更加丰富的数据。
基本的概念大概了解了一下,对于shell一定充满了很多疑问,接下来尝试编写一个shell脚本并且运行感受一下。
填写代码:
#!/bin/bash echo "Hello World !" #这是一条语句
解释代码内容:
第 1 行的#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell;后面的/bin/bash就是指明了解释器的具体位置。
第 2 行的 echo 命令用于向标准输出文件(Standard
Output,stdout,一般就是指显示器)输出文本。在.sh文件中使用命令与在终端直接输入命令的效果是一样的。
第 2 行的#及其后面的内容是注释。Shell
脚本中所有以#开头的都是注释(当然以#!开头的除外)。写脚本的时候,多写注释是非常有必要的,以方便其他人能看懂你的脚本,也方便后期自己维护时看懂自己的脚本——实际上,即便是自己写的脚本,在经过一段时间后也很容易忘记。
Shell 脚本也是一种解释执行的程序,可以在终端直接调用(需要使用 chmod 命令给 Shell 脚本加上执行权限),如下所示:
[zjh@localhost ~]$ cd demo #切换到 test.sh 所在的目录 [zjh@localhost demo]$ chmod +x ./Test.sh #给脚本添加执行权限 [zjh@localhost demo]$ ./Test.sh #执行脚本文件 Hello World ! #运行结果
chmod +x表示给 test.sh 增加执行权限。
./程序名字 (运行该程序的格式)
你也可以直接运行 Bash 解释器,将脚本文件的名字作为参数传递给 Bash,如下所示:
[zjh@localhost ~]$ cd demo #切换到 test.sh 所在的目录 [zjh@localhost demo]$ /bin/bash Test.sh #使用Bash的绝对路径 Hello World ! #运行结果
通过这种方式运行脚本,不需要在脚本文件的第一行指定解释器信息,写了也没用。
接下来进一步介绍一下:进程相关的东西,实验涉及到的知识
Linux 中的每一个进程都有一个唯一的 ID,称为 PID,查看PID可以跟踪到目的进程。
进程的创建是通过fork()函数完成的,
1)了解什么是fork?: https://zhuanlan.zhihu.com/p/53527981.
2)对fork有了基本的了解,更系统的学习一个fork() 的使用以及必须了解的一些函数吧!https://www.cnblogs.com/dongguolei/p/8098181.html.
1)进程的执行代码是execve()加载的
2)exec系列的系统调用是把当前程序替换成要执行的程序
3)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他也是(execl,execle,execlp,execv,execvp)功能一样,参数不同。
链接: https://cloud.tencent.com/developer/article/1592678.
1、execve是Linux的系统函数
2、execve在头文件unistd.h中。
3、execve的定义形式:
int execve(const char *filename, char *const argv[], char *const envp[]);
4、参数说明: const char*filename:执行文件的完整路径。 char *const argv[]:传递给程序的完整参数列表,包括argv[0],它一般是程序的名。 char *const
envp[]:一般传递NULL,表示可变参数的结尾。
#include <stdio.h> #include <unistd.h> int main(int argc, char *args[]) { // 定义一个字符数组,保存程序的参数信息 // 数组的第一个元素是程序的名称 char *buf[] = { "/bin/ls", "-l", NULL }; // 调用execve函数执行程序 // 第一个参数是程序的完整路径,第二个参数是字符数组,第三个参数是NULL execve("/bin/ls", buf, NULL); return 0; }
(3.2的链接最后的案例有提到)
1)C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。
2)str – 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。
3)实例参考
#include <stdio.h> int main () { FILE *fp; /* 首先重命名文件 */ rename("file.txt", "newfile.txt"); /* 现在让我们尝试打开相同的文件 */ fp = fopen("file.txt", "r"); if( fp == NULL ) { perror("Error: "); return(-1); } fclose(fp); return(0); }
运行结果 Error: : No such file or directory
可以发现如果只是使用这个函数的话十分简单,当我准备下一个步骤时我看到有人这么定义,我一下看不懂了,不过我认为如果了解了这段话就可以认识到这个函数背后的逻辑,于是我产生许多思考
他是如何找到对应的错误信息文本?是如何打印的呢?打印的信息保存在哪里呢?带着这些问题我继续探索…
后面将为大家解释这段话的意思
(4.1中perror概念提到了标准错误输出设备怎么理解呢)
1)stdout, stdin, stderr的中文名字分别是标准输出,标准输入和标准错误。
2)实例参考
fprintf(stdout,"Hello "); fprintf(stderr,"World!");
运行结果 World!Hello
原因:
在默认情况下,stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出,举例来说就是printf(stdout,“xxxx”) 和 printf(stdout, “xxxx\n”),前者会憋住,直到遇到新行才会一起输出。而printf(stderr, “xxxxx”),不管有么有\n,都输出。
3)区别:
stdout – 标准输出设备 (printf("…")) 同 stdout。
stderr – 标准错误输出设备
两者默认向屏幕输出。
但如果用转向标准输出到磁盘文件,则可看出两者区别。stdout输出到磁盘文件,stderr在屏幕。
参考 http://c.biancheng.net/ref/errno.html.
1) 错误代码仅仅是一个数字,并没有额外的结构,要想获取具体的错误信息,一般有两种方案:
2)使用
默认是0,如果发生错误,就会被改变,成为一个新的int类型数字, strerror()函数中放入这个int类型数字,就会被打印出来。
总结:perro() 之所以实现在屏幕输出错误原因,是因为error找到了错误,stderr提供输出功能
(前面我们了解了wait(),这里区分一下waitpid(),算是对知识的优化。)
要存在子进程或者其他的信号时候使用
pid_t waitpid(pid_t pid,int *status,int options)
wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数 status 可以设成NULL.
终于来到了上机实验部分
操作系统系统课程实验三:编写一个简单shell程序
1、学习几个有关进程的命令:ps, pstree, top。
2、观察进程的镜像(结构)。
3、使用fork和execve等调用实现一个简单的shell程序。
实验环境:openEuler+gnome或Ubuntu。
(1)ps命令:观察系统中的进程信息。 $ ps -el
可以看到进程的有关信息,如图1所示。
图1 ps命令
(2)pstree命令:观察进程树的结构 $ pstree
展示进程树的结构,如图2所示
图2 pstree命令
(2)top命令:实时观察系统的资源使用状况。 $ top
运行结果为:
(4)观察进程的镜像。
首先在Helloworld.c程序中加入sleep(1000),或while(1){…}死循环,使程序不能马上运行结束。启动程序并转入后台执行后,通过ps命令获取Helloworld程序的PID,假定为3146。使用如下命令观察进程的镜像:
$ cat /proc/3146/maps
结果为:
(5)在openEuler或Ubuntu环境下,编写并运行通过下述程序。仔细分析execve的输入参数。
ch3-exp-fork-v1.c
#include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <errno.h> int main(int argc, char* argv[]){ pid_t pid; char* prog_argv[]={"ls", "-l", "/", NULL}; if((pid=fork())<0){ perror("Fork failed\n"); exit(errno); } if(!pid){ /*Child*/ printf("argc=%d, argv[0]=%s.\n", argc, argv[0]); execve("/bin/ls", prog_argv, NULL); exit(0); } if(pid){ /*Parent*/ waitpid(pid, NULL, 0); printf("Child is reaped.\n"); } }//end else return 0; }
经过前面的学习,大家对这个程序已经非常熟悉了,如果不熟悉说明前面的东西吸收的不够好哦,我都写这么详细了。如果不懂查看 【part 三 】
(6)在上述程序的基础上设计一个自己的shell程序。假定该程序名为myshell.c,编译后的可执行程序名为myshell,它带有一系列参数,比如myshell /bin/ls -el,其中/bin/ls是第一个参数,-el是第二个参数。在执行myshell /bin/ls -el时,myshell首先创建一个子进程,然后用该子进程去执行一个Linux固有的命令程序/bin/ls,选项为-el。对于myshell程序,验证以下几个命令是否能够正确执行:
(a)/bin/ls -l
(b)ps -el
(c)top
(d)bash myshell /bin/ls -l
(e)cd /
1、实验环境要求:openEuler(Ubuntu)+C+gcc+make。
2、程序myshell运行通过,展示源程序,Makefile文件以及运行结果。
3、实验报告内容包括:
(1)通过top命令的执行,得到系统中当前的进程数,CPU使用率、内存占用率。
(2)通过ps命令和top命令都能获取进程的优先级PRI。查阅相关资料,说明通过这样两个命令得到的同一进程的优先级为什么是不同的?
(3)在shell下启动可执行文件helloworld(死循环版本),通过pstree命令的执行结果,说明helloworld所在的进程与shell进程是什么关系?
(4)展示myshell.c和Makefile程序的源代码。解释为什么myshell不能成功的执行“cd /”等命令?