文章目录
一、环境变量
1.什么是环境变量
首先,在百度百科中,环境变量的解释是这样的:
指令本质上就是编译好的程序和脚本,被存储在特定的路径下(默认/user/bin/)。
比如我们执行 ls 指令,实际上就是执行这个程序或者脚本,而我们要执行一个程序就必须找到该程序,我们通常要运行一个可执行程序,是用 ./a.out
来执行的,./
表示在当前目录,a.out
表示一个可执行程序。所以我们要执行 ls 指令,就要找到 ls 所在的路径,而环境变量的作用就是让系统从指定的路径去找,而在PATH 中有/uer/bin
路径,所以我们就能执行所有的指令了。
常用的10个环境变量如下:
环境变量名称 | 作用 |
---|---|
HOME | 用户的主目录(也称家目录) |
SHELL | 用户使用的 Shell 解释器名称 |
PATH | 定义命令行解释器搜索用户执行命令的路径 |
EDITOR | 用户默认的文本解释器 |
RANDOM | 生成一个随机数字 |
LANG | 系统语言、语系名称 |
HISTSIZE | 输出的历史命令记录条数 |
HISTFILESIZE | 保存的历史命令记录条数 |
PS1 | Bash解释器的提示符 |
邮件保存路径 |
Linux 作为一个多用户多任务的操作系统,能够为每个用户提供独立的、合适的工作运行环境,因此,一个相同的环境变量会因为用户身份的不同而具有不同的值。
2.环境变量的分类
按照变量的生存周期划分,Linux 变量可分为两类:
按作用的范围分,在 Linux 中的变量,可以分为环境变量和本地变量:
3.查看环境变量
使用 echo 命令查看单个环境变量,例如:echo $PATH
;使用 env 查看当前系统定义的所有环境变量;使用 set 查看所有本地定义的环境变量。查看 PATH 环境的实例如下:
常用的命令如下:
例如我们要设置一个新的环境变量,然后再清除:
`
4.设置环境变量
在 Linux 中设置环境变量有三种方法:
1.所有用户永久添加环境变量: vi /etc/profile
,在 /etc/profile
文件中添加变量。
2.当前用户永久添加环境变量: vi ~/.bashrc
,在用户目录下的 ~/.bashrc
文件中添加变量。
3.临时添加环境变量 : 可通过 export 命令,如运行命令export HELLO=100
。
5.获取环境变量
- 命令行第三个参数
#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
int i = 0;
for(; env[i]; i++){
printf("%s\n", env[i]);
}
return 0;
}
- 通过第三方变量environ 获取
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++){
printf("%s\n", environ[i]);
}
return 0;
}
lib.c中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern声明。
- 通过系统调用获取
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
二、进程控制
1.进程终止
正常退出:
异常退出:
_exit():
#include <unistd.h>
void _exit(int status);
DESCRIPTION:
_eixt()函数立即终止进程,关闭所有属于该进程的文件描述符,其子进程被1号init 进程领养,然后向父进程发送SIGCHLD 信号。
值状态作为进程的退出状态返回到父进程,并且可以使用 wait(2) 系列调用之一来收集。
exit():
#include <unistd.h>
void exit(int status);
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
2.进程等待
进程等待的必要性:
wait():用来等待任何一个子进程退出,由父进程调用。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
wait方式:
阻塞式等待,等待的子进程不退出时,父进程一直不退出;
waitpid():
pid_ t waitpid(pid_t pid, int *status, int options);
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
当options参数为0时,与wait功能相同,仍是阻塞式等待,不提供额外功能
如果为下列常量按位或则提供更多功能:
WCONTINUED:若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但状态尚未报告,则返回状态
WNOHANG:若由pid指定的子进程并不是立即结束,则waitpid不阻塞,即此时以非阻塞方式(轮询式访问的必要条件)等待子进程,并且返回0。若正常结束,则返回该⼦进程的ID。
WUNTRACED:若实现支持作业控制,而pid指定的任一子进程已经暂停,且其状态尚未报告,则返回其状态。
返回值:
当正常返回的时候,waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
获取子进程status:
我们可以通过status & 0x7f来判断异常信号是否为0;若为0,则正常退出,然后可以通过(status >> 8) & 0xff来获取子进程返回值。sys/wait.h中提供了一些宏来简化这些操作:
if (WIFEXITED(status)) {
// 正常退出:((status) & 0x7f) == 0
// 打印退出码:(status >> 8) & 0xff
printf("child return: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
// 异常退出:((signed char) (((status) & 0x7f) + 1) >> 1) > 0
// 打印异常信号值:(status) & 0x7f
printf("child signal: %d\n", WTERMSIG(status));
}
我们用这段代码来读取子进程的status,以获取它的退出码和异常信号值,以及父进程收到的SIGCHLD信号:
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
void catchsig(int sig)
{
printf("catch a sig : %d , pid->%d\n",sig,getpid());
}
int main()
{
signal(SIGCHLD,catchsig);
pid_t id = fork();
if(id<0)
{
printf("fork error!\n");
exit(-1);
}
else if(id==0)
{
int cnt = 3;
while(cnt)
{
printf("this is a child process! id-->%d pid-->%d ppid--> %d\n",cnt--,getpid(),getppid());
}
//exit(-1);
int i = 10/0;
}
sleep(2);
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(id>0)
//0-7:终止信号 15-8:退出状态
printf("wait success:%d , sign number:%d , child exit code:%d\n",ret,(status & 0x7F),(status>>8 & 0xFF));
return 0;
}
运行结果如下:
其实,⼦进程在终⽌时会给⽗进程发SIGCHLD信号,该信号的默认处理动作是忽略,⽗进程可以⾃定义SIGCHLD信号的处理函数,这样⽗进程只需专⼼处理⾃⼰的⼯作,不必关⼼⼦进程了,⼦进程终⽌时会通知⽗进程,⽗进程在信号处理函数中调⽤wait清理⼦进程即可。一般情况下父进程收到这个信号的默认处理是忽略这个信号,即就是不做任何处理。
3.进程替换
替换原理:
替换函数:
#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[]);
int execvpe(const char *file, char *const argv[], *const envp[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
命名理解:
int execl(const char *path, const char *arg, ...);
其中*path表示的是路径,arg表示的是要执行的程序,“…”表示的就是可变参数列表,即命令行上怎么执行这里就写入什么参数。必须以NULL作为参数列表的结束。
#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
if(fork()==0)
{
printf("command begin\n");
execl("/usr/bin/ls","ls","-a","-l",NULL);
printf("command fail\n");
exit(1);
}
waitpid(-1,NULL,0);
printf("wait child success\n");
return 0;
}
当子进程执行完打印command begin的语句的时候,进行进程的替换。其中替换的是/usr/bin/ls,在命令行要输入的是ls -a -l,将程序运行起来:
下面是这些函数的用法:
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}
操作系统实际上只提供了一个接口那就是:execve,其他的函数都是对该接口封装而成的库函数。它们的底层都是使用execve来进行实现的。
三、实现一个简单的shell
以下代码利用程序替换实现了shell 的基本功能,包括重定向,退出码等功能,后续还可以再补充。
#include<stdio.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#define NUM 1024//缓冲区大小
#define COM_NUM 64//存放指令字符串的最大值
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUPUT_REDIR 2
#define APPEND_REDIR 4
char lineCommand[NUM];//输入缓冲区
char *myargv[COM_NUM];//存放一个个的指令字符串
int exit_code=0;//退出码
int exit_sign=0;//退出信号值
int redirType = NONE_REDIR;//读方式
char* myFile = NULL;
//跳过空格
#define trimSpace(start) do{while(isspace(*start)) ++start;}while(0)
void commandCheck(char* commands)
{
assert(commands);
char *start = commands;
char *end = commands+strlen(commands);
while(start<end)
{
if(*start == '>')
{
*start = '\0';
start++;
if(*start == '>') // >> 表示追加重定向
{
start++;
redirType = APPEND_REDIR;
}
else // > 表示输出重定向
{
redirType = OUPUT_REDIR;
}
trimSpace(start);
myFile = start;
break;
}
else if(*start == '<') // < 表示输入重定向
{
*start = '\0';
start++;
redirType = INPUT_REDIR;
trimSpace(start);
myFile = start;
break;
}
else
start++;
}
}
//最新添加重定向功能!!!!!
int main()
{
while(1)
{
redirType = NONE_REDIR;
myFile = NULL;
printf("[wml @ my_bash path#]");
fflush(stdout);
//获取输入行内容
char *s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);
assert(s != NULL);
(void)s;
lineCommand[strlen(lineCommand)-1] = 0;
//重定向:
//"ls -a -l > "test.txt"" ----> "ls -a -l" > "test.txt"
//"ls -a -l >> "test.txt"" ----> "ls -a -l" >> "test.txt"
//"cat < "test.txt"" ----> "cat" < "test.txt"
commandCheck(lineCommand);
//切割字符串
myargv[0] = strtok(lineCommand," ");
int i = 1;
//添加颜色选项
if(myargv[0]!= NULL && strcmp(myargv[0],"ls") == 0 )
{
myargv[i++] = (char*) "--color=auto";
}
//读取每个选项
while(myargv[i++] = strtok(NULL," "));
//解决工作路径无法改变的问题
if(myargv[0]!=NULL && strcmp(myargv[0],"cd") == 0)
{
if(myargv[1]!=NULL)
chdir(myargv[1]);
continue;
}
//设置退出码
if(myargv[0]!=NULL && myargv[1]!=NULL && strcmp(myargv[0],"echo")==0 )
{
if(strcmp(myargv[1],"$?")==0)
printf("exit_code-->%d | exit_sign-->%d\n",exit_code,exit_sign);
else
printf("%s\n",myargv[1]);
continue;
}
#ifdef DEBUG
for(int i=0;myargv[i];i++)
printf("myargv[%d]:%s\n",i,myargv[i]);
#endif
pid_t id = fork();
assert(id!=-1);
if(id==0)
{
switch(redirType)
{
case NONE_REDIR:
break;
case INPUT_REDIR:
{
int fd = open(myFile,O_RDONLY);
if(fd<0) {perror("open");return 1;}
dup2(fd,0);
}
break;
case OUPUT_REDIR:
case APPEND_REDIR:
{
umask(0);
int flag = O_WRONLY | O_CREAT;
if(redirType == APPEND_REDIR) flag |= O_APPEND;
else flag |= O_TRUNC;
int fd = open(myFile,flag,0666);
if(fd<0){perror("open");return 2;};
dup2(fd,1);
}
break;
default:
printf("bug!\n");
break;
}
execvp(myargv[0],myargv);
_exit(1);
}
int status=0;
pid_t ret = waitpid(id,&status,0);
assert(ret!=-1);
(void)ret;
exit_sign = (status & 0x7f);
exit_code = (status>>8) & 0xff;
}
return 0;
}