最新资讯

  • Linux之进程控制

Linux之进程控制

2025-05-16 04:37:36 1 阅读

目录

一、进程创建

1.1、fork函数初始

1.2、fork函数返回值

1.3、写实拷贝

1.4、fork常规用法

1.5、fork调用失败的原因

二、进程终止

2.1、进程退出场景

2.2、进程常见退出方法

2.2.1、退出码

2.3.2、_exit函数

2.3.3、exit函数

2.3.4、return退出

三、进程等待

3.1、进程等待必要性

3.2、进程等待的方法

3.2.1、wait方法

3.2.2、waitpid方法

3.2.3、获取子进程status

3.2.4、阻塞与非阻塞等待

3.2.5、为什么要通过系统调用来获取进程退出码

四、进程程序替换

4.1、替换原理

4.2、替换函数

4.2.1、函数解释

4.2.2、命名理解

五、自主Shell命令行解释器

5.1、目标

5.2、实现原理

5.3、源码

5.4、总结


一、进程创建

1.1、fork函数初始

在linux中fork函数是⾮常重要的函数,它从已存在进程中创建⼀个新进程。新进程为⼦进程,⽽原进程为⽗进程。

#include

pid_t  fork(void);

返回值:子进程中返回0,⽗进程返回⼦进程id,出错返回-1

进程调⽤fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给⼦进程
  • 将⽗进程部分数据结构内容拷⻉⾄⼦进程
  • 添加⼦进程到系统进程列表当中
  • fork返回,开始调度器调度

当⼀个进程调⽤fork之后,就有两个⼆进制代码相同的进程。⽽且它们都运⾏到相同的地⽅。但每个进程都将可以开始它们⾃⼰的旅程,如下图:

所以,fork之前⽗进程独⽴执⾏,fork之后,⽗⼦两个执⾏流分别执⾏。注意,fork之后,谁先执⾏完全由调度器决定。

1.2、fork函数返回值

  • ⼦进程返回0。
  • ⽗进程返回的是⼦进程的pid。

1.3、写实拷贝

通常,⽗⼦代码共享,⽗⼦在不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷⻉的⽅式各⾃⼀份副本。具体⻅下图:

详细解释:父进程创建子进程后,更新页表中表示数据访问权限的标志位为只读属性,当父子进程中的某一方写入数据时,触发系统错误,进而引发缺页中断,然后系统就会进行一系列较为复杂的检测,当判定需要发生写实拷贝后,就会开始申请内存,发生拷贝,修改父子页表对应数据的权限为可读可写,然后恢复执行。

意义:因为有写时拷⻉技术的存在,所以⽗⼦进程得以彻底分离离!完成了进程独⽴性的技术保证!

写时拷⻉,是⼀种延时申请技术,可以提⾼整机内存的使⽤率。

1.4、fork常规用法

  • ⼀个⽗进程希望复制⾃⼰,使⽗⼦进程同时执⾏不同的代码段。例如,⽗进程等待客户端请求, ⽣成⼦进程来处理请求。
  • ⼀个进程要执⾏⼀个不同的程序。例如⼦进程从fork返回后,调⽤exec函数。

1.5、fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

二、进程终止

进程终⽌的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。

2.1、进程退出场景

  • 代码运⾏完毕,结果正确
  • 代码运⾏完毕,结果不正确
  • 代码异常终⽌
    • 当代码是异常终止时,其实是OS提前发现的代码的问题,然后发送对应的信号终止了该进程。

所有信号:

2.2、进程常见退出方法

正常终⽌(可以通过 echo $? 查看进程退出码):

  • 从main返回
  • 调⽤exit
  • _exit

异常退出:

  • ctrl + c,信号终⽌

2.2.1、退出码

退出码(退出状态)可以告诉我们最后⼀次执⾏的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表⽰执⾏成功,没有问题。 代码 1 或 0 以外的任何代码都被视为不成功。通过错误码,我们可以让父进程或系统更好的了解当前进程是成功了还是失败了,如果失败了是因为什么失败的。

Linux Shell 中的主要退出码:

  • 退出码 0 表⽰命令执⾏⽆误,这是完成命令的理想状态。
  • 退出码 1 我们也可以将其解释为 “不被允许的操作”。例如在没有 sudo 权限的情况下使⽤ yum;再例如除以 0 等操作也会返回错误码 1 ,对应的命令为 let a=1/0 。
  • 130 ( SIGINT 或 Ctrl C )和 143 ( SIGTERM )等终⽌信号是⾮常典型的,它们属于 128+n 信号,其中 n 代表终⽌码。
  • 可以使⽤strerror函数来获取退出码对应的描述。

