• [技术干货] Linux的Inode号和日志服务管理—转载
     一、Inode号 1.inode和block 文件是存储在硬盘上的,硬盘的最小存储单位叫做扇区sector,每个扇区存储512字节。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个块block。这种由多个扇区组成的块,是文件存取的最小单位。块的大小,最常见的是4KB,即连续八个sector组成一个block。  文件数据存储在块中,那么还必须找到一个地方存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种存储文件元信息的区域就叫做inode,中文译名为索引节点,也叫i节点。因此,一个文件必须占用一个inode,但至少占用一个block。  元信息-----inode  数据-------block  2.查看inode信息 stat:查看inode信息  [root@localhost ~]# mkdir test [root@localhost ~]# echo "this is test file" > test.txt 三个主要的时间属性:  ctime:change time是最后一次改变文件或目录(属性)的时间,例如执行chmod,chown等命令。 atime:access time是最后一次访问文件或目录的时间。 mtime:modify time是最后一次修改文件或目录(内容)的时间。  ls  -i:直接查看文件i节点号  [root@localhost ~]# ls -i 33574991 anaconda-ks.cfg      2086 test  33574994 test.txt df-i: 查看硬盘分区的inode总数和已使用情况  [root@localhost ~]# df -i Filesystem               Inodes IUsed   IFree IUse% Mounted on /dev/mapper/centos-root 8910848 26029 8884819    1% / devtmpfs                 230602   384  230218    1% /dev tmpfs                    233378     1  233377    1% /dev/shm tmpfs                    233378   487  232891    1% /run tmpfs                    233378    16  233362    1% /sys/fs/cgroup /dev/sda1                524288   328  523960    1% /boot tmpfs                    233378     1  233377    1% /run/user/0 二、日志服务管理 作用:  将系统和应用发生的事件记录至日志中,以助于排错和分析使用。  记录的内容包括:时间,地点,人物,事件  1.日志的级别 级别    消息    级别    说明 0    EMERG    紧急    会导致主机系统不可用的情况 1    ALERT    警告    必须马上采取措施解决问题 2    CRIT    严重    比较严重的情况 3    ERR    错误    运行出现错误 4    WARNING    提醒    可能会影响系统功能的事件 5    NOTICE    注意    不会影响系统但值得注意 6    INFO    信息    一般信息 7    DEBUG    调试    程序或系统调试信息等 日志级别数字越小越紧急,一般运维过程中出现4级就要进行检查注意了。  2.日志的种类 系统日志:  messages:系统大部分信息  secure:和安全相关的信息  用户登录日志:  btmp:查看用户登陆失败的信息。(lastb命令可以查看,应为btmp是一个二进制的文件)  wtmp:哪些用户正常登陆到系统中。(可以使用last命令查看)  3.日志的功能和日志文件的分类 功能:  用于记录系统、程序运行中发生的各种事件  通过阅读日志,有助于诊断和解决系统故障  分类:  内核及系统日志 由系统服务rsyslog统一进行管理,目志格式基本相似  用户日志 记录系统用户登录及退出系统的相关信息  程序日志 由各种应用程序独立管理的日志文件,记录格式不统一  4.日志的格式和分析工具  分析工具:users、who、w、lastb、last  三、rsyslog日志处理系统 1、使用Rsyslog创建日志优点 在运维过程中某些服务时自带错误日志和运行日志的,但是有一些服务安装完成后是没有日志的。下面我们以sshd服务为例创建一个日志。  例如sshd本身是有日志的,但是与其他安全服务一样都存放在/var/log/secure/messages文件中不方便我们查看sshd的日志,如果我们将sshd服务的日志单独生成一个日志文件便于我们查看,注意使用Rsyslog创建日志的服务必须支持Rsyslog。  2、Rsyslog配置文件解析 配置文件路径:/etc/rsyslog.conf  分为三大块:  MODULES:相关模块配置  GLOBAL DIRECTIVES:全局配置  RULES:日志记录相关的规则配置  3.通过rsyslog将ssh服务的日志单独设置 第一步在rsyslog的配置文件(/etc/rsyslog.conf)# RULES #模块中设置ssh服务的local等级为local6并设置日志存路径为/mnt/ssh.logs。其中local6后的.*表示所等级的日志都输出,  .表示等于或高于后面等级日志的都会输出,如果是.=则表示只有等于后面的日志的等级才会输出,如果是.!则表示除了后面等级的其他等级日志全部记录  第二步在ssh服务的服务器端配置文件中(/etc/ssh/sshd_conf)修改自定义rsyslog  重启sshd服务和rsyslog服务  本机ssh自己并查看实时日志是否生成记录  将 ssh服务的日志 文件 独立成出来 原来ssh               /var/log/secure         [root@localhost ~]#tail -f /var/log/secure #查看ssh服务的日志位置     [root@localhost ~]#vim /etc/ssh/sshd_config #修改ssh配置文件,32下一行添加自己的自定义 32 #SyslogFacility AUTHPRIV 33 SyslogFacility LOCAL6   local 0-7     [root@localhost ~]#vim /etc/rsyslog.conf #76 行添加自己的文件位置  local6.*                                                /var/log/ssh.log       [root@localhost log]#systemctl restart rsyslog.service sshd #重启服务  4、使用rsyslog搭建日志服务器 第一步将2台服务器的防火墙以及selinux防护关闭  systemctl stop firewalld setenforce 0 第二步在业务服务器A和日志服务器B上配置rsyslog服务开启tcp协议,在业务服务器A上将内核和公共日志实时传输到日志服务器B上   第三步业务服务器A和日志服务器B上重启rsyslog服务并检查514端口是否开启 ———————————————— 版权声明:本文为CSDN博主「Miraitowa_xu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Miraitowa_xu/article/details/135458684 
  • [技术干货] 【Linux】进程查看|fork函数|进程状态-转载
     一、基本概念 1.1 概念提出 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体(百度百科)。  1.2 特征 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。 并发性:任何进程都可以同其他进程一起并发执行 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位; 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进 结构特征:进程由程序、数据和进程控制块三部分组成。 多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。  二、描述进程-PCB 引入一个新概念: 进程控制块PCB(process control block)。PCB就是进程属性的集合(数据结构),里面存储的是进程信息。  2.1 什么是进程控制块PCB 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,称之为PCB(process control block),Linux操作系统下的PCB是: task_struct。  即task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。  2.2 task_struct内容分类(成员) 标示符(PID): 描述本进程的唯一标示符,用来区别其他进程(每次启动都会变化)。 状态: 任务状态,退出代码,退出信号等。 优先级: 相对于其他进程的优先级。 程序计数器(pc): 程序中即将被执行的下一条指令的地址。 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。 上下文数据: 进程执行时处理器的寄存器中的数据。 I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。 记账信息: 可能包括处理器时间总和,使用的时钟总和,时间限制,记账号等。 其他信息 2.3 进程控制块如何对进程进行管理 磁盘中的可执行程序在将要运行时,所有的进程中的数据(并不是程序本身)加载到内存中,此时操作系统会建立起一个PCB来保存每一个程序的信息; 这个时候PCB就会对每一个进程都建立起相应的结构体(即进程控制块)将对应的进程的属性、代码等匹配的传到这个结构体中:(这就是先描述) 此时,操作系统就会将每一个进程控制块都连接起来,形成链表结构,并返回头结点。这样便通过数据结构的形式将进程的信息组织起来。 三、查看进程 3.1 通过系统目录查看 根目录下的proc目录,/proc下存储着进程信息。  目录名为数字的即为进程信息的目录,每个目录内存储着他们对应的进程信息。而这些数字对应着该进程的标识符PID。如下查看标识符PID=18964的进程信息:   我们进入进程 18964进程目录   3.2 通过用户级工具ps查看 实例:ps ajx/ps aux  该命令可以查看所有系统进程。如下查看PID=18964的进程信息   创建如下测试脚本并执行  #!/bin/bash   proc_test() {     while(true)     do         echo "I am a process!"         sleep 1     done }   proc_test   ## 执行 sh proc_test_18964.sh 另外通过指令对进程进行检测,检测它是否运行:  ps ajx | head -1 && ps ajx | grep  proc_test  | grep -v grep 这里grep实际也是进程,且该进程内包含有proc_test的信息,所以也显示出来了。-v选项是反向搜索的意思,即过滤掉包含有grep内容的信息。   PPID (Parent Process ID):代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号 PID (Process ID): 代表这个进程的代号 PGID(Process Group):PGID就是进程所属的Group的Leader的PID,如果PGID=PID,那么该进程是Group Leader SID(Session ID):和PGID非常相似,SID就是进程所属的Session Leader的PID,如果SID==PID,那么该进程是session leader TPGID:终端进程组ID STAT:进程状态 四、通过系统调用获取进程标识符(PID) 4.1 使用getpid和getppid #include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() {     pid_t id = getpid();     while (1)     {         printf("I am a process!pid:%d\n", id);         sleep(1);     }     return 0; } 执行上面可执行程序并运行如下命令进行监控  while :; do   ps ajx | head -1 && ps ajx | grep  proc_test  | grep -v grep ; sleep 1;done  函数getppid(获取父进程的进程标识符),一般在Linux中普通进程都有他的父进程。  每一个子进程都是由父进程创建出来的。  子进程只能有一个父进程,父进程可以有多个子进程。每次执行可执行程序,进程标识符会改变,因为每次都会创建新的进程。  编写如下测试代码  #include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() {     pid_t id = getpid();     pid_t fid = getppid();     while (1)     {         printf("I am a process!pid:%d ppid:%d\n", id, fid);         sleep(1);     }     return 0; }  执行结果如下图:    多次执行我们发现程序多次执行子进程每次都会创建新的新的进程标识符,父进程标识符没有改变。如下父进程是bash命令行解释器。   五、通过系统调用创建进程-fork初识 5.1 fork函数 pid_t  fork(void);  一个进程包括代码、数据和分配给进程的资源。fork 函数会新生成一个进程,调用 fork 函数的进程为父进程,新生成的进程为子进程。在父进程中返回子进程的 pid,在子进程中返回 0,失败返回-1。  5.2 fork函数创建子进程 创建测试代码:   #include<stdio.h> #include<sys/types.h> #include<unistd.h>   int main() {     pid_t pid;     printf("before fork : pid is : %d, ppid is : %d\n", getpid(), getppid());       pid=fork();       printf("after fork : pid is : %d, ppid is : %d, fork return %d\n", getpid(), getppid(),pid);     sleep(2);     return 0; } 编译后执行  fork函数执行后,后面的"after fork"执行了两次,8897作为父进程创建了子进程 8898。也就是说fork之后,代码共享,从一个进程分为两个分支,一为父,一为子。  进程调用fork,当控制转移到内核中的fork代码后,内核会:  分配新的内存块和内核数据结构给子进程; 将父进程部分数据结构内容拷贝至子进程; 添加子进程到系统进程列表当中; fork返回,开始调度器调度; 所以,fork之前父进程独立执行,fork之后父子两个执行流分别执行。注意:fork之后谁先执行完全由调度器决定。  fork它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:  (1) 在父进程中,fork返回新创建子进程的进程ID;  (2)在子进程中,fork返回0;  (3)如果出现错误,fork返回一个负值;  一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。  六、Linux进程状态 进程状态:  TASK_RUNNING:就绪/可运行状态  TASK_INTERRUPTABLE:进程被挂起(睡眠),直到等待条件为真被唤醒  TASK_UNINTERRUPTABLE:深度睡眠,睡眠期间不响应信号  TASK_STOPPED:进程的执行被暂停  TASK_TRACED:被其它进程跟踪,常用于调试  EXIT_ZOMBIE:僵死状态,进程的执行被终止  EXIT_DEAD:僵死撤销状态,防止wait类系统调用的竞争状态发送  可以使用man ps 查看进程状态 Ss、Sl、S+、Z、I 释义   具体解释如下:  D (TASK_UNINTERRUPTIBLE) 不可中断的睡眠状态 R (TASK_RUNNING) 正在运行,或在队列中的进程 S (TASK_INTERRUPTIBLE)     可中断的睡眠状态 T (TASK_STOPPED)    停止状态 t (TASK_TRACED) 被跟踪状态 Z (TASK_DEAD - EXIT_ZOMBIE) 退出状态,但没被父进程收尸,成为僵尸状态 W            进入内存交换(从内核2.6开始无效) X (TASK_DEAD - EXIT_DEAD)   退出状态,进程即将被销毁 <    高优先级 N    低优先级 L    有些页被锁进内存 s    包含子进程 位于前台的进程组; l   多线程,克隆线程  multi-threaded (using CLONE_THREAD, like NPTL pthreads do) 七、两种特殊进程 7.1 僵尸进程 1.僵死状态(Zombies)是一个比较特殊的状态。当子进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程  2.僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。  3.所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态  测试代码 :    #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() {     pid_t id = fork();     if (id < 0) {         perror("fork");         return 1;     }     else if (id > 0) { //parent         while(1)         {             printf("parent[%d] is sleeping...,ppid: %d\n", getpid(),getppid());             sleep(1);         }     }     else {         printf("child[%d] is begin Z...,ppid: %d\n", getpid(),getppid());         sleep(5);         exit(1);     }     return 0; }  fork函数执行后子进程PID:15156 在5 s后exit(1)异常退出 ,父进程15155没有读取到正常的退出代码导致产生僵尸进程。  进程PID:15156的名称加上了【】且后面跟着< defunct > (失效的)  7.2 孤儿进程 修改代码,让子进程和父进程同时不间断运行  #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() {     pid_t id = fork();     if (id < 0) {         perror("fork");         return 1;     }     else if (id > 0) { //parent              while(1)              {         printf("parent[%d] is sleeping...,ppid: %d\n", getpid(),getppid());         sleep(1);              }     }     else {              while(1)              {         printf("child[%d] is begin Z...,ppid: %d\n", getpid(),getppid());         sleep(1);              }     }     return 0; }  运行  while :; do ps ajx | head -1 && ps ajx | grep pro_test | grep -v grep ; sleep 1;done  监控进程状态   杀掉父进程(10552),子进程被编号为1的进程接管,该1号进程就是bash,而bash就是父进程的父进程,父进程被杀死后,bash进程就接管了子进程,这种失去“爸爸”后被接管的进程就被称为孤儿进程。且子进程从前台进程变成了后台进程。 ———————————————— 版权声明:本文为CSDN博主「阿龙先生啊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_35995514/article/details/135243157 
  • [技术干货] Linux自定义shell编写-转载
     经过了创建进程,终止进程,进程等待和进程程序替换之后, 我们就可以借助这些知识实现一个简单的shell命令行解释器了 温馨提示: 建议大家自己写一遍,这些代码分块之后每一个函数都很简单, 不过实现过程中可能会有各种各样非常细枝末节的地方被我们所忽视 因此可能会发生一看就懂,一写就废的情况…  一.最终版本展示 输入命令行时想要删除字符时不能直接按backspace,而是要按ctrl+backspace才能成功删除  1.动图展示  2.代码展示 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> //#define DEBUG 1 #define SEP " "  char cwd[1024]={'\0'}; int lastcode=0;//上一次进程退出时的退出码  char env[1024][1024]={'\0'}; int my_index=0;  const char* getUsername() {     const char* username=getenv("USER");     if(username==NULL) return "none";     return username; }  const char* getHostname() {      const char* hostname=getenv("HOSTNAME");     if(hostname==NULL) return "none";     return hostname; }  const char* getPwd() {      const char* pwd=getenv("PWD");     if(pwd==NULL) return "none";     return pwd; }  //分割字符串填入usercommand数组当中 //例如: "ls -a -l" 分割为"ls" "-a" "-l" void CommandSplit(char* usercommand[],char* command) {     int i=0;     usercommand[i++]=strtok(command,SEP);     while(usercommand[i++]=strtok(NULL,SEP)); }  //解析命令行 void GetCommand(char* command,char* usercommand[]) {     command[strlen(command)-1]='\0';//清理掉最后的'\0'     CommandSplit(usercommand,command); #ifdef DEBUG     int i=0;     while(usercommand[i]!=NULL)     {         printf("%d : %s\n",i,usercommand[i]);         i++;     } #endif }  //创建子进程,完成任务 void Execute(char* usercommand[]) {     pid_t id=fork();     if(id==0)     {         //子进程执行部分         execvp(usercommand[0],usercommand);         //如果子进程程序替换失败,已退出码为1的状态返回         exit(1);     }     else     {         //父进程执行部分         int status=0;         //阻塞等待         pid_t rid=waitpid(id,&status,0);         if(rid>0)         {             lastcode=WEXITSTATUS(status);         }     } }  void cd(char* usercommand[]) {     chdir(usercommand[1]);     char tmp[1024]={'\0'};     getcwd(tmp,sizeof(tmp));     sprintf(cwd,"PWD=%s",tmp);     putenv(cwd);     lastcode=0; }     int echo(char* usercommand[]) {     //1.echo后面什么都没有,相当于'\n'     if(usercommand[1]==NULL)     {         printf("\n");         lastcode=0;         return 1;     }     //2.echo $?  echo $PWD echo $     char* cmd=usercommand[1];     int len=strlen(cmd);     if(cmd[0]=='$' && len>1)     {         //echo $?         if(cmd[1]=='?')         {             printf("%d\n",lastcode);             lastcode=0;         }         //echo $PWD         else         {             char* tmp=cmd+1;             const char* env=getenv(tmp);             //找不到该环境变量,打印'\n',退出码依旧为0             if(env==NULL)             {                 printf("\n");             }             else             {                 printf("%s\n",env);             }             lastcode=0;         }     }     else     {         printf("%s\n",cmd);     }     return 1; }  void export(char* usercommand[]) {     //export     if(usercommand[1]==NULL)     {         lastcode=0;         return;     }     strcpy(env[my_index],usercommand[1]);     putenv(env[my_index]);     my_index++; }  int doBuildIn(char* usercommand[]) {     //cd     if(strcmp(usercommand[0],"cd")==0)     {         if(usercommand[1]==NULL) return -1;         cd(usercommand);         return 1;     }     //echo     else if(strcmp(usercommand[0],"echo")==0)     {         return echo(usercommand);     }     //export     else if(strcmp(usercommand[0],"export")==0)     {         export(usercommand);     }     return 0; }  int main() {     while(1)     {         //1.打印提示符信息并获取用户的指令         printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());         char command[1024]={'\0'};         fgets(command,sizeof(command),stdin);         char* usercommand[1024]={NULL};         //2.解析command字符串,放入usercommand指针数组当中         GetCommand(command,usercommand);         //3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0         int flag=doBuildIn(usercommand);         //返回值!=0说明是内建命令,无需执行第4步         if(flag!=0) continue;         //4.创建子进程,交由子进程完成任务         Execute(usercommand);     }     return 0; } 二.具体步骤 1.打印提示符  const char* getUsername() {     const char* username=getenv("USER");     if(username==NULL) return "none";     return username; }  const char* getHostname() {     const char* hostname=getenv("HOSTNAME");     if(hostname==NULL) return "none";     return hostname; }  const char* getPwd() {     const char* pwd=getenv("PWD");     if(pwd==NULL) return "none";     return pwd; }  int main() {     while(1)     {         //1.打印提示符信息并获取用户的指令         printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());         char command[1024]={'\0'};         fgets(command,sizeof(command),stdin);     }     return 0; } 注意: 1.因为scanf默认读取到空格或者’\n’时就会停止继续读取, 可是我们命令行中要求读取到空格也不能停止,(否则我们的指令就无法带选项了,因为选项之间是用空格来分割的) 因此我们需要fgets函数  char command[1024]={'\0'}; fgets(command,sizeof(command),stdin); 1 2 从命令行当中读取一行字符串  2.因为我们用户输入的时候在最后的时候一定会输入一个’\n’,因此我们需要把’\n’置为’\0’  command[strlen(command)-1]='\0';//去除我们最后输入的'\n' 1 把’\n’置为’\0’的操作我们放到了下一个函数当中来完成  2.解析命令行 因为有些命令带有选项:例如 “ls -a -l” 我们在进行程序替换的时候需要分别传入"ls" “-a” "-l"这几个字符串,所以需要把用户输入的字符串分割为若干个字符串存放到一个指针数组当中,可以使用strtok字符串切割函数  #define DEBUG 1 #define SEP " "  //分割字符串填入usercommand数组当中 //例如: "ls -a -l" 分割为"ls" "-a" "-l" void CommandSplit(char* usercommand[],char* command) {     int i=0;     usercommand[i++]=strtok(command,SEP);     while(usercommand[i++]=strtok(NULL,SEP)); }  //解析命令行 void GetCommand(char* command,char* usercommand[]) {     command[strlen(command)-1]='\0';//清理掉最后的'\0'     CommandSplit(usercommand,command); #ifdef DEBUG     int i=0;     while(usercommand[i]!=NULL)     {         printf("%d : %s\n",i,usercommand[i]);         i++;     } #endif } 我们可以使用条件编译来方便我们自由选择是否需要打印 解析命令行后的usercommand数组中的内容  3.分析是否是内建命令 1.shell对于内建名令的处理  下面我们就一起来实现一下 cd,echo,export这几个内建命令  2.cd命令  可是如果shell进行进程程序替换了,那么shell执行完之后不就没了吗? 因此shell执行内建命令时直接封装函数调用系统调用接口即可  内建命令的个数是有限的,所以shell是可以对内建命令进行穷举的  因此我们就能更好地理解内建命令了:  内建命令:不需要创建子进程执行,shell自己执行,本质就是调用系统调用接口  3.cd函数的实现 对于cd而言,我们可以调用chdir这个系统调用接口来改变当前进程的工作目录  同时也可以设置一个全局的char类型的数组cwd来保存当前路径 每次cd之后用cwd来记录新路径,然后通过putenv来修改环境变量PWD 让我们第一步打印的提示符中的PWD路径可以动态调整  //cwd:存放当前路径,是一个全局变量 char cwd[1024]={'\0'};  void cd(char* usercommand[]) {     //1.chdir改变当前进程的工作目录     chdir(usercommand[1]);     //2.获取当前进程所在工作目录到tmp数组当中     char tmp[1024]={'\0'};     getcwd(tmp,sizeof(tmp));     //3.把tmp数组中的内容格式化为"PWD=tmp数组中的内容"放到cwd数组当中     sprintf(cwd,"PWD=%s",tmp);     //4.导入环境变量     putenv(cwd);     //5.最后一次进程的退出码置为0(是为了echo $?获取最后一次进程的退出码的实现,跟cd无关)     lastcode=0; }   注意:cwd数组必须是全局变量,如果cwd是局部变量,那么出了cd这个函数之后cwd数组就会销毁,其中的内容也将会消失 而我们putenv导入环境变量时其实是把cwd的地址放入了环境变量当中, 而不是拷贝一份这个cwd放入环境变量当中,因此cwd数组销毁时,对应导入的环境变量中的内容就不是原来的内容了  4.echo命令的实现 echo命令也是一个内建命令  echo 空串      打印换行 echo $ ?      打印上次进程退出时的退出码 echo $环境变量  打印环境变量 echo 字符串    打印字符串 注意: echo $       打印$这个字符串 因此即使判断了$,还要继续判断$后面还有没有字符 //全局变量 int lastcode=0;//上一次进程退出时的退出码  int echo(char* usercommand[]) {       //1.echo后面什么都没有,相当于'\n'       if(usercommand[1]==NULL)       {           printf("\n");           lastcode=0;           return 1;       }       //2.echo $?  echo $PWD echo $       char* cmd=usercommand[1];       int len=strlen(cmd);       if(cmd[0]=='$' && len>1)       {           //echo $?           if(cmd[1]=='?')           {               printf("%d\n",lastcode);               lastcode=0;           }           //echo $PWD           else           {               char* tmp=cmd+1;               const char* env=getenv(tmp);               //找不到该环境变量,打印'\n',退出码依旧为0               if(env==NULL)               {                   printf("\n");               }               else               {                   printf("%s\n",env);               }               lastcode=0;           }       }       else       {           printf("%s\n",cmd);       }       return 1; } 5.export命令的实现 export导入环境变量 注意: 1.刚才介绍cd函数的时候.我们说明了环境变量导入时其实是导入的对应字符串的地址 因此我们环境变量字符串必须要保证全局有效  2.由于我们可以导入很多环境变量,因此env需要是一个二维数组,同时还需要一个index下标来标记该数组当中已经导入过的环境变量  char env[1024][1024]={'\0'}; int my_index=0;  void export(char* usercommand[]) {     //1.export后面什么都没跟,什么都不执行,直接返回即可     if(usercommand[1]==NULL)     {         lastcode=0;         return;     }     //2.要导入的环境变量拷贝到env数组当中     strcpy(env[my_index],usercommand[1]);     //3.将env数组当中的环境变量导入该进程当中     putenv(env[my_index]);     my_index++; } 6.内建命令函数的实现 写好了cd,echo,export这几个函数之后,我们只需要在内建命令函数当中调用这几个函数即可  //返回值=0,说明不是内建命令 //返回值=1,说明是内建命令并且执行成功 int doBuildIn(char* usercommand[]) {     //cd     if(strcmp(usercommand[0],"cd")==0)     {         if(usercommand[1]==NULL) return -1;         cd(usercommand);         return 1;     }     //echo     else if(strcmp(usercommand[0],"echo")==0)     {         return echo(usercommand);     }     //export     else if(strcmp(usercommand[0],"export")==0)     {         export(usercommand);     }     return 0; }  int main() {     while(1)     {         //1.打印提示符信息并获取用户的指令         printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());         char command[1024]={'\0'};         fgets(command,sizeof(command),stdin);         char* usercommand[1024]={NULL};                  //2.解析command字符串,放入usercommand指针数组当中         GetCommand(command,usercommand);                  //3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0         int flag=doBuildIn(usercommand);         //返回值!=0说明是内建命令,无需执行第4步         if(flag!=0) continue;                  //4.创建子进程,交由子进程完成任务         Execute(usercommand);     }     return 0; } 4.创建子进程通过程序替换执行命令 使用execvp这个函数来进行程序替换 带上v:因为我们的命令都是放在数组当中的 带上p:因为我们输入的都是系统指令,带上p才可以自动在环境变量中查找我们的命令 否则就要显式传入路径  注意lastcode的设置  //创建子进程,完成任务 void Execute(char* usercommand[]) {     pid_t id=fork();     if(id==0)     {         //子进程执行部分         execvp(usercommand[0],usercommand);         //如果子进程程序替换失败,已退出码为1的状态返回         exit(1);     }     else     {         //父进程执行部分         int status=0;         //阻塞等待         pid_t rid=waitpid(id,&status,0);         if(rid>0)         {             lastcode=WEXITSTATUS(status);         }     } } 5.循环往复即可 main函数加上while(1)死循环即可  int main() {     while(1)     {         //1.打印提示符信息并获取用户的指令         printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());         char command[1024]={'\0'};         fgets(command,sizeof(command),stdin);         char* usercommand[1024]={NULL};         //2.解析command字符串,放入usercommand指针数组当中         GetCommand(command,usercommand);         //3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0         int flag=doBuildIn(usercommand);         //返回值!=0说明是内建命令,无需执行第4步         if(flag!=0) continue;         //4.创建子进程,交由子进程完成任务         Execute(usercommand);     }     return 0; } 三.shell运行原理 shell内部提取用户输入的命令行进行解析 判断是否是内建命令, 1.如果是内建命令的话,shell自己通过调用自己封装的函数来执行该命令 2.如果不是内建命令,shell创建子进程,通过程序替换来然子进程执行该命令 shell进程阻塞等待回收子进程的退出状态 然后循环往复  以上就是Linux自定义shell编写的全部内容,希望能对大家有所帮助! ———————————————— 版权声明:本文为CSDN博主「program-learner」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Wzs040810/article/details/135181981 
  • [技术干货] Ubuntu22.04 vnc远程黑屏 -转载
     一、原因 原因是Ubuntu22.04使用的gnome启用了Wayland。vnc、teamviewer、向日葵、todesk等均无法使用或者远程黑屏等。  简单的说vnc、teamviewer、向日葵、todesk等均基于xorg实现(xorg太流行),并不兼容Wayland,所以vnc无法正常使用。  realvnc官方也明确说明,目前在Linux上无法支持 Wayland。   GNOME是一套桌面环境,包括一系列应用程序。比如显示管理器,窗口管理器,firefox浏览器等都是XClient。  Xorg:广义的说已经成为一种统称它是Linux上通用的桌面环境(后端)服务器(X11的一种具体开源实现)。  X Window System(又称为X11):以前好像说是一种Linux桌面后端的协议或者规范?应该也提供了一个基本抽象库。如今广泛使用的实现的似乎只有Xorg,所以现在大多时候,X11、Xorg、X Server说的都是一个东西就是Linux桌面的X11后端服务器,也就是Xorg。  Wayland是新一代的Linux桌面后端服务器的协议(或者说规范)而且其也实现了一个此协议的基本库,但是这个不属于具体实现,只是提供一个协议的基础抽象。它有一个参考实现叫Weston。Gnome和KDE(也是一个桌面环境前端)似乎也都有对应的Wayland实现。  因为X11已经很老了,而且有很多问题不好在原有基础上动刀了等等一系列原因,才有的Wayland。  二、现象复现 2.1 按照往常惯例在设置,共享,启用远程桌面,勾选vnc协议,如下图:   2.2 vnc viewer直接输入ip,Ubuntu弹出提示,点允许,连接成功了,但如下图2完全黑屏,仅有鼠标指针,完全无法使用;      三、解决方案  两种思路,一种登录界面切换到Xorg,另一种使用Windows自带的远程桌面mstc  3.1、一般的主流发行版本在登陆时都可以切换到Xorg,此时常用远程软件就可以使用,如下图:   此时还需要在vnc viewer里改一下下图参数,不然还是黑屏‘    3.2、如下图Wayland居然支持了Windows自带的远程桌面mstc;  ———————————————— 版权声明:本文为CSDN博主「闫有尽意无琼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/yanchenyu365/article/details/133038162 
  • 【Linux】磁盘分区管理及挂载/永久挂载管理-转载
     前言   今天新到了一台服务器,需要安装服务;因为在安装服务的时候,发现/根目录下没有空间了,通过fdisk -l查看了一下磁盘空间,发现还有多余的100G没有使用,所以,这时候就需要去扩容磁盘分区;   当然,在扩容的时候也是有受阻的,比如遇到了磁盘损坏问题“报错fdisk: cannot write disk label: Invalid argument”,这个需要去修复一下:使用parted -l此命令去修复一下,即可重新进行扩容分区;因为在安装的时候也是遇到了一些问题的,因为好几年都没对磁盘空间进行分区了,所以有些忘了,在这里记录一下;    因为服务器已经安装好服务,我们就用虚拟机来进行磁盘分区和挂载并设置永久挂载。  首先,我们需要准备一个虚拟机,并在添加一块硬盘;添加的时候使用推荐的即可,然后创建一个新的虚拟磁盘,这个就不会影响其他的了;磁盘大小我这里就设置成50G即可;过多的细节就不一一说了,一直下一步即可。 设置好之后,启动虚拟机,进行磁盘管理,和磁盘扩容。  一、查看磁盘空间 可以使用fdsik -l查看磁盘空间,这样查看的比较详细;  fdisk -l 也可以使用lsblk查看现有的磁盘,看着比较简便明了。 lsblk  使用fdisk -l可以看到/dev/sdb下还有50G空间;目前是空闲的;我们需要将他这50G挂载到/data/上;  二、进行磁盘分区 首先我们先说说磁盘分区的一些命令:  磁盘分区的管理工具 fdisk gdisk lsblk # 查看磁盘分区 fdisk -l     # 查看所有磁盘信息 lsblk        # 查看所有磁盘信息  # 对磁盘分区进行操作 fdisk -l    # 对磁盘分区进行操作 gdisk -l    # 对磁盘分区进行操作(用于划分容量超过2T磁盘分区,需要手动安装) 进入磁盘分区的常用的操作命令 命令    解析 m /help    帮助命令,查看其他参数 p    列出分区表 n    创建新的分区 d    删除一个分区 v    查看分区详细信息 e    扩展分区 q    不保存,退出 w    保存,退出 分区讲解 MBR中只可以划分4个主分区,或者时3个主分区+1个扩展分区 扩展分区后,可以划分逻辑分区  进行磁盘分区 # 首先刷新一下磁盘分区表(如果没执行成功也无所谓,这步可有可无) partprobe  # 进行磁盘分区 [root@localhost ~]# fdisk /dev/sdb  Welcome to fdisk (util-linux 2.32.2). Changes will remain in memory only, until you decide to write them. Be careful before using the write command.  Device does not contain a recognized partition table. Created a new DOS disklabel with disk identifier 0x14c524a5.  # n 创建磁盘分区 Command (m for help): n Partition type    p   primary (0 primary, 0 extended, 4 free)    e   extended (container for logical partitions) Select (default p):   # 默认为主分区,直接回车;e为扩展分区。 Using default response p. # 选择1-4分区,默认为1,因为sdb还没有分区,所以直接回车默认就行 Partition number (1-4, default 1):  # 选择起始扇区,默认为2048,直接回车就好了 First sector (2048-104857599, default 2048):  # 选择要分的磁盘空间大小,默认就是最大,直接回车即可;如果有其他需求,写对应的值即可; Last sector, +sectors or +size{K,M,G,T,P} (2048-104857599, default 104857599):   # 提示,成功创建一个Linux类型的磁盘分区,大小为50G。 Created a new partition 1 of type 'Linux' and of size 50 GiB.  # p 进行查看,列出分区表;这块是最后我在虚拟机上复制的,虽然是中文,但是都一样。 Command (m for help): p 磁盘 /dev/sdb:53.7 GB, 53687091200 字节,104857600 个扇区 Units = 扇区 of 1 * 512 = 512 bytes 扇区大小(逻辑/物理):512 字节 / 512 字节 I/O 大小(最小/最佳):512 字节 / 512 字节 磁盘标签类型:dos 磁盘标识符:0xe5a06cd9     设备 Boot      Start         End      Blocks   Id  System /dev/sdb1            2048   104857599    52427776   83  Linux  # 可以看到已经列出来了一个50G的sdb1磁盘分区,这时候我们要保存退出; # w 保存退出,如果不想保存直接退出可以使用 q 。 Command (m for help): w The partition table has been altered. Calling ioctl() to re-read partition table. Syncing disks. 这样磁盘分区就创建完了;  查看磁盘分区 fdisk -l lsblk 可以看到sdb分出来一个区为50G;  接下来我们就是挂载磁盘分区。  三、挂载/dev/sdb1到/data/目录 首先,需要创建一个/data/空目录 # 记得先查看一下,如果没有就可以创建了,如果有可以重新找一个挂载目录。 然后对磁盘分区进行挂载;直接挂载肯定是不行的,会提示报错的;  所以需要先格式化磁盘分区。  这里针对一下centos格式化磁盘空间的类型; centos7是xfs,centos6是ext4  #Cetnos6格式化磁盘命令为: mkfs.ext4 -f /dev/[sda] #Centos7格式化磁盘命令为: mkfs.xfs -f /dev/[sda]  这里我们是centos7的环境,那么就使用xfs来格式化;  # 格式化磁盘分区 [root@192 ~]# mkfs.xfs -f /dev/sdb1 meta-data=/dev/sdb1              isize=512    agcount=4, agsize=3276736 blks          =                       sectsz=512   attr=2, projid32bit=1          =                       crc=1        finobt=0, sparse=0 data     =                       bsize=4096   blocks=13106944, imaxpct=25          =                       sunit=0      swidth=0 blks naming   =version 2              bsize=4096   ascii-ci=0 ftype=1 log      =internal log           bsize=4096   blocks=6399, version=2          =                       sectsz=512   sunit=0 blks, lazy-count=1 realtime =none                   extsz=4096   blocks=0, rtextents=0  # 格式化完之后,这时候我们就可以挂载目录了 [root@192 ~]# mount /dev/sdb1 /data/  # 挂载完成之后df -Th查看一下磁盘空间即可; [root@192 ~]# df -Th 文件系统                类型      容量  已用  可用 已用% 挂载点 devtmpfs                devtmpfs  898M     0  898M    0% /dev tmpfs                   tmpfs     910M     0  910M    0% /dev/shm tmpfs                   tmpfs     910M  9.6M  901M    2% /run tmpfs                   tmpfs     910M     0  910M    0% /sys/fs/cgroup /dev/mapper/centos-root xfs        17G  1.2G   16G    7% / /dev/sda1               xfs      1014M  150M  865M   15% /boot tmpfs                   tmpfs     182M     0  182M    0% /run/user/0 /dev/sdb1               xfs        50G   33M   50G    1% /data  # 可以看到/dev/sdb1已经挂载上了,类型为 xfs。 扩展:  mkfs: 创建文件系统 -f: 强制覆盖  红帽系统8.8格式化命令为: /sbin/mke2fs -j /dev/sdb1 (-j 是ext2,而日志是ext3) 永久挂载为: /dev/sdb1 /data ext3 defaults 1 2 至于为什么是ext3,我也不是很清楚,因为这是商家的一个要求,毕竟永久挂载错了,服务器就起不来了,就需要去虚拟机或者连接服务器去看报错信息;一般配置完这个导致服务器启动不来大部分都是因为类型配置错了。  这个直接mount挂载完只是临时挂载,如果不设置永久挂载,服务器重启之后就会掉,还需要手动去挂载,容易丢失数据,所以我们还需要设置永久挂载。  临时卸载挂载的磁盘为:numount /data/  四、设置永久挂载 永久挂载我们需要去/etc/fstab 配置文件中来配置;  [root@192 ~]# vim /etc/fstab  # 在最后一行添加 /dev/sdb1 /data/ xfs defaults 0 0 然后保存退出,重启测试reboot重启,如果配置的挂载有问题会导致服务器启动不了,需要到虚拟机或服务器去排查问题,查看报错信息,一般配置完这个导致服务器启动不了或者是大部分都是因为类型配置错了,所以要谨慎更谨慎;  解析:  /dev/sdb1:为磁盘分区的目录,也就是挂载到data的源目录 /data/:这个是目录路径,挂载的目标路径及目录 xfs:centos7是xfs,centos6是ext4,如果不确定可以使用df -Th命令看临时挂载的时候的类型是什么。 defaults:挂载的参数 defaults默认参数 第五段:是否使用dump备份 0不备份 1备份 (0) 第六段:是否使用fsck检测 0不检测 1检测 (0)  重启测试,可以连接上,在使用df -Th看看,没有问题即可;那便是完成永久挂载了。  五、配置完成 ———————————————— 版权声明:本文为CSDN博主「A-刘晨阳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/liu_chen_yang/article/details/134648396 
  • [技术干货] 几款值得选的SSH客户端软件
    对于服务器运维工作来说,我们少不了SSH远程客户端管理工具。我们在用哪款呢?比如常见用的包括PuTTY、XShell、WindTerm等,有很多的付费或者免费的,既然有这么多免费且好用的为什么我们还会选择付费的呢?在这篇文章中,乐小虎就来盘点市面上6款好用的SSH客户端软件,如果我们有需要的化当然建议到官方下载,确保软件的版本最新 ,以及安全。PS:对于FTP、SSH等服务器运维软件工具,不建议下载第三方的破解版或者汉化版,这样很容易有不安全的后门问题。1、PuTTYPuTTY SSH软件算是相当有年代感的免费SSH工具之一。相信很多老一辈的服务器运维都会用过这款软件,这款软件算是比较早的,当然也是免费的。只不过这么多年来PuTTY软件一直没有进行更新UI界面,所以对于颜值要求较高的用户来说逐渐的选择新的SSH工具。PuTTY SSH软件只支持Windows系统。2、XShellXShell SSH软件近几年比较流行的,基本上很多朋友从PuTTY都会过度到XShell。主要原因是颜值高、中文界面支持,而且还有多标签可以同时管理多态服务器。当然,这款软件的学生和家庭版是免费的,商业用途是需要授权。一般情况下,我们个人都会选择下载用学生和家庭版。XShell SSH软件也是只支持Win客户端的,同时也有配套的XFTP FTP软件。我在实用过程中不清楚是兼容性问题还是版本问题,我在有些电脑中用添加站点的时候会出现偶尔的无法添加的情况,尤其是FTP工具,我是用WinSCP FTP代替的。3、WindTermWindTerm 终端工具的特点支持 Windows、MacOS、Linux 三个系统镜像,最为主要的还是免费开源。我们看到 WindTerm 的界面就像IDE编辑器,体验上也是较好的,而且直接默认是本地语言版本,我们是简体中文版本。对于我们有些用的SSH工具,是不是比较讨厌SFTP和SSH工具是分开的。在WindTerm这里,我们可以在一个界面软件中实现文件上传和下载。上传直接拖动文件拉到文件管理器中就可以。下载直接看到文件右键指定下载到本地。包括文件的移动、复制等都可以可视化操作。WindTerm 支持Win、Mac等多跨平台,当然也是免费的。4、MobaXtermMobaXterm,一款功能齐全的远程工具箱。适用于在Win系统中,给我们提供SSH、FTP,以及RDP远程桌面连接任务。MobaXterm提供了所有重要的远程网络工具,比如支持SSH, X11, RDP, VNC, FTP, MOSH,以及Unix命令(bash, ls, cat, sed, grep, awk, rsync,…)到Windows桌面,有提供便携版和安装版。比如我们在使用SSH连接到远程服务器时,图形SFTP浏览器将自动弹出,以便直接编辑您的远程文件。远程应用程序还将使用嵌入式服务器无缝地显示在Windows桌面上。MobaXterm有提供家庭版免费版本,以及高级付费版本,当然免费版本是有同时打开服务器的数量限制的,以及线程数限制,但是不影响我们的日常使用。5、TermiusTermius,一个全平台的SSH客户端,支持安卓、MacOS、Windows、Mac、Linux,单机版SSH是免费的,如果需要用到云端和同步功能是付费的,需要在软件中自行订阅,我们只是简单的使用它的SSH功能,那么直接下载使用Termius免费版即可。与众不同的是,Termius支持SSH和SFTP,我们只需要一个软件就可以管理服务器。像我们用的XSHELL只有SSH功能。如果我们是安卓或者iOS客户端下载,在软件商店都应该是有的直接下载。如果是Linux、MacOS、Windows客户端,可以官方下载。6、FinalshellFinalshell,一款国产的SSH远程客户端工具。一体化的的服务器,网络管理软件,不仅是SSH客户端,还是功能强大的开发,运维工具,充分满足开发,运维需求。多平台支持,支持Linux、Win和Mac系统。支持云端同步、免费海外服务器远程桌面加速、SSH加速、本地化命令输入框、支持自动补全等。我们在用Finalshell软件的时候可以可视化的看到服务器的性能监控,如果不用云端同步功能无需付费。以上6款SSH软件肯定有一款是可以满足工作业务需要的。我没有准备移动端SSH软件的理由是,我们真运维服务器很少会在手机端操作的,也没有准备十款或者多款,毕竟能让我们选择的在这里几款中肯定有一款适合你。原创文章 乐小虎,如若转载,请注明出处:cid:link_3
  • 三款真正免费的密码管理器软件推荐(免费也强大)
    我们在前面的文章中有介绍到6款商业版密码管理器,对于很多朋友来说每个月十几元到二十几元的软件成本,而且每年都需要付费确实还是有一些不甘心。基于硕大的互联网资源,是不是能找到免费可用的密码管理器呢?对于大部分的密码管理器免费版本是有限制的,比如限制账户数量,限制不支持同步多设备。在这篇文章中,乐小虎整理三款真正可以免费的密码管理器软件。如果我们有在寻找免费密码管理器的可以参考使用。1、KeePassKeePass,这是一款免费开源的密码管理器,我之前也有使用好几年。唯独的缺点就是界面太丑,默认官方是有支持Win客户端软件的,且基于他的开源免费,有很多开发者匹配开发兼容支持的MacOS、手机端的软件匹配同步。KeePass密码管理器支持本地密码,或者是可以用支持webDAV网盘同步密码,以前我用的是坚果云盘同步密码的。这款软件是免费程度较高的密码库可以本地化的软件。2、BitwardenBitwarden,也是一款免费开源的密码管理器,有免费和付费版本。云端密码管理的免费和付费区别在于免费版的加密稍微弱一些,但是也是能用的。付费版本的云端加密功能比较强。但是,我们可以用Bitwarden云服务器端密码库自建密码库管理。Bitwarden 支持移动端、电脑端、浏览器端插件同步密码。支持一键导入到其他主流的密码管理器的密码。3、EnpassEnpass,原则上是一款商业密码管理器,有支持Win、Mac以及手机端跨平台。如果是单机电脑版是免费的,免费版本只支持单机个人使用。我们也可以作为本地用或者自己搭配webDAV网盘,但是,如果移动端也需要同步的是需要付费的。相比其他的密码管理器,Enpass 也可以选择买断模式,购买后就可以永久用了,当然也可以选择按月。总结,相对来说免费密码管理器还是有一些弊端的,我们也不能过分要求完美毕竟是免费的。原创文章,作者:乐小虎,如若转载,请注明出处:cid:link_0
  • 盘点10款国内国外主流云服务器可视化管理面板
    如今无论是搭建网站,还是部署小程序,甚至一些企业应用都会用到云服务求或者独立服务器。对于很多希望利用网站、网络创业的,也会用到服务器,不过在使用服务器过程中,我们对于服务器环境的配置,尤其是Web环境的部署面板,以及一些软件的安装,还是需要一些技术含量的。但是,如果我们借助一些第三方的运维软件工具,那是可以提高效率。在我们使用VPS和云服务器部署Web环境面板的时候,早年我们可能会去编译安装软件,但是如今我们可以使用可视化面板或者一键脚本。我们是不是也比较熟悉宝塔面板、AMH面板、WDCP面板、LNMP、LAMP一键安装脚本等。在这篇文章中,乐小虎准备盘点当下国内和国外主流的服务器面板和脚本工具,对比看看各自的优缺点,如果有需要的网友也可以参考选择。1、宝塔面板如果没有猜错的话,目前宝塔面板的用户量应该是比较多的。这款服务器、VPS面板软件虽然不是较早推出的,但是从开始永久免费的模式,加上更新推进速度,直接碾压超越原来一时很火的WDCP面板。当然,宝塔面板的受欢迎原因还在于软件的及时更新、用户体验、相关生态环境的自动化部署,包括运维管理提高运维工作效率。宝塔面板有支持Linux和Windows系统,免费版本能够满足大部分的用户使用。付费专业版和企业版,比免费版本多运维工具软件。如果不需要的话免费版本也是够用的。yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec建议安装在CentOS7+系统,兼容性脚本。复制脚本直接一键部署。2、WDCP如果我们老一辈的站长朋友们应该记得WDCP这款面板的,曾经可谓红极一时。在AMH面板宣布收费后,WDCP在几年时间家喻户晓。但是,随着用户体验的要求提高,以及软件版本更新不及时,包括有出现一些问题得不到及时的解决方案。WDCP面板的开发速度赶不上用户的需求,而在此时,宝塔面板的出现形成竞争对比。虽然WDCP面板的官方网站还存在,而且乐小虎有看到还有专业授权版等产品,但是在用户体验上,包括在知名度上已经逐渐淡出用户视野。#安装脚本 wget http://dl.wdlinux.cn/files/lanmp_v3.3.tar.gz tar zxvf lanmp_v3.3.tar.gz sh lanmp.sh3、AMHAMH面板也是国产面板,如果没有记错的话,在AMH4.2版本左右的时候还是免费的,但是由于运营策略的调整,后来要使用新版本都是需要授权收费,每月大约6元。虽然成本不高,但是对于我们国内用户的习惯来说,哪怕你收费1元,也会拒绝很多的用户,那时候国内还有其他的面板免费提供。从2022年夏天开始,AMH7.0版本开始又免费授权,对于我们用户来说又多一个选择。有些功能是宝塔面板专业版才有的功能在AMH面板中是免费使用的。#安装脚本 wget http://dl.amh.sh/amh.sh && bash amh.sh4、小皮面板提到小皮面板可能很多朋友不了解,但是如果说phpstudy应该很多人都知道吧。尤其是很多朋友本地电脑的客户端环境会有用到 phpstudy,没有错,这个软件的中文名就是小皮面板。具体是通过转让还是整合这里不清楚,反正现在这个老牌的服务器客户端WEB软件,独立出来运营的。小皮面板这款免费面板笔者也是有使用过的,但是是早期的版本,现在不清楚在用户体验上有没有修正。给我的个人感觉是功能上确实是够用的,毕竟这款面板的前生有一定的积累和技术开发能力。但是在操作用户体验上,相比宝塔面板还是弱一些。小皮面板也是有支持Linux和Windows系统的。yum install -y wget && wget -O install.sh https://notdocker.xp.cn/install.sh && sh install.sh5、AppNodeAppNode 面板也是国产面板之一,作者前身如果没有记错的话应该还有一款面板参与一个活动做的,后来逐渐不更新,而转向这款新的AppNode。这款面板软件有免费和付费功能,免费版本支持10个站点、当然对于一般的用户应该是够用的。这款软件和我们常规用的云服务器面板稍微不同,这个还属于服务器集群管理软件,安装一个控制中心即可管理你的所有服务器,其它服务器只需要安装受控端。再也不用分别登录到每台服务器去管理了。通过整合 Nginx、PHP、MySQL 等组件,帮助您快速创建和配置网站。笔者有体验过,如果初次使用可能稍微费力一些,操作习惯上稍微有点繁琐,适合服务器多的用户。单台服务器管理不适合用这个面板。#安装脚本 INSTALL_AGENT=1 INSTALL_APPS=sitemgr INIT_SWAPFILE=1 INSTALL_PKGS='nginx-stable,php74,mysql56' bash -c "$(curl -sS http://dl.appnode.com/install.sh)"6、Vesta Control PanelVesta Control Panel(VestaCP)一款国外的老牌服务器管理面板,涵盖我们常用的Linux服务器WEB环境的部署和邮局部署。以前我们国内还没有面板的时候,我们还是有很多人用的,但是在操作习惯上和语言上不是太合适,现在国内的朋友也很少有去用。VestaCP 面板的强大之处还支持用于服务器分销管理。支持设置中文,可以设置企业邮局。7、VirtualminVirtualmin 也是一款老牌的国外主机面板,支持Linux系统。这款WEB面板笔者很早也有用过,那时候国内确实找不到合适的,除了编译脚本安装,那只能用它。算是WEB服务器端面板的老前辈,现在看官方网站也有改版,也有一直在维护。笔者不可否认Virtualmin 面板在国外确实是知名度和安装量是很大的,但是在国内并不是特别适合。尤其是操作习惯上不适合我们,你功能再强大也没有用。wget https://software.virtualmin.com/gpl/scripts/install.sh sudo /bin/sh install.sh8、CyberPanelCyberPanel 面板来自知名的引擎开发商LiteSpeed。其实在早年的时候,笔者也有和 LiteSpeed 引擎商家也有沟通过开发面板或者脚本的事宜,因为当初市面上有一款llsmp脚本是基于openLiteSpeed且比较好用,但是后来开发者停止维护。CyberPanel 面板有基于openlistespeed的免费版,以及有支持litespeed的付费版本。如果我们有喜欢这个引擎的,可以选择这个可视化面板,我们其他看到的面板和一键安装包都是基于Nginx和Apache的居多。9、LNMP一键包在上面,我们介绍的8个WEB服务器安装面板都是基于客户端可视化的。但是我们有些朋友考虑到安全或者是服务器的负载问题,会考虑无面板的服务器WEB环境。这里LNMP一键安装包还是相当有知名度的,保持着每年6.1的大版本更新。wget http://soft.vpser.net/lnmp/lnmp1.9.tar.gz -cO lnmp1.9.tar.gz && tar zxf lnmp1.9.tar.gz && cd lnmp1.9 && ./install.sh lnmp当然,也是可以安装LAMP,只需要将脚本中最后的lnmp更换成lamp即可。我们可以根据实际的需要在安装向导中选择对应的软件。10、OneInStack一键包OneInStack 面世没有上面的LNMP早,但是凭借作者更新频率和软件的友好程度我个人觉得比LNMP好,所以很多得服务器部署无面板一键安装包我会用到OneInStack,而不是LNMP。OneInStack 支持快速部署PHP、Nginx、Apache、SSL证书,以及支持同步备份等。yum -y install wget screen #for CentOS/Redhat # apt-get -y install wget screen #for Debian/Ubuntu wget http://mirrors.linuxeye.com/oneinstack-full.tar.gz #包含源码,国内外均可下载 tar xzf oneinstack-full.tar.gz cd oneinstack #如果需要修改目录(安装、数据存储、Nginx日志),请修改options.conf文件 screen -S oneinstack #如果网路出现中断,可以执行命令`screen -R oneinstack`重新连接安装窗口 ./install.sh总结,以上乐小虎整理到当前市面上国内和国外服务器、VPS主机WEB面板。从我个人使用体验看,如果我们是单台服务器用得话,用AMH和宝塔面板合适。如果我们是集群服务器,可以用APPNODE,如果无需面板用脚本操作得话,用LNMP或者OneInStack。原创文章,作者:乐小虎,如若转载,请注明出处:cid:link_0
  • [技术干货] linux性能优化
    一、引言Linux作为一个强大的开源操作系统,广泛应用于服务器、桌面、嵌入式设备等领域。然而,随着应用复杂性的增加和硬件资源的有限,Linux系统性能优化变得越来越重要。本文将从多个方面详细探讨Linux性能优化的方法和技巧,帮助读者更好地发挥系统的潜力。二、系统资源监控top命令:实时显示系统中各个进程的资源占用情况,包括CPU、内存、I/O等。通过top命令,可以快速发现系统瓶颈和耗资源的进程。vmstat命令:显示包含CPU,内存,磁盘IO和进程等的系统信息。通过vmstat命令,可以实时监控系统的整体性能。iostat命令:用于监控系统输入输出设备负载情况,包括磁盘、网络等。三、CPU优化CPU调度策略:Linux支持多种CPU调度策略,如CFS(完全公平调度)、BFS(脑裂调度)等。选择合适的调度策略,可以提高CPU的利用率和响应速度。CPU亲和性设置:通过设置进程的CPU亲和性,可以将进程绑定到特定的CPU核心上运行,减少CPU上下文切换带来的开销。启用超线程:对于支持超线程的CPU,启用超线程可以提高CPU的并发处理能力。四、内存优化Swap空间调整:Swap空间是Linux系统中的虚拟内存,当物理内存不足时,系统会使用Swap空间。合理设置Swap空间的大小,可以避免内存溢出导致的性能下降。内存分页策略:Linux支持多种内存分页策略,如LRU(最近最少使用)、FIFO(先进先出)等。选择合适的分页策略,可以提高内存的利用率和访问速度。内存压缩与交换:启用内存压缩和交换功能,可以在内存紧张时,将部分内存数据进行压缩或交换到磁盘上,以释放物理内存空间。五、磁盘I/O优化文件系统选择:Linux支持多种文件系统,如Ext4、XFS、Btrfs等。不同文件系统在性能、稳定性和扩展性方面有所差异,选择合适的文件系统可以提高磁盘I/O性能。磁盘阵列(RAID):通过RAID技术,可以将多块磁盘组合成一个逻辑磁盘,提供更高的磁盘I/O性能和数据冗余能力。磁盘缓存:启用磁盘缓存,可以将频繁访问的数据存储在内存中,减少磁盘I/O操作次数,提高数据访问速度。六、网络优化网络参数调优:通过调整网络参数,如TCP拥塞控制算法、网络缓冲区大小等,可以提高网络传输性能。网络设备绑定:将多个网络设备绑定为一个逻辑设备,可以提高网络的带宽和容错能力。启用网络加速技术:如TCP_FASTOPEN、SO_REUSEPORT等,可以减少网络连接的建立时间和并发连接的开销,提高网络性能。七、内核参数优化修改内核启动参数:通过修改GRUB引导加载器的配置文件,可以调整内核的启动参数,如启用或禁用某些内核功能、调整内核的运行模式等。加载优化模块:根据系统需求,加载合适的内核优化模块,如文件系统优化模块、网络优化模块等,可以提高系统的整体性能。自定义内核:根据特定应用场景和需求,编译定制化的内核版本,可以去除不必要的功能和驱动,减小内核体积和启动时间。八、总结与建议本文从系统资源监控、CPU优化、内存优化、磁盘I/O优化、网络优化和内核参数优化等方面探讨了Linux性能优化的方法和技巧。为了保持系统持续高性能运行,建议读者定期监控和分析系统性能数据,结合实际应用场景和需求,综合运用本文提到的优化方法进行性能调优。
  • [技术干货] 深入探索Linux操作系统中的多线程编程
    深入探索Linux操作系统中的多线程编程一、引言多线程编程已经成为了现代软件开发的重要组成部分。对于Linux操作系统而言,多线程的支持和实现更是被广泛应用。本文将通过详细解析Linux操作系统中的多线程概念、线程的创建与管理、同步与互斥、线程间通信等方面,并结合示例代码,来深入探讨Linux的多线程编程。二、多线程的基本概念在现代操作系统中,进程是系统资源分配的最小单位,而线程则是CPU调度的最小单位。多线程编程是指在一个进程中创建多个线程,使得这些线程可以并发执行,从而提高程序的执行效率。优点:资源共享:同一进程的线程共享进程的内存空间、文件描述符等资源,不同线程间通信更便捷。经济高效:线程的创建、销毁和切换开销通常比进程小。并发执行:多线程能充分利用多核处理器,提高CPU利用率。三、线程的创建与管理在Linux系统中,我们通常使用POSIX线程库(pthread库)来创建和管理线程。主要函数包括pthread_create()创建一个新的线程,pthread_join()等待线程结束,pthread_exit()结束当前线程等。四、线程的同步与互斥多线程编程中,多个线程可能同时访问同一资源,如果处理不当,可能会导致数据不一致或其他不可预知的结果。因此,我们需要一些同步和互斥机制来确保数据的一致性和准确性。1. 互斥锁(Mutex)互斥锁是最常用的一种线程同步机制,它确保一次只有一个线程可以访问共享资源。在访问共享资源前,线程需要获取锁,如果锁被占用,线程将阻塞,直到锁被释放。2. 条件变量(Condition Variable)条件变量用于在多线程之间同步数据的访问。一个线程可以在条件变量上等待,直到另一个线程通知它某个条件已经满足。3. 信号量(Semaphore)信号量是一种用于保护对共享资源访问的同步原语。信号量维护一个计数器,表示可用的资源数量。线程在访问共享资源前,需要获取信号量。五、线程间通信线程间通信是多线程编程的重要部分。在Linux中,我们可以通过共享内存、消息队列、管道等方式实现线程间通信。选用何种通信方式,需根据具体的应用场景和需求来决定。六、示例代码解析在此部分,我们将通过一系列示例代码来实际演示如何在Linux系统中进行多线程编程,包括线程的创建、同步、互斥以及线程间的通信等。这些示例代码将用C语言编写,并使用pthread库来实现多线程。示例1:线程的创建和销毁我们首先创建一个简单的多线程程序,其中有两个线程,每个线程打印一条消息然后结束。示例2:线程的同步与互斥然后,我们创建一个多线程程序,多个线程共享一个全局变量,并使用互斥锁来确保同一时间只有一个线程可以修改该全局变量。示例3:线程间通信最后,我们创建一个程序,其中有两个线程,一个线程将消息写入共享队列,另一个线程从队列中读取消息。以此来演示线程间的通信。(由于篇幅限制,具体的示例代码在此省略。在实际编程过程中,你可以参考这些描述来编写你的多线程程序,也可以根据实际需求来修改和扩展这些示例代码。)七、总结与展望本文通过详细解析了Linux操作系统中多线程编程的各个方面,包括基本概念、线程的创建与管理、同步与互斥、线程间通信等,并给出了一系列示例代码来帮助理解。多线程编程能够极大提高程序的执行效率,但也需要我们注意数据的一致性、线程的同步等问题。
  • [技术干货] 数据库连接池性能优
    数据库连接池性能优化在现代企业级应用中,数据库连接是至关重要的部分。而数据库连接池作为数据库连接管理的核心组件,对于提升系统性能和稳定性具有重要意义。本文将深入探讨数据库连接池的性能优化,包含代码示例,帮助读者更好地理解和应用连接池技术。数据库连接池概述数据库连接池是一种创建和管理数据库连接的技术,用于减少创建新连接和销毁无效连接的开销。通过复用现有的数据库连接,连接池能有效降低系统资源消耗,提升数据库访问性能。连接池性能问题虽然数据库连接池能解决很多性能问题,但不当的使用或配置也可能导致性能瓶颈。常见的问题包括:连接池大小设置不当:连接池过大,会浪费系统资源;连接池过小,又可能导致数据库连接不足,影响系统性能。连接泄露:如果数据库连接在使用后没有被正确关闭或归还到连接池,会导致连接泄露,进而影响其他请求获取数据库连接。长连接与空闲连接管理不当:长连接过多可能会消耗过多资源,而空闲连接过多则可能导致资源浪费。连接池性能优化策略1. 设置合适的连接池大小设置合适的连接池大小是优化连接池性能的第一步。要根据应用的需求和数据库的性能来决定连接池的大小。如果数据库能支持更多并发连接,可以适当增大连接池的大小。同时要监控连接池的使用情况,根据实际情况进行调整。例如,在Java的HikariCP连接池中,可以这样设置连接池大小:HikariConfig config = new HikariConfig(); config.setMaximumPoolSize(100); //设置最大连接数 config.setMinimumIdle(10); //设置最小空闲连接数2. 防范连接泄露防范连接泄露的关键在于确保每次使用数据库连接后都能正确关闭或归还到连接池。在使用数据库连接时,建议使用try-with-resources语句确保连接的正确关闭。例如:try (Connection conn = dataSource.getConnection()) { // 执行数据库操作 } catch (SQLException e) { // 处理异常 }在这个例子中,无论是否发生异常,连接都会在try语句块结束后自动关闭。3. 长连接与空闲连接管理对于长连接和空闲连接的管理,可以通过设置合适的超时时间和空闲连接数来进行优化。超时时间不宜设置过长,避免无谓的资源消耗;空闲连接数也应适当,不宜过多也不宜过少。例如,在HikariCP中可以这样配置:config.setIdleTimeout(600000); //设置空闲连接超时时间,单位为毫秒 config.setMinimumIdle(5); //设置最小空闲连接数4. 使用连接复用技术对于支持复用连接的数据库操作(如查询操作),应尽量复用已有连接而不是创建新连接。这样可以减少创建和销毁连接的开销,提高性能。要确保复用连接的线程安全,防止出现线程间的数据干扰。5. 监控与调优要定期对数据库连接池进行监控和调优。通过监控可以了解连接池的使用情况,如活跃连接数、空闲连接数、等待连接的请求数等。根据监控结果,可以及时调整连接池的大小和其他参数,以达到最优性能。可以使用诸如JMX、Prometheus等监控工具进行监控。总结数据库连接池的性能优化是一个复杂且持续的过程,需要综合考虑多个因素进行调整。通过本文的介绍,希望能给读者提供一些思路和方向,以更好地进行数据库连接池的性能优化。在实际操作过程中,一定要结合自身的业务需求和系统环境,进行适当的调整和优化,以达到最佳的性能和稳定性。
  • [技术干货] ​Linux操作系统忘记root密码后的恢复方法
    Linux操作系统忘记root密码后的恢复方法当我们使用Linux操作系统时,有时候可能会遇到忘记root密码的情况。这种情况下,我们需要采取一些措施来恢复密码并重新进入系统。本文将详细介绍在忘记root密码后,如何在常见的Linux发行版中恢复进入系统的方法,包括代码部分。一、Ubuntu/Debian系统密码恢复对于Ubuntu和Debian系统,可以按照以下步骤进行密码恢复:重新启动系统,在引导过程中进入Grub引导菜单。在Grub菜单中,使用向下箭头键选择以“recovery mode”或“advanced options”开头的选项,然后按“e”键进入编辑模式。在编辑模式中,找到以“linux”或“linuxefi”开头的行,将光标移动到该行末尾。在行末尾添加以下代码,然后按下Ctrl + X组合键启动到单用户模式:rw init=/bin/bash进入单用户模式后,挂载文件系统为可写:mount -o remount,rw /编辑/etc/shadow文件,将root用户的密码字段清空:passwd -d root或者使用文本编辑器(如vi)打开/etc/shadow文件,找到root用户的行,将其密码字段(即第二个字段)清空。保存更改并重启系统:reboot系统重启后,您应该能够以空密码登录到root账户,然后尽快为root账户设置一个新的强密码。二、CentOS/RHEL系统密码恢复对于CentOS和RHEL系统,可以按照以下步骤进行密码恢复:重新启动系统,在引导过程中按下“e”键进入Grub引导菜单的编辑模式。在编辑模式中,找到以“vmlinuz”或“linux16”开头的行,将光标移动到该行末尾。在行末尾添加以下代码,然后按下Ctrl + X组合键以单用户模式启动:rd.break=pre-mount进入单用户模式后,挂载文件系统为可写:mount -o remount,rw /sysroot编辑/sysroot/etc/shadow文件,将root用户的密码字段清空:使用文本编辑器(如vi)打开/sysroot/etc/shadow文件,找到root用户的行,将其密码字段(即第二个字段)清空。注意,此时您处于chroot环境中,因此要对/sysroot目录下的文件进行编辑。保存更改并退出编辑器。然后执行以下命令重启系统:exit reboot系统重启后,您应该同样能够以空密码登录到root账户。登录后,请尽快为root账户设置一个新的强密码。三、其他注意事项和密码安全建议在成功恢复root密码并重新进入系统后,有几个注意事项和密码安全建议需要牢记:尽快为root账户设置一个新的强密码。密码应该包含大写字母、小写字母、数字和特殊字符,并且长度至少为8位。避免使用容易猜测或与个人信息相关的密码。对于生产环境中的重要服务器,建议使用专门的管理员账户进行日常操作,而不是直接使用root账户。这样可以更好地控制权限和审计操作。定期更换密码,并定期备份关键配置文件和数据。这将有助于防止未经授权的访问和数据泄露。启用SSH密钥认证作为远程登录的一种方式,以增加安全性。通过使用SSH密钥对进行身份验证,可以减少对密码的依赖,并提高登录的安全性。
  • [技术干货] Linux磁盘扩容技术详解
    Linux磁盘扩容技术详解一、引言 随着数据量不断增长,对磁盘空间的需求也日益迫切。作为IT运维人员,掌握Linux磁盘扩容技术至关重要。本文将介绍在Linux系统中进行磁盘扩容的必要性和核心技术,以帮助读者有效管理磁盘空间,满足不断增长的数据需求。 二、磁盘分区 2.1 磁盘分区概念 在Linux中,磁盘分区是将物理磁盘划分为独立的逻辑区域,每个区域可以作为一个独立的文件系统挂载到系统中。通过磁盘分区,我们可以更好地组织和管理磁盘空间。 2.2 使用fdisk进行磁盘分区 下面示例代码演示如何使用fdisk进行磁盘分区的步骤:以root权限运行fdisk命令sudo fdisk /dev/sdb创建新的分区Command (m for help): n Select (default p): p Partition number (1-4, default 1): First sector (2048-20971519, default 2048): Last sector, +sectors or +size{K,M,G} (2048-20971519, default 20971519):保存设置并退出Command (m for help): w 上述代码中,我们通过fdisk工具对/dev/sdb磁盘进行分区,创建了一个新的主分区。在实际操作中,根据磁盘大小和需求,可以选择创建更多分区。 三、文件系统扩展 3.1 文件系统扩展概念 当磁盘分区完成后,我们需要扩展文件系统以适应新的分区。文件系统扩展是在不丢失数据的情况下,使文件系统能够使用新的磁盘空间。 3.2 使用resize2fs扩展ext4文件系统 下面示例代码演示如何使用resize2fs扩展ext4文件系统的步骤:首先,卸载文件系统sudo umount /dev/sdbX使用e2fsck检查文件系统完整性sudo e2fsck -f /dev/sdbX使用resize2fs扩展文件系统sudo resize2fs /dev/sdbX 上述代码中,我们首先卸载要扩展的文件系统,然后使用e2fsck检查文件系统完整性,最后使用resize2fs命令扩展文件系统大小。这样,ext4文件系统就可以使用新的磁盘空间了。 四、逻辑卷管理(LVM) 4.1 LVM概念 LVM(逻辑卷管理)是一种在Linux中管理逻辑卷的技术。它允许我们在不改变物理磁盘配置的情况下,动态调整逻辑卷的大小。通过使用LVM,我们可以更灵活地管理磁盘空间,满足不断增长的数据需求。 4.2 扩展LVM逻辑卷 下面示例代码演示如何扩展LVM逻辑卷的步骤: 首先我们需要把新的分区创建为物理卷:创建物理卷sudo pvcreate /dev/sdbX 接下来我们可以将该物理卷加入到已有的卷组中:扩展卷组sudo vgextend vg_name /dev/sdbX 其中vg_name为你的卷组名称。之后我们需要将新的物理卷空间扩展到逻辑卷中:扩展逻辑卷sudo lvextend -l +100%FREE /dev/vg_name/lv_name 在这里lv_name代表你的逻辑卷名称。以上步骤将把新的物理卷空间全部扩展到逻辑卷中。最后我们需要扩展文件系统以使用新的空间:扩展ext4文件系统以适应新的逻辑卷大小sudo resize2fs /dev/vg_name/lv_name 经过以上步骤,我们成功的将新的磁盘空间通过LVM扩展到了已有的文件系统中。这样我们可以在不改变原有文件系统结构的情况下,实现磁盘空间的动态扩展。需要注意的是,以上步骤中的vg_name和lv_name需要替换为你实际的卷组和逻辑卷名称。在实际操作过程中,也请务必保证数据备份,防止误操作导致数据丢失。
  • [技术干货] Podman工具详解及使用指南
    一、Podman概述Podman是一个无守护进程、无根的容器引擎,允许开发人员和运行容器的工作负载的用户在Linux系统上运行OCI容器。Podman提供了与Docker类似的命令行接口,使用户能够轻松地从Docker迁移到Podman。Podman不需要守护进程,可以直接与容器运行时和镜像进行交互,从而提高了安全性和性能。二、安装与配置安装Podman在大多数Linux发行版中,可以通过包管理器安装Podman。例如,在Fedora上可以使用以下命令安装:sudo dnf install podman对于其他发行版,请参考官方文档的安装指南。配置PodmanPodman的配置文件位于/etc/containers/podman.conf。通过编辑此文件,可以自定义Podman的行为。例如,可以配置存储驱动、网络设置、身份验证等。在大多数情况下,默认配置即可满足需求。三、基本用法运行容器使用podman run命令可以创建一个新的容器并运行。例如,运行一个名为nginx的Nginx容器:podman run --name nginx -d nginx列出容器使用podman ps -a命令可以列出所有正在运行和已停止的容器。要查看容器的详细信息,可以使用podman inspect 命令。删除容器使用podman rm 命令可以删除一个已停止的容器。若要删除正在运行的容器,需要添加-f参数强制删除。四、高级特性容器网络Podman支持多种网络模式,包括bridge、host、none等。可以使用--network参数指定容器的网络模式。例如,创建一个使用bridge网络的容器:podman run --network bridge --name nginx -d nginx容器存储卷Podman允许创建和管理存储卷,以便在容器之间共享数据。使用podman volume create命令可以创建一个新的存储卷。例如,创建一个名为myvolume的存储卷:podman volume create myvolume要在容器中挂载存储卷,可以使用-v或--mount参数。例如,创建一个名为nginx的容器,并挂载myvolume存储卷:podman run -v myvolume:/usr/share/nginx/html --name nginx -d nginx这将把myvolume存储卷挂载到容器的/usr/share/nginx/html目录。五、示例代码以下是一个使用Podman创建并运行一个Nginx容器的示例代码:#!/bin/bash # 创建并运行Nginx容器 podman run --name nginx -d nginx # 列出正在运行的容器 podman ps # 查看容器详细信息 container_id=$(podman ps -lq) podman inspect $container_id # 删除容器 podman rm -f $container_id
  • [技术干货] 创建多线程的几种方法
    创建多线程的几种方法 Python创建多线程主要有如下两种方法:函数类接下来,我们就来揭开多线程的神秘面纱。用函数创建多线程 在Python3中,Python提供了一个内置模块 threading.Thread,可以很方便地让我们创建多线程。threading.Thread() 一般接收两个参数:线程函数名:要放置线程让其后台执行的函数,由我们自已定义,注意不要加();线程函数的参数:线程函数名所需的参数,以元组的形式传入。若不需要参数,可以不指定。举个例子1 import time 2 from threading import Thread 3 ​ 4自定义线程函数。5 def target(name="Python"): 6 for i in range(2): 7 print("hello", name) 8 time.sleep(1) 9 ​ 10创建线程01,不指定参数11 thread_01 = Thread(target=target) 12启动线程0113 thread_01.start() 14 ​ 15 ​ 16创建线程02,指定参数,注意逗号17 thread_02 = Thread(target=target, args=("MING",)) 18启动线程0219 thread_02.start() 可以看到输出1 hello Python 2 hello MING 3 hello Python 4 hello MING 2. 用类创建多线程 相比较函数而言,使用类创建线程,会比较麻烦一点。首先,我们要自定义一个类,对于这个类有两点要求,必须继承 threading.Thread 这个父类;必须复写 run 方法。这里的 run 方法,和我们上面线程函数的性质是一样的,可以写我们的业务逻辑程序。在 start() 后将会调用。来看一下例子 为了方便对比,run函数我复用上面的main。1 import time 2 from threading import Thread 3 ​ 4 class MyThread(Thread): 5 def init(self, type="Python"): 6 # 注意:super().init() 必须写 7 # 且最好写在第一行 8 super().init() 9 self.type=type 10 ​ 11 def run(self): 12 for i in range(2): 13 print("hello", self.type) 14 time.sleep(1) 15 ​ 16 if name == 'main': 17 # 创建线程01,不指定参数 18 thread_01 = MyThread() 19 # 创建线程02,指定参数 20 thread_02 = MyThread("MING") 21 ​ 22 thread_01.start() 23 thread_02.start() 当然结果也是一样的。1 hello Python 2 hello MING 3 hello Python 4 hello MING 3. 线程对象的方法 上面介绍了当前 Python 中创建线程两种主要方法。创建线程是件很容易的事,但要想用好线程,还需要学习线程对象的几个函数。经过我的总结,大约常用的方法有如下这些:1如上所述,创建一个线程2 t=Thread(target=func) 3 ​ 4启动子线程5 t.start() 6 ​ 7阻塞子线程,待子线程结束后,再往下执行8 t.join() 9 ​ 10判断线程是否在执行状态,在执行返回True,否则返回False11 t.is_alive() 12 t.isAlive() 13 ​ 14设置线程是否随主线程退出而退出,默认为False15 t.daemon = True 16 t.daemon = False 17 ​ 18设置线程名19 t.name = "My-Thread"