认识环境变量和进程替换,实现一个简易的shell

news2024/9/27 5:58:01

文章目录

  • 一、环境变量
    • 1.什么是环境变量
    • 2.环境变量的分类
    • 3.查看环境变量
    • 4.设置环境变量
    • 5.获取环境变量
  • 二、进程控制
    • 1.进程终止
    • 2.进程等待
    • 3.进程替换
  • 三、实现一个简单的shell


一、环境变量

1.什么是环境变量

首先,在百度百科中,环境变量的解释是这样的:

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。例如Windows和DOS操作系统中的path环境变量,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到path中指定的路径去找。用户通过设置环境变量,来更好的运行进程。

指令本质上就是编译好的程序和脚本,被存储在特定的路径下(默认/user/bin/)。

比如我们执行 ls 指令,实际上就是执行这个程序或者脚本,而我们要执行一个程序就必须找到该程序,我们通常要运行一个可执行程序,是用 ./a.out 来执行的,./表示在当前目录,a.out表示一个可执行程序。所以我们要执行 ls 指令,就要找到 ls 所在的路径,而环境变量的作用就是让系统从指定的路径去找,而在PATH 中有/uer/bin 路径,所以我们就能执行所有的指令了。

在这里插入图片描述

常用的10个环境变量如下:

环境变量名称作用
HOME用户的主目录(也称家目录)
SHELL用户使用的 Shell 解释器名称
PATH定义命令行解释器搜索用户执行命令的路径
EDITOR用户默认的文本解释器
RANDOM生成一个随机数字
LANG系统语言、语系名称
HISTSIZE输出的历史命令记录条数
HISTFILESIZE保存的历史命令记录条数
PS1Bash解释器的提示符
MAIL邮件保存路径

Linux 作为一个多用户多任务的操作系统,能够为每个用户提供独立的、合适的工作运行环境,因此,一个相同的环境变量会因为用户身份的不同而具有不同的值。

2.环境变量的分类

按照变量的生存周期划分,Linux 变量可分为两类:

  • 永久的:需要修改配置文件,变量永久生效。
  • 临时的:使用 export 命令声明即可,变量在关闭 shell 时失效。

按作用的范围分,在 Linux 中的变量,可以分为环境变量和本地变量:

  • 环境变量:相当于全局变量,存在于所有的 Shell 中,具有继承性;
  • 本地变量:相当于局部变量只存在当前 Shell 中,本地变量包含环境变量,非环境变量不具有继承性。

3.查看环境变量

值得一提的是,Linux 系统中环境变量的名称一般都是大写的,这是一种约定俗成的规范。

使用 echo 命令查看单个环境变量,例如:echo $PATH;使用 env 查看当前系统定义的所有环境变量;使用 set 查看所有本地定义的环境变量。查看 PATH 环境的实例如下:

在这里插入图片描述

常用的命令如下:

  1. echo: 显示某个环境变量值
  2. export: 设置一个新的环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. set: 显示本地定义的shell变量和环境变量

例如我们要设置一个新的环境变量,然后再清除:

在这里插入图片描述