示例代码:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 
  7 int main()
  8 {
  9   printf("before: errno: %d, errstring: %s
",errno,strerror(errno));
 10 
 11   FILE *fp = fopen("log.txt","r");
 12   if(fp == nullptr)
 13   {
 14     printf("after: errno: %d, errstring: %s
",errno,strerror(errno));
 15     return errno;                                                                                                                                                               
 16   }
 17 
 18   return 0;
 19 }

效果:

解释:C语言中提供了可以查看当前错误码的全局变量errno,只要包含errno.h头文件就可以使用,还提供了将错误码转换为对应错误信息的strerror函数。

2.3.2、_exit函数

#include

void _exit(int status);

参数:status 定义了进程的终⽌状态,⽗进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被⽗进程所⽤。所以_exit(-1)时,在终端执⾏$?发现 返回值是255。

示例代码一:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 
  7 void func()
  8 {
  9   std::cout<<"hello,bit"<

效果:

解释:可以看到 "进程结束了"这句话并没有打印,所以得到结论 _exit 方法的作用是结束整个进程。

示例代码二:

#include
#include 
#include

int main()
{
    printf("进程运行结束!
");
    sleep(2);
    _exit(20);

    return 0;
}

效果:

解释:从上面代码可以得到结论,_exit 结束进程时,如果缓冲区内有数据,_exit 不会刷新。

2.3.3、exit函数

#include

void  exit(int status);

exit最后也会调⽤_exit,但在调⽤_exit之前,还做了其他⼯作:

  1. 执⾏用户通过 atexit 或 on_exit 定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写⼊。
  3. 调⽤_exit。

示例代码一:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 
  7 void func()
  8 {
  9   std::cout<<"hello,bit"<

效果:

解释:可以看到 "进程结束了"这句话并没有打印,所以得到结论 exit 方法的作用是结束整个进程。

示例代码二:

#include
#include 
#include

int main()
{
    printf("进程运行结束!
");
    sleep(2);
    exit(20);

    return 0;
}

效果:

解释:我们可以看到 "进程结束了"这句话打印出来了,这里代码我们没有通过 ' ' 强制刷新缓冲区,所以说明 exit 方法结束进程前会主动帮我们刷新缓冲区。

2.3.4、return退出

示例代码:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 
  7 int func()
  8 {
  9   std::cout<<"hello,bit"<

效果:

解释:return是⼀种更常⻅的退出进程⽅法。执⾏return num等同于执⾏exit(n),因为调⽤main的运⾏时函数会将main的返回值当做 exit 的参数。另外,只有main函数执行 return 语句才会结束进程,其他函数执行 return 语句只是结束当前函数而已。

三、进程等待

3.1、进程等待必要性

  • ⼦进程退出,⽗进程如果不管不顾,就可能造成‘僵⼫进程’的问题,进⽽造成内存泄漏。
  • 另外,进程⼀旦变成僵⼫状态,那就⼑枪不⼊,“杀⼈不眨眼”的kill -9 也⽆能为⼒,因为谁也没有办法杀死⼀个已经死去的进程。
  • 最后,⽗进程派给⼦进程的任务完成的如何,我们是需要知道的。如,⼦进程运⾏完成,结果对还是不对,或者是否正常退出。
  • ⽗进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息。

3.2、进程等待的方法

3.2.1、wait方法

#include

#include

pid_t wait(int* status);

返回值:

        成功返回被等待进程pid,失败返回-1。

参数:

        输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。

示例代码:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 
  8 int main()
  9 {
 10   pid_t id = fork();
 11   if(id < 0)
 12   {
 13     printf("errno: %d,errstring: %s
",errno,strerror(errno));
 14     return errno;
 15   }
 16   else if(id == 0)
 17   {
 18     int cnt = 3;
 19     while(cnt)
 20     {
 21       printf("子进程运行中,pid:%d
",getpid());
 22       cnt--;
 23       sleep(1);
 24     }
 25     exit(123);
 26   }
 27   else
 28   {
 29     sleep(5);
 30     pid_t rid = wait(nullptr);
 31     if(rid > 0)
 32     {
 33       printf("wait sub process success,rid: %d
",rid);
 34     }
 35     else
 36     {                                                                                                                                                                           
 37       perror("wait fail:");
 38     }
 39     while(true)
 40     {
 41       printf("我是父进程,pid: %d
",getpid());
 42       sleep(1);
 43     }
 44   }
 45 
 46   return 0;  
 47 }

效果:

3.2.2、waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

        当正常返回的时候waitpid返回收集到的⼦进程的进程ID;

        如果设置了选项WNOHANG,⽽调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;

        如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;

参数:

        pid:

                pid=-1,等待任⼀个⼦进程。与wait等效。

                pid>0.等待其进程ID与pid相等的⼦进程。

        status: 输出型参数

        WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程是否是正常退出)

        WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程的退出码)

        options:默认为0,表⽰阻塞等待。

        WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该⼦进程的ID。

  • 如果⼦进程已经退出,调⽤wait/waitpid时,wait/waitpid会⽴即返回,并且释放资源,获得⼦进程退出信息。
  • 如果在任意时刻调⽤wait/waitpid,⼦进程存在且正常运⾏,则进程可能阻塞。
  • 如果不存在该⼦进程,则⽴即出错返回。

示例代码:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 
  8 int main()
  9 {
 10   pid_t id = fork();
 11   if(id < 0)
 12   {
 13     printf("errno: %d,errstring: %s
",errno,strerror(errno));
 14     return errno;
 15   }
 16   else if(id == 0)
 17   {
 18     int cnt = 3;
 19     while(cnt)
 20     {
 21       printf("子进程运行中,pid:%d
",getpid());
 22       cnt--;
 23       sleep(1);
 24     }
 25     exit(123);
 26   }
 27   else
 28   {
 29     sleep(5);
 30     pid_t rid = waitpid(id,nullptr,0);
 31     if(rid > 0)
 32     {
 33       printf("wait sub process success,rid: %d
",rid);
 34     }
 35     else
 36     {                                                                                                                                                                           
 37       perror("wait fail:");
 38     }
 39     while(true)
 40     {
 41       printf("我是父进程,pid: %d
",getpid());
 42       sleep(1);
 43     }
 44   }
 45 
 46   return 0;  
 47 }

效果:

3.2.3、获取子进程status

wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。

如果传递NULL,表⽰不关⼼⼦进程的退出状态信息。

否则,操作系统会根据该参数,将⼦进程的退出信息反馈给⽗进程。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16 ⽐特位):

在 status 的低十六位中,次低八位是退出码,次低七位是退出信号的值。

示例代码:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 
  8 int main()
  9 {
 10   pid_t id = fork();
 11   if(id < 0)
 12   {
 13     printf("errno: %d,errstring: %s
",errno,strerror(errno));
 14     return errno;
 15   }
 16   else if(id == 0)
 17   {
 18     int cnt = 3;
 19     while(cnt)
 20     {
 21       printf("子进程运行中,pid:%d
",getpid());
 22       cnt--;
 23       sleep(1);
 24     }
 25     exit(123);
 26   }
 27   else
 28   {
 29     sleep(5);
 30 
 31     int status = 0;
 32     pid_t rid = waitpid(id, &status, 0);
 33 
 34     if(rid > 0)                                                                                                                                                                 
 35     {
 36       if(WIFEXITED(status))
 37       {
 38         //两种获取退出码的方法,第一种还可以获取退出信号
 39         printf("wait sub process success,rid: %d, status code: %d, exit signal: %d
", rid, (status>>8)&0xFF, status&0x7F);
 40 
 41         printf("wait sub process success,rid: %d, status code: %d
", rid, WEXITSTATUS(status));
 42       }
 43       else
 44       {
 45         printf("child process quit error!
");
 46       }
 47     }
 48     else
 49     {
 50       perror("wait fail:");
 51     }
 52 
 53     while(true)
 54     {
 55       printf("我是父进程,pid: %d
",getpid());
 56       sleep(1);
 57     }
 58   }
 59 
 60   return 0;  
 61 }

效果:

3.2.4、阻塞与非阻塞等待

  • 进程的阻塞等待⽅式:

示例代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "task.h"

enum{
    OK = 0,
    OPEN_FILE_ERROR,
};

const std::string gsep = " ";
std::vector data;

int SaveBegin()
{
    std::string name = std::to_string(time(nullptr));
    name += ".backup";
    FILE *fp = fopen(name.c_str(), "w");
    if(fp == nullptr) return OPEN_FILE_ERROR;

    std::string dataStr;
    for (auto d : data)
    {
        dataStr += std::to_string(d);
        dataStr += gsep;
    }
    fputs(dataStr.c_str(), fp);
    fclose(fp);
    return OK;
}

void Save()
{
    pid_t id = fork();
    if(id == 0) // 子进程
    {
        int code = SaveBegin();
        exit(code);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        int code = WEXITSTATUS(status);
        if(code == 0) printf("备份成功, exit code : %d
", code);
        else printf("备份失败, exit code : %d
", code);
    }
    else
    {
        perror("waitpid");
    }
}

int main()
{
    int cnt = 1;
    while(true)
    {
        data.push_back(cnt++);
        sleep(1);

        if(cnt % 10 == 0)
        {
            Save();
        }
    }
}
  • 进程的非阻塞等待方式:

如果我们想让父进程在等待子进程的同时还可以做自己的事,不被阻塞住,我们可以通过将waitpid方法的第三个参数传入WNOHANG,即可进入非阻塞等待模式。该方法返回值 > 0 时,等待成功,返回值为目标子进程的pid;当返回值 == 0 时,等待成功,但是目标子进程没有退出,返回值< 0 时,等待失败。

示例代码:

task.h:

#pragma once
#include 

void PrintLog();
void Download();
void Backup();

task.cc:

#include "task.h"

void PrintLog()
{
    std::cout << "Print log task" << std::endl;
}

void Download()
{
    std::cout << "DownLoad task" << std::endl;
}

void Backup()
{
    std::cout << "BackUp task" << std::endl;
}

proc.cc

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "task.h"

typedef std::function task_t;

void LoadTask(std::vector &tasks)
{
    tasks.push_back(PrintLog);
    tasks.push_back(Download);
    tasks.push_back(Backup);
}

int main()
{
    std::vector tasks;
    LoadTask(tasks);

    pid_t id = fork();
    if(id == 0)
    {
        // child
        while(true)
        {
            printf("我是子进程, pid : %d
", getpid());
            sleep(1);
        }
        exit(0);
    }

    // father
    while(true)
    {
        sleep(1);
        pid_t rid = waitpid(id, nullptr, WNOHANG);
        if(rid > 0)
        {
            printf("等待子进程%d 成功
", rid);
            break;
        }
        else if(rid < 0)
        {
            printf("等待子进程失败
");
            break;
        }
        else
        {
            printf("子进程尚未退出
");

            // 做自己的事情
            for(auto &task : tasks)
            {
                task();
            }
        }
    }

}

Makefile:

process:process.cc task.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f process

3.2.5、为什么要通过系统调用来获取进程退出码

这是因为如果我们使用全局变量来获取,子进程和父进程的数据在不修改时是共享的,但是一旦修改就会发生写实拷贝,这就导致父进程拿不到修改后的值,可子进程要想通过变量将退出码传递出去,就必须对变量进行修改,所以通过变量的方式行不通,就只能通过系统提供的接口了。

四、进程程序替换

fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢?进程的程序 替换来完成这个功能!程序替换是通过特定的接⼝,加载磁盘上的⼀个全新的程序(代码和数据),加载到调⽤进程的地址空间中!

4.1、替换原理

⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀种exec函数以执⾏另⼀个程序。当进程调⽤⼀种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执⾏。调⽤exec并不创建新进程,所以调⽤exec前后该进程的id并未改变。

4.2、替换函数

其实有六种以exec开头的函数,统称exec函数:

#include

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

4.2.1、函数解释

  • 这些函数如果调⽤成功则加载新的程序从启动代码开始执⾏,不再返回。
  • 如果调⽤出错则返回-1。
  • 所以exec函数只有出错的返回值⽽没有成功的返回值。

4.2.2、命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • l(list):表⽰参数采⽤列表。
  • v(vector):参数⽤数组。
  • p(path):有p⾃动搜索环境变量PATH。
  • e(env):表⽰⾃⼰维护环境变量。

exec调⽤举例如下:

为了方便演示,所有exec调用都放到了一个程序中了,实际使用时一个子进程根据需要执行某一个就可以。

myexec.cc:

    1 #include 
    2 #include 
    3 #include 
    4 #include 
    5 #include 
    6 
    7 const std::string myenv="HELLO=AAAAAAAAAAAAAAAAAAAA";
    8 
    9 extern char **environ;
   10 int main()
   11 {
   12     //在原有环境变量上增加新的
   13     putenv((char*)myenv.c_str());
   14 
   15     pid_t id = fork();
   16 
   17     if(id == 0)
   18     {                                                                                                                                                                         
   19         char *const argv[] = {
   20             (char*)"other",
   21             nullptr
   22         };
   23 
   24         //(void)argv;
W> 25         char *const env[] = {
   26             (char*)"HELLO=bite",
   27             (char*)"HELLO1=bite1",
   28             (char*)"HELLO2=bite2",
   29             (char*)"HELLO3=bite3"
   30         };
   31 
   32         //允许自己传入环境变量
   33         // 传入的会覆盖继承来的
   34         // 该方法会使用传入的新的环境变量
   35         // 我们可以自己写一个全新的,也可以在原有环境变量后面添加新的项
   36         execvpe("./other", argv, environ);                                                                                                                                    
   37 
   38         //可以自动查找环境变量,第一个参数不需要指明路径
   39         execvp(argv[0], argv);
   40 
   41         //和execl一样,只是第二个参数变成了数组
   42         execv("/usr/bin/ls", argv);
   43         
   44         // execl最后一个参数必须是nullptr
   45         // 第一个参数代表要执行谁
   46         // 后面的可变参数列表表明要怎么执行
   47         execl("/bin/ls", "ls", "-l", "--color", "-a", nullptr);
   48         execl("./other", "other", nullptr);
   49         execl("/usr/bin/python", "python", "test.py", nullptr);
   50         execl("/usr/bin/bash", "bash", "test.sh", nullptr);
   51         
   52         //自动查找环境变量,第一个参数不需要指明路径
   53         execlp("ls", "ls", "--color", "-aln", nullptr);
   54         
   55         //如果程序替换失败,结束子进程返回错误码 -> 1
   56         exit(1);
   57     }
   58    
   59     // father
   60     pid_t rid = waitpid(id, nullptr, 0);
   61     if(rid > 0)
   62     {
   63         printf("等待子进程成功!
");
   64     }
   65 
   66     return 0;
   67 }

other.c:

#include 

extern char**environ;

int main()
{
    for(int i = 0; environ[i]; i++)
    {
        printf("evn[%d]: %s
", i, environ[i]);
    }
}

test.py:

#!/usr/bin/python

print ("hello python")

test.sh:

#!/bin/bash


echo "hello shell"

Makefile:

myexec:myexec.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f myexec

事实上,只有execve是真正的系统调⽤,其它五个函数最终都调⽤execve,所以execve在man⼿册第2节, 其它函数在man⼿册第3节。这些函数之间的关系如下图所⽰。下图exec函数簇⼀个完整的例⼦:

五、自主Shell命令行解释器

5.1、目标

  • 要能处理普通命令。
  • 要能处理内建命令。
  • 要能帮助我们理解内建命令/本地变量/环境变量这些概念。
  • 要能帮助我们理解shell的允许原理。

5.2、实现原理

⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时间的流逝从左向右移动。shell从用户读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运⾏ls程序并等待那个进程结束。

然后shell读取新的⼀⾏输⼊,建⽴⼀个新的进程,在这个进程中运⾏程序并等待这个进程结束。 所以要写⼀个shell,需要循环以下过程:

  • 获取命令⾏
  • 解析命令⾏
  • 建⽴⼀个⼦进程(fork)
  • 替换⼦进程(execvp)        
  • ⽗进程等待⼦进程退出(wait)

根据这些思路,和我们前⾯的学的技术,就可以⾃⼰来实现⼀个shell了。

5.3、源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
// 全局的命令行参数表
char *gargv[argvnum];
int gargc = 0;

// 全局的变量
int lastcode = 0;

// 我的系统的环境变量
char *genv[envnum];

// 全局的当前shell工作路径 
char pwd[basesize];
char pwdenv[basesize];

string GetUserName()
{
    string name = getenv("USER");
    return name.empty() ? "None" : name;
}

string GetHostName()
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty() ? "None" : hostname;
}

string GetPwd()
{
    if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";
    snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);
    putenv(pwdenv); // PWD=XXX
    return pwd;

    //string pwd = getenv("PWD");
    //return pwd.empty() ? "None" : pwd;
}

string LastDir()
{
    string curr = GetPwd();
    if(curr == "/" || curr == "None") return curr;
    // /home/whb/XXX
    size_t pos = curr.rfind("/");
    if(pos == std::string::npos) return curr;
    return curr.substr(pos+1);
}

string MakeCommandLine()
{
    // [whb@bite-alicloud myshell]$ 
    char command_line[basesize];
    snprintf(command_line, basesize, "[%s@%s %s]# ",
            GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
    return command_line;
}

void PrintCommandLine() // 1. 命令行提示符
{
    printf("%s", MakeCommandLine().c_str());
    fflush(stdout);
}

bool GetCommandLine(char command_buffer[], int size)   // 2. 获取用户命令
{
    // 我们认为:我们要将用户输入的命令行,当做一个完整的字符串
    // "ls -a -l -n"
    char *result = fgets(command_buffer, size, stdin);
    if(!result)
    {
        return false;
    }
    command_buffer[strlen(command_buffer)-1] = 0;
    if(strlen(command_buffer) == 0) return false;
    return true;
}

void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{
    (void)len;
    memset(gargv, 0, sizeof(gargv));
    gargc = 0;
    // "ls -a -l -n"
    const char *sep = " ";
    gargv[gargc++] = strtok(command_buffer, sep);
    // =是刻意写的
    while((bool)(gargv[gargc++] = strtok(nullptr, sep)));
    gargc--;
}

void debug()
{
    printf("argc: %d
", gargc);
    for(int i = 0; gargv[i]; i++)
    {
        printf("argv[%d]: %s
", i, gargv[i]);
    }
}
// 在shell中
// 有些命令,必须由子进程来执行
// 有些命令,不能由子进程执行,要由shell自己执行 --- 内建命令 built command
bool ExecuteCommand()   // 4. 执行命令
{
    // 让子进程进行执行
    pid_t id = fork();
    if(id < 0) return false;
    if(id == 0)
    {
        //子进程
        // 1. 执行命令
        execvpe(gargv[0], gargv, genv);
        // 2. 退出
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else
        {
            lastcode = 100;
        }
        return true;
    }
    return false;
}

void AddEnv(const char *item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }

    genv[index] = (char*)malloc(strlen(item)+1);
    strncpy(genv[index], item, strlen(item)+1);
    genv[++index] = nullptr;
}
// shell自己执行命令,本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{
    if(strcmp(gargv[0], "cd") == 0)
    {
        // 内建命令
        if(gargc == 2)
        {
            chdir(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 1;
        }
        return true;
    }
    else if(strcmp(gargv[0], "export") == 0)
    {
        // export也是内建命令
        if(gargc == 2)
        {
            AddEnv(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 2;
        }
        return true;
    }
    else if(strcmp(gargv[0], "env") == 0)
    {
        for(int i = 0; genv[i]; i++)
        {
            printf("%s
", genv[i]);
        }
        lastcode = 0;
        return true;
    }
    else if(strcmp(gargv[0], "echo") == 0)
    {
        if(gargc == 2)
        {
            // echo $?
            // echo $PATH
            // echo hello
            if(gargv[1][0] == '$')
            {
                if(gargv[1][1] == '?')
                {
                    printf("%d
", lastcode);
                    lastcode = 0;
                }
            }
            else
            {
                printf("%s
", gargv[1]);
                lastcode = 0;
            }
        }
        else
        {
            lastcode = 3;
        }
        return true;
    }
    return false;
}

// 作为一个shell,获取环境变量应该从系统的配置来
// 我们今天就直接从父shell中获取环境变量
void InitEnv()
{
    extern char **environ;
    int index = 0;
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index])+1);
        strncpy(genv[index], environ[index], strlen(environ[index])+1);
        index++;
    }
    genv[index] = nullptr;
}

int main()
{
    InitEnv();
    char command_buffer[basesize];
    while(true)
    {
        PrintCommandLine(); // 1. 命令行提示符
        // command_buffer -> output
        if( !GetCommandLine(command_buffer, basesize) )   // 2. 获取用户命令
        {
            continue;
        }
        //printf("%s
", command_buffer);
        //ls
        //"ls -a -b -c -d"->"ls" "-a" "-b" "-c" "-d"
        ParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令

        if ( CheckAndExecBuiltCommand() )
        {
            continue;
        }

        ExecuteCommand();   // 4. 执行命令
    }
    return 0;
}

5.4、总结

exec/exit就像call/return。

 ⼀个C程序有很多函数组成。⼀个函数可以调⽤另外⼀个函数,同时传递给它⼀些参数。被调⽤的函数执⾏⼀定的操作,然后返回⼀个值。每个函数都有他的局部变量,不同的函数通过call/return系统进⾏通信。

这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux⿎励将这种应⽤于程序之内的模式扩展到程序之间。如下图

⼀个C程序可以fork/exec另⼀个程序,并传给它⼀些参数。这个被调⽤的程序执⾏⼀定的操作,然后通过exit(n)来返回值。调⽤它的进程可以通过wait(&ret)来获取exit的返回值。

本文地址:https://www.vps345.com/10829.html

搜索文章

Tags

PV计算 带宽计算 流量带宽 服务器带宽 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP docker 容器 运维 java-rabbitmq java 服务器安全 网络安全策略 防御服务器攻击 安全威胁和解决方案 程序员博客保护 数据保护 安全最佳实践 服务器 linux 游戏 云计算 Deepseek Deepseek-R1 大模型 私有化部署 推理模型 网络工程师 网络管理 软考 2024 2024年上半年 下午真题 答案 神经网络 人工智能 深度学习 计算机视觉 卷积神经网络 deepseek DeepSeek-R1 API接口 javascript 前端 chrome edge ddos android 网络安全 web安全 数据库 centos oracle 关系型 安全 分布式 物联网 ubuntu redis YOLO conda pytorch vscode python yolov5 macos windows 机器学习 DNS gcc centos 7 网络 tcp/ip 网络协议 ip协议 c语言 RTSP xop RTP RTSPServer 推流 视频 vue.js spring boot nginx 进程 操作系统 进程控制 Ubuntu 英语 Ollama Qwen2.5-coder 离线部署 pycharm ide 游戏引擎 学习 ai nlp llama 算法 opencv 自然语言处理 语言模型 模型联网 API CherryStudio php 开发语言 Flask FastAPI Waitress Gunicorn uWSGI Uvicorn CH340 单片机 嵌入式硬件 串口驱动 CH341 uart 485 fstab 经验分享 阿里云 笔记 C 环境变量 进程地址空间 harmonyos 华为 django 自动化 ssh unix 数据库系统 mysql adb https kubernetes prometheus grafana 云原生 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 计算机外设 bug MCP typescript 计算机网络 dify 知识库 本地化部署 react.js 前端面试题 node.js 持续部署 UEFI Legacy MBR GPT U盘安装操作系统 数据分析 后端 MacMini Mac 迷你主机 mini Apple 开发环境 arm开发 架构 网络结构图 ipython apache oracle fusion oracle中间件 交互 ip spring cloud compose c# 智能路由器 dell服务器 Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 AIGC 人工智能生成内容 Dify audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 jvm 虚拟机 部署 学习方法 程序 编程 内存 性能分析 开源 milvus ROS 自动驾驶 agi 科技 个人开发 负载均衡 tomcat qt arm 机器人 asm virtualenv 策略模式 c++ mcu svn fastapi mcp mcp-proxy mcp-inspector fastapi-mcp agent sse jenkins gitee 教程 环境搭建 Java Maven flutter Google pay Apple pay ui C语言 vnc Linux xrdp 远程桌面 远程连接 eureka burpsuite 安全工具 mac安全工具 burp安装教程 渗透工具 debian PVE maxkb ARG 面试 live555 rtsp rtp AI-native gpt flask 小程序 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 YOLOv8 NPU Atlas800 A300I pro asi_bench 大数据 spark hive ffmpeg 音视频 视频编解码 pip jellyfin nas deepseek-r1 大模型本地部署 http 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 websocket ACL 流量控制 基本ACL 规则配置 并查集 leetcode intellij-idea udp 本地环回 bind kafka AI大模型 大模型技术 本地部署大模型 ubuntu 18.04 安装教程 grub 版本升级 扩容 vue3 excel 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 鸿蒙 快捷键 旋转屏幕 自动操作 USB转串口 VMware springsecurity6 oauth2 授权服务器 前后端分离 虚拟现实 outlook 错误代码2603 无网络连接 2603 Alexnet centos-root /dev/mapper yum clean all df -h / du -sh 实时音视频 rpc cron crontab日志 ArkUI 鸿蒙系统 ArkTS gnu 目标跟踪 目标检测 OpenVINO 推理应用 外网访问 内网穿透 端口映射 sql 远程工作 devops elasticsearch 性能优化 word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 rust腐蚀 git npm 高级IO epoll ubuntu20.04 ros ros1 Noetic 20.04 apt 安装 github 实时互动 firewall kylin ollama llm DeepSeek Chatbox 程序人生 json Doris搭建 docker搭建Doris Doris搭建过程 linux搭建Doris Doris搭建详细步骤 Doris部署 孤岛惊魂4 LDAP adobe rag ragflow 大模型部署 AutoDL IMM openvpn server openvpn配置教程 centos安装openvpn 运维开发 spring sublime text 编辑器 桥接模式 vmware windows虚拟机 虚拟机联网 bash rsyslog stm32 IM即时通讯 QQ 微信 企业微信 剪切板对通 HTML FORMAT vim 缓存 HarmonyOS Next visualstudio AI编程 frp Hyper-V WinRM TrustedHosts 多线程 ip命令 新增网卡 新增IP 启动网卡 C++ ansible QT 5.12.12 QT开发环境 Ubuntu18.04 腾讯云 集成学习 集成测试 pygame 进程优先级 调度队列 进程切换 系统安全 matplotlib fonts-noto-cjk protobuf 序列化和反序列化 安装 华为认证 交换机 环境迁移 yum docker-compose docker compose AI cpu 实时 使用 pillow Linux 维护模式 自定义客户端 SAS 程序员创富 list 数据结构 opengl 华为云 华为od 智能体开发 Ubuntu20.04 GLIBC 2.35 虚拟显示器 远程控制 C# MQTTS 双向认证 emqx JDK LInux Windows python2 ubuntu24.04 uni-app 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 前端框架 burp suite 抓包 axure 富文本编辑器 ssl cursor transformer 数据挖掘 电子信息 通信工程 信息与通信 毕业 VMware Tools vmware tools安装 vmwaretools安装步骤 vmwaretools安装失败 vmware tool安装步骤 vm tools安装步骤 vm tools安装后不能拖 vmware tools安装步骤 多线程服务器 Linux网络编程 FTP 服务器 Linux PID k8s 媒体 安卓模拟器 mac 监控k8s集群 集群内prometheus rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK dubbo TCP WebServer mysql离线安装 ubuntu22.04 mysql8.0 聚类 mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 移动端开发 课程设计 nvm golang 统信 NFS maven IIS .net core Hosting Bundle .NET Framework vs2022 1024程序员节 postman 测试工具 开机黑屏 链表 压力测试 大模型压力测试 EvalScope JAVA WSL2 IP 地址 命令 ssh漏洞 ssh9.9p2 CVE-2025-23419 vite 私有化 本地部署 嵌入式 iftop 网络流量监控 stm32项目 zotero WebDAV 同步失败 代理模式 反向代理 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 xcode filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 perf react next.js 部署next.js windows 服务器安装 ue4 着色器 ue5 虚幻 EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 图像处理 思科 硬件 设备 GPU PCI-Express GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 匿名管道 命名管道 notepad elk jupyter asp.net大文件上传 asp.net大文件上传下载 asp.net大文件上传源码 ASP.NET断点续传 微信小程序 miniapp 真机调试 调试 debug 断点 网络API请求调试方法 进程信号 读写锁 gitlab ruoyi jar hadoop visual studio code samba wsl MQTT 消息队列 安卓 离线部署dify numpy intellij idea 微服务 pdf 智慧农业 开源鸿蒙 团队开发 灵办AI 源码剖析 rtsp实现步骤 流媒体开发 gpu算力 postgresql 电脑 软件需求 n8n 工作流 ci/cd 代码规范 big data web3.py IPMI 远程过程调用 Windows环境 qps 高并发 webdav 宝塔面板无法访问 vr linux驱动开发 串口服务器 大模型入门 大模型教程 Kali Linux 黑客 渗透测试 信息收集 Docker Desktop ecmascript nextjs reactjs tcp eclipse 热榜 具身智能 Isaac Sim 虚拟仿真 指令 一切皆文件 多层架构 解耦 WSL2 上安装 Ubuntu SSH Xterminal 防火墙 端口号 开放端口 访问列表 程序员 GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 Ubuntu22.04 虚拟化 开发人员主页 flash-attention 报错 压测 ECS 微信分享 Image wxopensdk ESP32 camera Arduino redhat 雨云 NPS 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 vu大文件秒传跨域报错cors Mac内存不够用怎么办 chatgpt oneapi 大模型微调 虚拟局域网 odoo 服务器动作 Server action WebUI DeepSeek V3 vm 创意 社区 db VMware创建虚拟机 卡死 rabbitmq edge浏览器 onlyoffice 游戏程序 ios 爬虫 网络爬虫 efficientVIT YOLOv8替换主干网络 TOLOv8 Qualcomm WoS Python QNN AppBuilder Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 shell Agent LLM CrewAI microsoft 冯诺依曼体系 fork wait waitpid exit 回显服务器 UDP的API使用 驱动开发 remote-ssh 工具 无人机 PX4 RustDesk自建服务器 rustdesk服务器 docker rustdesk React Next.js 开源框架 网络药理学 生信 生物信息学 gromacs 分子动力学模拟 MD 动力学模拟 升级 CVE-2024-7347 漏洞 selenium HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 VMware安装mocOS macOS系统安装 KylinV10 麒麟操作系统 Vmware database kotlin android studio iphone EtherCAT转Modbus ECT转Modbus协议 EtherCAT转485网关 ECT转Modbus串口网关 EtherCAT转485协议 ECT转Modbus网关 HP Anyware WLAN oceanbase 传统数据库升级 银行 rancher jdk webrtc grep open webui Cursor Dell R750XS Mermaid 可视化图表 自动化生成 langchain deep learning 产品经理 linux环境变量 VSCode iot linux内核 话题通信 服务通信 termux 服务器配置 温湿度数据上传到服务器 Arduino HTTP unity Trae IDE AI 原生集成开发环境 Trae AI aws 职场和发展 硬件工程 嵌入式实习 nftables 5G 硅基流动 ChatBox Kylin-Server 国产操作系统 服务器安装 Cline lvm 磁盘挂载 磁盘分区 进程程序替换 execl函数 execv函数 execvp函数 execvpe函数 putenv函数 镜像下载 freebsd VMware安装Ubuntu Ubuntu安装k8s Ubuntu 22.04 MySql 算家云 算力租赁 rc.local 开机自启 systemd 麒麟 express p2p scikit-learn SRS 流媒体 直播 Hive环境搭建 hive3环境 Hive远程模式 rdp 远程服务 政务 分布式系统 监控运维 Prometheus Grafana 实验 ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 matlab powerpoint 常用命令 文本命令 目录命令 DevEco Studio 7z kernel 银河麒麟 深度求索 私域 CPU 使用率 系统监控工具 linux 命令 OpenCore 主从复制 IIS服务器 IIS性能 日志监控 localhost 毕设 区块链 行情服务器 股票交易 速度慢 切换 股票量化接口 股票API接口 Linux Vim openEuler 教育电商 高效I/O FTP服务器 clickhouse Invalid Host allowedHosts vue 软件工程 软件构建 rust MobaXterm 文件传输 java-ee 云电竞 云电脑 todesk 安全威胁分析 沙盒 需求分析 提示词 zip unzip cuda html MCP server C/S 互信 kali 共享文件夹 vSphere vCenter 软件定义数据中心 sddc nohup 异步执行 浪潮信息 AI服务器 CPU 主板 电源 网卡 linuxdeployqt 打包部署程序 appimagetool 3d 数学建模 硬件架构 系统架构 计算生物学 生物信息 基因组 etcd 数据安全 RBAC 大语言模型 LLMs jmeter 软件测试 HCIE 数通 ocr MacOS录屏软件 云原生开发 K8S k8s管理系统 gateway Clion Nova ResharperC++引擎 Centos7 远程开发 国产数据库 瀚高数据库 数据迁移 下载安装 zabbix mariadb .netcore 计算机科学与技术 rocketmq NVML nvidia-smi Maxkb RAG技术 本地知识库 阻塞队列 生产者消费者模型 服务器崩坏原因 TrueLicense 网易邮箱大师 CLion ubuntu24.04.1 springboot 生活 kylin v10 麒麟 v10 HiCar CarLife+ CarPlay QT RK3588 IO 鲲鹏 昇腾 npu ROS2 photoshop 键盘 firewalld 华为OD 华为OD机试真题 可以组成网络的服务器 隐藏文件 隐藏目录 文件系统 管理器 通配符 linux安装配置 视频监控 网站 ollama下载加速 大模型应用 低代码 tar ollama api ollama外网访问 ux sysctl.conf vm.nr_hugepages 代码调试 ipdb 智能手机 设置代理 实用教程 单一职责原则 mybatis SSH 密钥生成 SSH 公钥 私钥 生成 网络编程 中兴光猫 换光猫 网络桥接 自己换光猫 vasp安装 嵌入式系统开发 计算机系统 kubeless string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap 中间件 iis 移动云 python3.11 fpga开发 MQTT协议 消息服务器 代码 磁盘清理 iperf3 带宽测试 UOS 开机自启动 桌面快捷方式 stable diffusion 跨平台 权限 k8s部署 MySQL8.0 高可用集群(1主2从) sqlite3 物联网开发 springcloud 其他 DevOps 软件交付 数据驱动 应用场景 retry 重试机制 fd 文件描述符 蓝耘科技 元生代平台工作流 ComfyUI 远程看看 远程协助 飞牛nas fnos EMQX 通信协议 x64 SIGSEGV SSE xmm0 浏览器自动化 zookeeper kind Github加速 Mac上Github加速 Chrome浏览器插件 搜索引擎 VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 visual studio Ardupilot 计算机 mq DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 sqlserver r语言 DrissionPage 相机 sublime text3 软链接 硬链接 AD域 Linux的权限 CentOS ssrf 失效的访问控制 ArkTs openssl Office html5 css less 正则表达式 信号处理 tcpdump ShapeFile GeoJSON Nginx 镜像源 Docker Hub docker pull daemon.json 宠物 毕业设计 免费学习 宠物领养 宠物平台 webstorm 视觉检测 MS Materials GameFramework HybridCLR Unity编辑器扩展 自动化工具 数据集 log4j 直播推流 机柜 1U 2U Windsurf WebVM CosyVoice .net netty minicom 串口调试工具 go 小智 强制清理 强制删除 mac废纸篓 华为证书 HarmonyOS认证 华为证书考试 知识图谱 智能体 autogen openai coze 数据采集 Crawlee Playwright c MAVROS 四旋翼无人机 AI写作 数据可视化 推荐算法 状态管理的 UDP 服务器 Arduino RTOS xml WSL win11 无法解析服务器的名称或地址 su sudo chatbox 考试 gitea armbian u-boot Trae叒更新了? gradle 客户端与服务端通信 加解密 Yakit yaklang ftp 云耀服务器 css3 k8s集群资源管理 统信UOS bonding 链路聚合 RAGFLOW nohup后台启动 双系统 执法记录仪 智能安全帽 smarteye docker run 数据卷挂载 交互模式 vscode1.86 1.86版本 ssh远程连接 远程 执行 sshpass 操作 自动化编程 统信操作系统 openwrt LLM Web APP Streamlit 京东云 vmamba Docker Docker Compose Kubernetes GCC aarch64 编译安装 HPC 显示器 curl wget arkUI llama3 Chatglm 开源大模型 iBMC UltraISO ros2 moveit 机器人运动 windows日志 es6 qt6.3 g726 深度优先 图论 并集查找 换根法 树上倍增 java-zookeeper Ubuntu共享文件夹 共享目录 Linux共享文件夹 信号 内核 监控k8s 监控kubernetes 笔灵AI AI工具 免密 登录 公钥 私钥 HistoryServer Spark YARN jobhistory 系统开发 binder 车载系统 framework 源码环境 域名服务 DHCP 符号链接 配置 ECT转485串口服务器 ECT转Modbus485协议 ECT转Modbus串口服务器 wireshark 显示过滤器 ICMP Wireshark安装 copilot ebpf uprobe 安装MySQL 全文检索 图搜索算法 export import save load 迁移镜像 rime 磁盘监控 内网渗透 靶机渗透 单例模式 券商 股票交易接口api 类型 特点 自动化运维 流水线 脚本式流水线 多产物 ufw yolov8 dash playbook 剧本 file server http server web server 系统 黑苹果 hosts hosts文件管理工具 开源软件 v10 软件 alias unalias 别名 RAID RAID技术 磁盘 存储 TRAE KingBase 博客 3GPP 卫星通信 nac 802.1 portal systemctl composer 负载测试 桌面环境 僵尸进程 网卡的名称修改 eth0 ens33 selete 源代码管理 框架搭建 webgl diskgenius 换源 国内源 Debian NAS Termux Samba Xinference 环境配置 mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 Redis Desktop 迁移指南 SWAT 配置文件 服务管理 网络共享 linux上传下载 arcgis 腾讯云大模型知识引擎 网络用户购物行为分析可视化平台 大数据毕业设计 基础环境 服务器扩容没有扩容成功 实战案例 Mac软件 macbook 底层实现 docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 shell脚本免交互 expect linux免交互 宝塔 deepseek-v3 ktransformers harmonyOS面试题 Linux权限 权限命令 特殊权限 拓扑图 sdkman 视频平台 录像 视频转发 性能测试 视频流 矩阵 大屏端 工业4.0 docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos 强化学习 工具分享 客户端 P2P HDLC autoware windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 openjdk AI作画 YOLOv12 junit 图文教程 VMware虚拟机 macOS系统安装教程 macOS最新版 虚拟机安装macOS Sequoia ESXi 弹性计算 云服务器 裸金属服务器 弹性裸金属服务器 gpt-3 mac设置host Webserver 异步 CNNs 图像分类 unity3d 新盘添加 partedUtil yaml Ultralytics 可视化 功能测试 自动化测试 c/c++ 串口 web CUPS 打印机 Qt5 nacos ping++ 豆瓣 追剧助手 迅雷 rustdesk Web服务器 多线程下载工具 PYTHON H3C dns googlecloud 模拟实现 arkTs 云服务 threejs 3D okhttp seatunnel top Linux top top命令详解 top命令重点 top常用参数 路径解析 ragflow 源码启动 监控 Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer Linux的基础指令 安防软件 openstack Xen KVM HarmonyOS Tabs组件 TabContent TabBar TabsController 导航页签栏 滚动导航栏 mongodb 个人博客 glibc cudnn anaconda 王者荣耀 pyside6 界面 Reactor 设计模式 firefox kamailio sip VoIP 大数据平台 DIFY 联网 easyconnect 代理 开发 高德地图 鸿蒙接入高德地图 HarmonyOS5.0 Cookie OpenManus 版本 RDP Carla 智能驾驶 云桌面 微软 AD域控 证书服务器 建站 iTerm2 海康 bcompare Beyond Compare 虚拟机安装 Node-Red 编程工具 流编程 mamba Pyppeteer nvidia URL VPS NLP模型 NLP pyqt 自学笔记 小米 澎湃OS Android 蓝桥杯 安全漏洞 信息安全 源代码 图片增强 增强数据 Linux find grep RTMP 应用层 OD机试真题 服务器能耗统计 SoC cpp-httplib opensearch helm 服务器主板 AI芯片 可信计算技术 安全架构 网络攻击模型 fast MI300x 大模型推理 WebRTC bat pppoe radius Claude Desktop Claude MCP Windows Cli MCP Kali workflow anythingllm open-webui docker国内镜像 显卡驱动持久化 GPU持久化 聊天服务器 套接字 Socket shard IPv4 子网掩码 公网IP 私有IP 智能音箱 智能家居 IPMITOOL BMC 硬件管理 opcua opcda KEPServer安装 Obsidian Dataview linux 命令 sed 命令 渗透 代理服务器 多端开发 智慧分发 应用生态 鸿蒙OS 游戏服务器 TrinityCore 魔兽世界 自动化任务管理 XCC Lenovo mysql安装报错 windows拒绝安装 命令行 基础入门 SVN Server tortoise svn asp.net上传文件夹 asp.net上传大文件 .net core断点续传 单元测试 群晖 文件分享 prompt easyui Qwen3 qwen3 32b vllm 繁忙 服务器繁忙 解决办法 替代网站 汇总推荐 AI推理 雨云服务器 密码学 token sas dba SSH 服务 SSH Server OpenSSH Server kvm 崖山数据库 YashanDB Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 ShenTong 国产化 服务器部署ai模型 pyicu SSL 域名 yum源切换 更换国内yum源 音乐服务器 Navidrome 音流 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 client-go k8s二次开发 wsl2 vsxsrv 线程 saltstack 服务器数据恢复 数据恢复 存储数据恢复 raid5数据恢复 磁盘阵列数据恢复 embedding GeneCards OMIM TTD 分子对接 autodock mgltools PDB PubChem echarts conda配置 conda镜像源 Docker快速入门 银河麒麟操作系统 图形化界面 nfs 稳定性 看门狗 健康医疗 Typore tensorflow trae crosstool-ng 三级等保 服务器审计日志备份 机架式服务器 1U工控机 国产工控机 卸载 列表 laravel Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 MAC SecureCRT DeepSeek r1 Open WebUI 内网环境 comfyui comfyui教程 bootstrap ELF加载 minio 免费域名 域名解析 vpn 支持向量机 黑客技术 流式接口 大版本升 升级Ubuntu系统 api 联想开天P90Z装win10 rtc 文件存储服务器组件 跨域 分析解读 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? 边缘计算 大文件秒传跨域报错cors 网工 充电桩平台 充电桩开源平台 办公自动化 pdf教程 交叉编译 数据库架构 数据管理 数据治理 数据编织 数据虚拟化 管道 软件开发 信任链 initramfs Linux内核 Grub 影刀 #影刀RPA# Linux环境 csrf zerotier Reactor反应堆 SSL证书 pyautogui 锁屏不生效 小艺 Pura X 免费 cmake cnn VGG网络 卷积层 池化层 能力提升 面试宝典 技术 IT信息化 thingsboard es qemu libvirt ftp服务 文件上传 gaussdb bot rpa 相差8小时 UTC 时间 日志分析 系统取证 LVM lvresize 磁盘扩容 pvcreate 动静态库 DenseNet IPv4/IPv6双栈 双栈技术 网路规划设计 ensp综合实验 IPv4过渡IPv6 IPv4与IPv6 can 线程池 电视剧收视率分析与可视化平台 cd 目录切换 文心一言 VR手套 数据手套 动捕手套 动捕数据手套 框架 RAGFlow Ark-TS语言 微信公众平台 智能电视 risc-v Wi-Fi 输入法 空间 查错 av1 电视盒子 机顶盒ROM 魔百盒刷机 apt 我的世界 我的世界联机 数码 EasyConnect cmos virtualbox glm4 resolv.conf DocFlow ubuntu24 vivado24 进程间通信 录音麦克风权限判断检测 录音功能 录音文件mp3播放 小程序实现录音及播放功能 RecorderManager 解决录音报错播放没声音问题 实时内核 keepalived env 变量 sonoma 自动更新 论文阅读 SysBench 基准测试 服务器时间 Echarts图表 折线图 柱状图 异步动态数据 鸿蒙开发 可视化效果 ecm bpm neo4j 数据仓库 数据库开发 重启 排查 系统重启 日志 原因 宕机切换 服务器宕机 xpath定位元素 HarmonyOS NEXT 原生鸿蒙 lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 联机 僵尸毁灭工程 游戏联机 开服 技能大赛 sudo原理 su切换 idm ArcTS ArcUI GridItem 服务网格 istio js 软件卸载 系统清理 cocos2d 3dcoat Minecraft DOIT 四博智联 esp32 蓝牙 chrome devtools chromedriver Helm k8s集群 宝塔面板 dity make Dell HPE 联想 浪潮 iDRAC R720xd powerbi 信息可视化 图形渲染 d3d12 终端 树莓派 VNC 实习 XFS xfs文件系统损坏 I_O error iNode Macos 测试用例 OpenHarmony 聊天室 pthread 本地部署AI大模型 iventoy VmWare OpenEuler searxng FunASR ASR ECS服务器 VPN wireguard 网络文件系统 蓝桥杯C++组 muduo 程序化交易 PTrade QMT 量化交易 量化股票 Zoertier 内网组网 小游戏 五子棋 X11 Xming 金仓数据库 2025 征文 数据库平替用金仓 飞牛 银河麒麟服务器操作系统 系统激活 postgres Dify重启后重新初始化 uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 wps scapy perl Spring Security AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 docker命令大全 AimRT CentOS Stream Linux的基础开发工具 xfce 显示管理器 lightdm gdm 源码 lio-sam SLAM jetty undertow 混合开发 环境安装 make命令 makefile文件 远程登录 telnet word lb 协议 web3 区块链项目 MNN Qwen NAT转发 NAT Server 创业创新 裸机装机 linux磁盘分区 裸机安装linux 裸机安装ubuntu 裸机安装kali 裸机 Bug解决 Qt platform OpenCV 考研 在线office 语音识别 ceph 玩游戏 wordpress DeepSeek行业应用 Heroku 网站部署 harmonyosnext chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 eNSP 网络规划 VLAN 企业网络 大模型面经 大模型学习 显卡驱动 AnythingLLM AnythingLLM安装 游戏机 Netty 即时通信 NIO MDK 嵌入式开发工具 论文笔记 llama.cpp IP配置 netplan archlinux kde plasma .net mvc断点续传 ajax 向日葵 netlink libnl3 beautifulsoup 金融 欧拉系统 micropython mqtt rclone AList fnOS 容器技术 k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm 思科模拟器 Cisco nuxt3 序列化反序列化 docker desktop 镜像 Ubuntu Server Ubuntu 22.04.5 算力 服务器管理 配置教程 网站管理 cpolar ruby cfssl 飞牛NAS 飞牛OS MacBook Pro 模拟退火算法 终端工具 远程工具 EVE-NG 邮件APP 免费软件 webpack 互联网医院 代码托管服务 llamafactory 微调 GRUB引导 Linux技巧 社交电子 电路仿真 multisim 硬件工程师 硬件工程师学习 电路图 电路分析 仪器仪表 etl Linux24.04 deepin 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 umeditor粘贴word ueditor粘贴word ueditor复制word ueditor上传word图片 ueditor导入word ueditor导入pdf ueditor导入ppt hibernate Xshell ssh远程登录 UDP safari UOS1070e 历史版本 下载 同步 备份 vscode 1.86 网站搭建 serv00 服务器部署 本地拉取打包 直流充电桩 充电桩 微信开放平台 微信公众号配置 W5500 OLED u8g2 TCP服务器 SEO uv chfs ubuntu 16.04 fpga banner deepseak 豆包 KIMI 腾讯元宝 GRE 网络穿透 火绒安全 Nuxt.js lighttpd安装 Ubuntu配置 Windows安装 服务器优化 多进程 7-zip AI代码编辑器 用户管理 支付 微信支付 开放平台 毕昇JDK 致远OA OA服务器 服务器磁盘扩容 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 rnn CORS Alist mount 挂载 网盘 uniapp 恒源云 用户缓冲区 jina mvc cocoapods Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 半虚拟化 硬件虚拟化 Hypervisor Web应用服务器 SenseVoice 大模型训练/推理 推理问题 mindie NVM Node Yarn PM2 飞腾处理器 弹性服务器 Kylin OS Vmamba 产测工具框架 IMX6ULL 管理框架 CDN pgpool 业界资讯 端口测试 田俊楠 Attention code-server mosquitto DBeaver skynet RAG 检索增强生成 文档解析 大模型垂直应用 AISphereButler miniconda wsgiref Web 服务器网关接口 网页设计 WINCC 配置原理 GPU训练 java-rocketmq Jellyfin Java Applet URL操作 服务器建立 Socket编程 网络文件读取 swift 医疗APP开发 app开发 LORA trea idea ukui 麒麟kylinos openeuler 服务器ssl异常解决 模拟器 h.264 image 网络建设与运维 项目部署到linux服务器 项目部署过程 超融合 软负载 open Euler dde EMUI 回退 降级 react native 内存管理 可执行程序 端口 查看 ss 嵌入式Linux IPC post.io 企业邮箱 搭建邮箱 docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 动态规划 MinIO NVIDIA WireGuard 异地组网 Python 视频爬取教程 Python 视频爬取 Python教程 Python 视频教程 OS Radius mcp服务器 client close 企业网络规划 华为eNSP 星河版 Anolis nginx安装 linux插件下载 钉钉 IPv6 IPv6测试 IPv6测速 IPv6检测 IPv6查询 僵尸世界大战 游戏服务器搭建 AzureDataStudio 抓包工具 ai工具 阿里云ECS ldap yashandb 无桌面 语法 GIS 遥感 WebGIS linux子系统 忘记密码 paddle win向maOS迁移数据 qt5 客户端开发 android-studio 架构与原理 动态库 GCC编译器 -fPIC -shared 查看显卡进程 fuser servlet ArtTS 多个客户端访问 IO多路复用 TCP相关API IO模型 deekseek MacOS IDEA qtcreator gunicorn autodl triton 模型分析 子系统 机械臂 logstash ranger MySQL8.0 vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 g++ g++13 docker部署Python Unity Dedicated Server Host Client 无头主机 yum换源 NFC 近场通讯 智能门锁 百度云 矩池云 数据下载 数据传输 运维监控 搭建个人相关服务器 figma mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 midjourney 红黑树封装map和set 华为机试 SystemV 信创 信创终端 中科方德 商用密码产品体系 sentinel 佛山戴尔服务器维修 佛山三水服务器维修 swoole 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 c/s web开发 增强现实 沉浸式体验 技术实现 案例分析 AR 可用性测试 GoogLeNet 干货分享 黑客工具 密码爆破 匿名FTP 邮件传输代理 SSL支持 chroot监狱技术 技术共享 虚幻引擎 我的世界服务器搭建 跨域请求 MVS 海康威视相机 问题解决 Putty 花生壳 OpenSSH hdc 脚本 Sealos tailscale derp derper 中转 线性代数 电商平台 massa sui aptos sei xshell termius iterm2 流量运营 C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 fiddler gitee go 欧标 OCPP lua csrutil mac恢复模式进入方法 SIP 恢复模式 计算机学习路线 编程语言选择 烟花代码 烟花 元旦 Ubuntu 24.04.1 轻量级服务器 网络搭建 神州数码 神州数码云平台 云平台 极限编程 音乐库 合成模型 扩散模型 图像生成 mybase 移动开发 PPI String Cytoscape CytoHubba 金仓数据库概述 金仓数据库的产品优化提案 抗锯齿 捆绑 链接 谷歌浏览器 youtube google gmail #STC8 #STM32 prometheus数据采集 prometheus数据模型 prometheus特点 Qwen2.5-VL aac whistle regedit 开机启动 nosql chromium dpi ISO镜像作为本地源 游戏开发 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 设备树 Erlang OTP gen_server 热代码交换 事务语义 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 dock 加速 Python基础 Python技巧 proxy模式 性能调优 安全代理 本地知识库部署 DeepSeek R1 模型 xss PyQt PySide6 Claude win服务器架设 windows server minecraft hugo Apache Beam 批流统一 案例展示 数据分区 容错机制 网页服务器 web服务器 玩机技巧 软件分享 软件图标 HTTP 服务器控制 ESP32 DeepSeek AI员工 dns是什么 如何设置电脑dns dns应该如何设置 银河麒麟桌面操作系统 查询数据库服务IP地址 SQL Server 分布式训练 AI agent 软考设计师 中级设计师 SQL 软件设计师 多路转接 授时服务 北斗授时 Bandizip Mac解压 Mac压缩 压缩菜单 智能合约 哈希算法 移动魔百盒 qt项目 qt项目实战 qt教程 国标28181 监控接入 语音广播 流程 SDP docker search 状态模式 solidworks安装 线程同步 线程互斥 条件变量 finebi USB网络共享 对比 meld DiffMerge 银河麒麟高级服务器 外接硬盘 Kylin VS Code 根服务器 deepseek r1 浏览器开发 AI浏览器 项目部署 小智AI服务端 xiaozhi TTS element-ui 上传视频并预览视频 vue上传本地视频及进度条功能 vue2选择视频上传到服务器 upload上传视频组件插件 批量上传视频 限制单个上传视频 AD 域管理 ubuntu安装 linux入门小白 电脑桌面出现linux图标 电脑桌面linux图标删除不了 电脑桌面Liunx图标删不掉 linux图标删不掉 hexo System V共享内存 进程通信 rsync 内网服务器 内网代理 内网通信 规格说明书 备选 调用 示例 bigdata 网络原理 飞书 deployment daemonset statefulset cronjob finalsheel node brew kerberos navicat Charles 打不开xxx软件 无法检查其是否包含恶意软件 高频交易 wpf sqlite 集群管理 seleium IMX317 MIPI H265 VCU TCP协议 Headless Linux 三次握手 达梦 DM8 pycharm安装 OpenManage Logstash 日志采集 端口聚合 windows11 flink 概率论 接口返回 分布式账本 共识算法 做raid 装系统 材料工程 pxe watchtower 自定义登录信息展示 motd 美化登录 网络库 AI Agent 字节智能运维 接口优化 物理地址 页表 虚拟地址 北亚数据恢复 oracle数据恢复 西门子PLC 通讯 Qt QModbus 流程图 mermaid 软件商店 livecd systemtools 元服务 应用上架 静态IP 解决方案 STL 李心怡 accept Masshunter 质谱采集分析软件 使用教程 科研软件 风扇控制软件 工厂方法模式 tidb pythonai PlaywrightMCP 数字证书 签署证书 火山引擎 solr 知行EDI 电子数据交换 知行之桥 EDI 设计规范 RK3568 EMC存储 NetApp存储 数据库管理 带外管理 云计算面试题 caddy qwen2vl gru 服务器正确解析请求体 UFW 零售 Unity插件 sequoiaDB 粘包问题 计算虚拟化 弹性裸金属 鸿蒙NEXT 华为鸿蒙系统 ArkTS语言 Component 生命周期 条件渲染 Image图片组件 macOS 向量数据库 安装部署 milvus安装 输入系统 GRANT REVOKE 进程池实现 HAProxy VM虚拟机 easyTier 组网 实时云渲染 云渲染 3D推流 pyscenic 生信教程 mujoco 搜狗输入法 中文输入法 lrzsz Metastore Catalog ABAP authing 大大通 第三代半导体 碳化硅 mcp协议 go-zero 存储维护 ardunio BLE homeassistant Async注解 AWS AI提示词优化 web环境 华为昇腾910b3 命令模式 智能问答 Spring AI Milvus vCenter服务器 ESXi主机 监控与管理 故障排除 日志记录 事件驱动 模板 泛型编程 notepad++ ubantu mapreduce 定义 核心特点 优缺点 适用场景 漏洞报告生成 webview bert Multi-Agent PostgreSQL15数据库 CAD瓦片化 栅格瓦片 矢量瓦片 Web可视化 DWG解析 金字塔模型 顽固图标 启动台 SPI 轮播图 OpenGL java毕业设计 微信小程序医院预约挂号 医院预约 医院预约挂号 小程序挂号 文件共享 Modbus TCP 能源 环境 非root 医院门诊管理系统 导航栏 devmem openvino 学习路线 手机 蜂窝网络 频率复用 射频单元 无线协议接口RAN 主同步信号PSS 代码复审 制造 容器化 Serverless vmware tools 烟雾检测 yolo检测 消防检测 Ubuntu 24.04 搜狗输入法闪屏 Ubuntu中文输入法 4 - 分布式通信、分布式张量 LSTM access blocked 破解 数字比特流 模拟信号 将二进制数据映射到模拟波形上 频谱资源 振幅频率相位 载波高频正弦波 MLLMs VLM gpt-4v