`

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.进程终止

正常退出:

  • 从 main 函数返回
  • 调用exit
  • 调用_exit

异常退出:

  • crtl + c,信号终止

_exit():

 #include <unistd.h>
 void _exit(int status);

DESCRIPTION:

The function _exit() terminates the calling process “immediately”. Any open file descriptors belonging to the process are closed; any children of the process are inherited byprocess 1, init, and the process’s parent is sent a SIGCHLD signal.

_eixt()函数立即终止进程,关闭所有属于该进程的文件描述符,其子进程被1号init 进程领养,然后向父进程发送SIGCHLD 信号。

The value status is returned to the parent process as the process’s exit status, and can be collected using one of the wait(2) family of calls.

值状态作为进程的退出状态返回到父进程,并且可以使用 wait(2) 系列调用之一来收集。

exit():

 #include <unistd.h>
 void exit(int status);
  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

2.进程等待

进程等待的必要性:

  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

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:

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
    否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下(只研究status低16比特位):
    在这里插入图片描述

我们可以通过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.进程替换

替换原理:

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种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[]);
       int execvpe(const char *file, char *const argv[], *const envp[]);
       
	这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
	如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。

命名理解:

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1130772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第1篇 目标检测概述 —(3)目标检测评价指标

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。目标检测评价指标是用来衡量目标检测算法性能的指标&#xff0c;主要包括几个指标&#xff1a;精确率&#xff08;Precision&#xff09;、召回率&#xff08;Recall&#xff09;、交并比&#xff08;IoU&#xff09;、平均…

ES SearchAPI----Query DSL语言

文章目录 Getting Startedmatch_all查询全部sort排序from\size分页_source指定字段 match匹配查询match_phrase短语匹配multi_match多字段匹配range范围查询bool复合查询must必须匹配&#xff0c;可贡献得分must_not必须不匹配&#xff0c;可贡献得分should可有可无&#xff0c…

GIS 数据结构BSP树

BSP树&#xff08;Binary Space Partitioning Tree&#xff0c;二值空间划分树&#xff09;的主要思想是将空间分割成更小的部分&#xff0c;从而使空间查询和其他运算更加高效。每个节点在BSP树中代表一个空间区域&#xff0c;这个区域可以进一步被子节点所分割。 一旦把凹形区…

C# 读取文件类型

获取示例 代码 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks;namespace ConsoleApp6filetype {internal class Program{static void Main(…

使用openlayers加载离线瓦片地图

一、需求背景 我们现在的项目就说使用openlayer geoServer自己发布的矢量地图&#xff0c;是和公安合作的项目&#xff0c;由于政府大都使用的是天地图&#xff0c;所以需要将geoServer的矢量地图改为天地图&#xff0c;搭配openlayers使用&#xff0c;openlayers还可以切换不…

spi的基本知识、软件spi、硬件spi(ADXL362为测试对象)

一、spi基础知识 1.1、spi基础知识一 SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是由摩托罗拉&#xff08;Motorola&#xff09;在1980前后提出的一种全双工同步串行通信接口&#xff0c;它用于MCU与各种外围设备以串行方式进行通…

纹波类型及纹波抑制措施

纹波主要有这几种类型&#xff1a;输入低频纹波、高频纹波、寄生参数引起的共模纹波噪声、功率器件开关过程中产生的超高频谐振噪声和闭环调节控制引起的纹波噪声。 1、低频纹波 低频纹波是与输出电路的滤波电容容量相关。电容的容量不可能无限制地增加&#xff0c;导致输出低…

视频号视频提取小程序,快速下载视频号视频

​视频号提取小程序可以帮助用户方便地从视频号视频平台获取到自己喜欢的视频号内容。通过这个小程序&#xff0c;你可以快速搜索并提取出视频号&#xff0c;并进行相关的操作。 据悉视频下载bot小程序目前已经更名为【提取下载小助手】 使用视频号提取小程序有以下几个步骤&…

postgresql group by之后对string字段进行拼接

数据如下 想要的效果 sql 如下 SELECT company_id, string_agg(employee, , ORDER BY employee) FROM mytable GROUP BY company_id;PostgreSQL STRING_AGG()函数是一个聚合函数&#xff0c;用于连接字符串列表并在它们之间放置分隔符。该函数不会在字符串的末尾添加分隔符。…

C语言猜数字小游戏(也包含python实现的用法)

猜数字小游戏 前言1. C语言中随机数生成1.1 rand1.2 srand1.3 time1.4 设置随机数的范围 2. 猜数字游戏实现C语言实现猜数字小游戏python方法实现猜数字小游戏 前言 本文基于VS2022、pycharm和前面的知识&#xff0c;写一个凭借分支与循环的小游戏&#xff0c;比如&#xff1a…

C# 使用using报错CS1674解决办法

Severity Code Description Project File Line Suppression State Error CS1674 ‘MutipleThreadResetEvent’: type used in a using statement must be implicitly convertible to ‘System.IDisposable’. 第一种解决方法&#xff1a;工具 -.NuGet下载EntityFramework是否能…

C++设计模式_12_Singleton 单件模式

在之前的博文C57个入门知识点_44&#xff1a;单例的实现与理解中&#xff0c;已经详细介绍了单例模式&#xff0c;并且根据其中内容&#xff0c;单例模式已经可以在日常编码中被使用&#xff0c;本文将会再做梳理。 Singleton 单件模式可以说是最简单的设计模式&#xff0c;但由…

力扣第1005题 K 次取反后最大化的数组和 c++ 贪心 双思维

题目 1005. K 次取反后最大化的数组和 简单 相关标签 贪心 数组 排序 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以…

【Javascript】通过浏览器书签构建与执行自动刷新脚本

0x00 前言 日常工作中&#xff0c;经常遇到需要时不时点一下刷新这样的事情&#xff08;怪前端想不到写一个自动刷新&#xff09; 但是…… 没有自动刷新按钮&#xff0c;在这页面手动点刷新还是太浪费时间了。 有时候懒得等了去做别的事情&#xff0c;过一小时回来刷新一下&a…

k8s之Flannel网络插件安装提示forbidden无权限

一、问题描述 在安装k8s的网络插件时&#xff0c;提示如下信息&#xff0c;各种forbidden无权限 [rootzzyk8s01 scripts]# kubectl apply -f kube-flannel.yml Error from server (Forbidden): error when retrieving current configuration of: Resource: "policy/v1b…

Vue中使用Web Serial API连接串口,实现通信交互

Vue中使用Web Serial API连接串口&#xff0c;实现通信交互 Web Serial API&#xff0c;web端通过串口与硬件通信; 该API是JS本身 navigator 对象上就独有的&#xff0c;所以与Vue和React框架开发都没有太大的关系&#xff0c; 串口是一个双向通信接口&#xff0c;允许字节发送…

ES6 模块化编程 详解

目录 一、基本介绍 二、基本原理示意图 三、传统CommonJS实现模块化编程 1.介绍 : 2.实例 : 四、ES6新特性实现模块化编程 1.介绍 : 2.实例 : 一、基本介绍 (1) ES6新特性——模块化编程&#xff0c;用于解决传统非模块化开发中出现的"命名冲突", "文件…

LDAP和Kerberos疑难问题诊断方法

不同的工具和方法总能给问题的解决带来希望。本文使用SSSD工具诊断Kerberos和LDAP登录问题诊断。后端&#xff08;通常也称为数据提供程序&#xff09;是管理和创建缓存的 SSSD 子进程。此过程与LDAP服务器通信&#xff0c;执行不同的查找查询并将结果存储在缓存中。它还针对 L…

PY32F002A系列单片机:高性价比、低功耗,满足多样化应用需求

PY32F002A系列微控制器是一款高性能、低功耗的MCU&#xff0c;它采用32位ARM Cortex-M0内核&#xff0c;最高工作频率达到24MHz&#xff0c;提供了强大的计算能力。此外&#xff0c;PY32F002A拥有最大20Kbytes的flash存储器和3Kbytes的SRAM&#xff0c;为简单的数据处理提供了充…

一文速通Nginx网关与gateway网关区分

目录 API网关介绍 gateway基本介绍 Nginx基本介绍 Nginx与API gateway网关 API网关介绍 网关的角色是作为一个 API 架构&#xff0c;用来保护、增强和控制对于 API 服务的访问。API 网关是一个处于应用程序或服务&#xff08;提供 REST API 接口服务&#xff09;之前的系…