关注

Linux 学习必杀技:从菜鸟到高手的蜕变密码

踏入 Linux 奇幻世界,借 C++ 利刃,解锁文件、进程、网络等核心编程奥秘。附实用学习法与精选好书,助你从菜鸟一跃成 Linux 高手  。

一、本篇介绍:

在当今的技术领域,Linux 操作系统以其开源、稳定、高效等特性,占据着至关重要的地位。无论是服务器领域、嵌入式系统,还是云计算、大数据等新兴技术,Linux 都发挥着核心作用。对于初学者来说,掌握 Linux 系统不仅能拓宽职业道路,还能深入理解计算机系统的底层原理。本文将为你揭示从 Linux 菜鸟到高手的蜕变密码,同时结合 C++ 代码辅助讲解,帮助你更好地理解和掌握 Linux 编程,此外还会介绍实用的学习方法以及推荐相关的优质书籍。

二、Linux 基础环境搭建:

2.1 选择合适的 Linux 发行版:

常见的 Linux 发行版有 Ubuntu、CentOS、Debian 等。对于初学者来说,Ubuntu 是一个不错的选择,它具有友好的图形界面和丰富的软件源。你可以从 Ubuntu 官方网站下载最新版本的镜像文件,然后通过虚拟机软件(如 VMware 或 VirtualBox)安装到本地计算机上。

2.2 安装开发工具:

在 Linux 系统中,我们需要安装一些基本的开发工具,如 GCC 编译器、GDB 调试器等。以 Ubuntu 为例,可以使用以下命令进行安装:

sudo apt-get update
sudo apt-get install build-essential gdb

build-essential 包包含了 GCC 编译器、G++ 编译器、make 工具等常用的开发工具,gdb 则是一个强大的调试器。

2.3 编写第一个 C++ 程序:

在 Linux 系统中,我们可以使用任何文本编辑器(如 Vim、Emacs 或 VS Code)来编写 C++ 代码:

将上述代码保存为 hello.cpp,然后使用 GCC 编译器进行编译:

g++ hello.cpp -o hello

其中,g++ 是 C++ 编译器,hello.cpp 是源文件,-o hello 表示将编译后的可执行文件命名为 hello。编译成功后,运行可执行文件:

./hello

如果一切正常,你将看到输出结果:Hello, Linux!

三、Linux 文件系统与操作:

3.1 文件系统结构:

Linux 文件系统采用树形结构,根目录用 / 表示。常见的目录有 /bin(存放系统命令)、/etc(存放系统配置文件)、/home(存放用户主目录)、/var(存放可变数据文件)等。了解文件系统结构对于在 Linux 系统中进行文件操作非常重要。

3.2 文件和目录操作命令:

在 Linux 系统中,我们可以使用各种命令来进行文件和目录操作。以下是一些常用的命令示例:

创建目录:使用 mkdir 命令创建目录,例如:

mkdir mydir

切换目录:使用 cd 命令切换目录,例如:

cd mydir

查看目录内容:使用 ls 命令查看目录内容,例如:

ls

创建文件:使用 touch 命令创建文件,例如:

touch myfile.txt

复制文件:使用 cp 命令复制文件,例如:

cp myfile.txt myfile_copy.txt

移动文件:使用 mv 命令移动文件,例如:

mv myfile.txt /tmp

删除文件和目录:使用 rm 命令删除文件和目录,例如:

rm myfile_copy.txt
rm -r mydir

其中,-r 选项表示递归删除目录及其子目录。

3.3 使用 C++ 进行文件操作:

在 C++ 中,我们可以使用 <fstream> 头文件来进行文件操作。以下是一个简单的示例,演示如何创建一个文件并写入内容:

#include <iostream>
#include <fstream>

int main() {
    std::ofstream outfile("test.txt");
    if (outfile.is_open()) {
        outfile << "This is a test file." << std::endl;
        outfile.close();
        std::cout << "File written successfully." << std::endl;
    } else {
        std::cerr << "Unable to open file." << std::endl;
    }
    return 0;
}

上述代码中,std::ofstream 用于创建一个输出文件流对象,is_open() 方法用于检查文件是否成功打开,close() 方法用于关闭文件流。

四、Linux 进程管理:

4.1 进程的概念:

在 Linux 系统中,进程是程序在操作系统中的一次执行实例。每个进程都有自己独立的内存空间、程序计数器、寄存器等。进程可以分为前台进程和后台进程,前台进程会占用终端,直到执行完毕;后台进程则在后台运行,不会影响终端的使用。

4.2 进程操作命令:

在 Linux 系统中,我们可以使用各种命令来进行进程操作。以下是一些常用的命令示例:

查看进程列表:使用 ps 命令查看当前运行的进程列表,例如:

ps aux

其中,a 选项表示显示所有用户的进程,u 选项表示以详细格式显示进程信息,x 选项表示显示没有控制终端的进程。

终止进程:使用 kill 命令终止指定的进程,例如:

kill -9 1234

其中,-9 表示使用强制终止信号(SIGKILL),1234 是进程的 PID(进程标识符)。

启动后台进程:在命令后面加上 & 符号可以将进程放到后台运行,例如:

./myprogram &

4.3 使用 C++ 创建和管理进程:

在 C++ 中,我们可以使用 fork() 和 exec() 系列函数来创建和管理进程。以下是一个简单的示例,演示如何创建一个子进程并执行另一个程序:

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        std::cerr << "Fork failed." << std::endl;
        return 1;
    } else if (pid == 0) {
        // 子进程
        execlp("ls", "ls", "-l", nullptr);
        std::cerr << "Exec failed." << std::endl;
        return 1;
    } else {
        // 父进程
        wait(nullptr);
        std::cout << "Child process finished." << std::endl;
    }
    return 0;
}

上述代码中,fork() 函数用于创建一个子进程,execlp() 函数用于在子进程中执行另一个程序,wait() 函数用于等待子进程结束。

五、Linux 网络编程:

5.1 网络编程基础:

在 Linux 系统中,网络编程主要基于套接字(Socket)进行。套接字是一种网络编程接口,它允许不同的进程在网络上进行通信。套接字可以分为 TCP 套接字和 UDP 套接字,TCP 套接字提供可靠的、面向连接的通信,UDP 套接字提供不可靠的、无连接的通信。

5.2 TCP 网络编程示例:

下面是一个简单的 TCP 服务器和客户端程序示例:

TCP 服务器代码

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8888

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};
    const char *hello = "Hello from server";

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        std::cerr << "Socket creation error" << std::endl;
        return -1;
    }

    // 设置套接字选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        std::cerr << "Setsockopt" << std::endl;
        return -1;
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定套接字到指定地址和端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        std::cerr << "Bind failed" << std::endl;
        return -1;
    }
    // 监听连接
    if (listen(server_fd, 3) < 0) {
        std::cerr << "Listen" << std::endl;
        return -1;
    }
    // 接受连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        std::cerr << "Accept" << std::endl;
        return -1;
    }
    // 读取客户端发送的数据
    int valread = read(new_socket, buffer, 1024);
    std::cout << buffer << std::endl;
    // 发送响应数据给客户端
    send(new_socket, hello, strlen(hello), 0);
    std::cout << "Hello message sent" << std::endl;
    // 关闭套接字
    close(new_socket);
    close(server_fd);
    return 0;
}

TCP 客户端代码

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8888

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char *hello = "Hello from client";
    char buffer[1024] = {0};

    // 创建套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "\n Socket creation error \n" << std::endl;
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将 IPv4 地址从点分十进制转换为二进制形式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "\nInvalid address/ Address not supported \n" << std::endl;
        return -1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "\nConnection Failed \n" << std::endl;
        return -1;
    }
    // 发送数据给服务器
    send(sock, hello, strlen(hello), 0);
    std::cout << "Hello message sent" << std::endl;
    // 读取服务器响应数据
    int valread = read(sock, buffer, 1024);
    std::cout << buffer << std::endl;
    // 关闭套接字
    close(sock);
    return 0;
}

5.3 UDP 网络编程示例:

UDP 网络编程与 TCP 网络编程类似,但不需要建立连接。以下是一个简单的 UDP 服务器和客户端程序示例:

UDP 服务器代码

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8888

int main() {
    int sockfd;
    struct sockaddr_in servaddr, cliaddr;
    char buffer[1024];
    socklen_t len;

    // 创建套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        std::cerr << "Socket creation failed" << std::endl;
        return -1;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    // 填充服务器地址信息
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定套接字到指定地址和端口
    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        std::cerr << "Bind failed" << std::endl;
        return -1;
    }

    len = sizeof(cliaddr);

    // 接收客户端数据
    int n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&cliaddr, &len);
    buffer[n] = '\0';
    std::cout << "Client: " << buffer << std::endl;

    // 发送响应数据给客户端
    const char *hello = "Hello from server";
    sendto(sockfd, (const char *)hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *)&cliaddr, len);
    std::cout << "Hello message sent." << std::endl;

    close(sockfd);
    return 0;
}

UDP 客户端代码

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8888

int main() {
    int sockfd;
    struct sockaddr_in servaddr;
    char buffer[1024];

    // 创建套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        std::cerr << "Socket creation failed" << std::endl;
        return -1;
    }

    memset(&servaddr, 0, sizeof(servaddr));

    // 填充服务器地址信息
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = INADDR_ANY;

    const char *hello = "Hello from client";
    int n, len;

    // 发送数据给服务器
    sendto(sockfd, (const char *)hello, strlen(hello), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
    std::cout << "Hello message sent." << std::endl;

    // 接收服务器响应数据
    n = recvfrom(sockfd, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *)&servaddr, (socklen_t*)&len);
    buffer[n] = '\0';
    std::cout << "Server: " << buffer << std::endl;

    close(sockfd);
    return 0;
}

六、Linux 系统编程进阶:

6.1 信号处理:

在 Linux 系统中,信号是一种软件中断机制,用于通知进程发生了某种特定的事件。例如,当用户按下 Ctrl+C 组合键时,会向当前进程发送 SIGINT 信号,通知进程终止。在 C++ 中,我们可以使用 signal() 函数来处理信号。以下是一个简单的示例:

#include <iostream>
#include <signal.h>
#include <unistd.h>

void signal_handler(int signum) {
    std::cout << "Received signal: " << signum << std::endl;
    // 可以在这里进行一些清理操作
    exit(signum);
}

int main() {
    // 注册信号处理函数
    signal(SIGINT, signal_handler);

    while (true) {
        std::cout << "Running..." << std::endl;
        sleep(1);
    }

    return 0;
}

上述代码中,signal() 函数用于注册信号处理函数,当接收到 SIGINT 信号时,会调用 signal_handler() 函数进行处理。

6.2 线程编程:

在 Linux 系统中,线程是轻量级的进程,多个线程可以共享同一个进程的资源。在 C++ 中,我们可以使用 <thread> 头文件来进行线程编程。

#include <iostream>
#include <thread>

void thread_function() {
    std::cout << "Thread is running." << std::endl;
}

int main() {
    // 创建一个线程
    std::thread t(thread_function);

    // 等待线程结束
    t.join();

    std::cout << "Main thread is exiting." << std::endl;
    return 0;
}

6.3 共享内存编程:

共享内存是一种高效的进程间通信(IPC)方式,多个进程可以直接访问同一块物理内存区域,从而避免了数据的复制,提高了数据传输的效率。在 Linux 系统中,我们可以使用 shmget()shmat()shmdt() 和 shmctl() 等系统调用进行共享内存的操作。

以下是一个简单的 C++ 示例,展示了如何使用共享内存进行两个进程之间的数据传递:

写入共享内存的程序:

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>

#define SHM_SIZE 1024

int main() {
    key_t key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    char* shmaddr = static_cast<char*>(shmat(shmid, nullptr, 0));
    if (shmaddr == reinterpret_cast<char*>(-1)) {
        perror("shmat");
        return 1;
    }

    const char* message = "Hello, shared memory!";
    std::strcpy(shmaddr, message);

    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        return 1;
    }

    return 0;
}

读取共享内存的程序

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>

#define SHM_SIZE 1024

int main() {
    key_t key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    int shmid = shmget(key, SHM_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    char* shmaddr = static_cast<char*>(shmat(shmid, nullptr, 0));
    if (shmaddr == reinterpret_cast<char*>(-1)) {
        perror("shmat");
        return 1;
    }

    std::cout << "Read from shared memory: " << shmaddr << std::endl;

    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        return 1;
    }

    if (shmctl(shmid, IPC_RMID, nullptr) == -1) {
        perror("shmctl");
        return 1;
    }

    return 0;
}

七、Linux 内存管理:

7.1 内存管理基础概念:

在 Linux 系统中,内存管理是操作系统的核心功能之一。它负责为进程分配和回收内存空间,同时还要处理内存的交换和分页等操作。虚拟内存是 Linux 内存管理的重要概念,它允许进程使用比物理内存更大的地址空间。每个进程都有自己独立的虚拟地址空间,操作系统通过页表将虚拟地址映射到物理地址。

7.2 内存分配和释放:

在 C++ 中,我们可以使用 new 和 delete 运算符来进行动态内存分配和释放,也可以使用 C 语言的 malloc()calloc()realloc() 和 free() 函数。以下是一个简单的示例:

#include <iostream>

int main() {
    // 使用 new 分配内存
    int* ptr = new int;
    *ptr = 10;
    std::cout << "Value: " << *ptr << std::endl;

    // 释放内存
    delete ptr;

    // 使用 malloc 分配内存
    int* ptr2 = (int*)malloc(sizeof(int));
    if (ptr2 != nullptr) {
        *ptr2 = 20;
        std::cout << "Value from malloc: " << *ptr2 << std::endl;
        // 释放内存
        free(ptr2);
    }

    return 0;
}

7.3 内存泄漏检测:

内存泄漏是指程序在运行过程中,动态分配的内存没有被正确释放,导致内存占用不断增加。在 Linux 中,可以使用一些工具来检测内存泄漏,如 valgrind。以下是使用 valgrind 检测内存泄漏的示例:
首先,编写一个存在内存泄漏的程序:

编译该程序:

g++ -g -o leaky_program leaky_program.cpp

使用 valgrind 进行检测:

valgrind --leak-check=full ./leaky_program

valgrind 会输出详细的内存泄漏信息,帮助我们定位问题。

八、Linux 性能优化:

8.1 性能分析工具:

Linux 提供了许多性能分析工具,如 tophtopvmstatiostat 等。top 命令可以实时显示系统中各个进程的资源使用情况,包括 CPU 使用率、内存使用率等。htop 是 top 的增强版,提供了更直观的界面。vmstat 可以显示系统的虚拟内存、进程、CPU 等方面的统计信息,iostat 可以显示磁盘 I/O 性能。

8.2 代码优化:

在编写 C++ 代码时,也可以进行一些优化来提高性能。例如,减少不必要的内存分配和释放,避免频繁的函数调用,使用高效的数据结构等。以下是一个简单的示例,展示如何优化代码的性能:

#include <iostream>
#include <vector>
#include <chrono>

// 未优化的代码
void unoptimized() {
    std::vector<int> vec;
    for (int i = 0; i < 1000000; ++i) {
        vec.push_back(i);
    }
}

// 优化后的代码
void optimized() {
    std::vector<int> vec;
    vec.reserve(1000000); // 预先分配内存
    for (int i = 0; i < 1000000; ++i) {
        vec.push_back(i);
    }
}

int main() {
    auto start1 = std::chrono::high_resolution_clock::now();
    unoptimized();
    auto end1 = std::chrono::high_resolution_clock::now();
    auto duration1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();
    std::cout << "Unoptimized time: " << duration1 << " ms" << std::endl;

    auto start2 = std::chrono::high_resolution_clock::now();
    optimized();
    auto end2 = std::chrono::high_resolution_clock::now();
    auto duration2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();
    std::cout << "Optimized time: " << duration2 << " ms" << std::endl;

    return 0;
}

8.3 系统调优:

除了代码优化,还可以对 Linux 系统进行调优。例如,调整内核参数、优化磁盘 I/O 调度算法等。可以通过修改 /etc/sysctl.conf 文件来调整内核参数,例如增加系统的最大文件描述符数量:

fs.file-max = 1000000

修改后,使用 sysctl -p 命令使配置生效。

九、Linux 自动化与脚本编程:

9.1 Shell 脚本基础:

Shell 脚本是一种在 Linux 系统中执行一系列命令的脚本语言。可以使用 #!/bin/bash 作为脚本的开头,指定使用的 Shell 类型。以下是一个简单的 Shell 脚本示例,用于列出当前目录下的所有文件:

#!/bin/bash
ls -l

将上述代码保存为 list_files.sh,然后赋予执行权限:

chmod +x list_files.sh

运行脚本:

./list_files.sh

9.2 使用 C++ 调用 Shell 脚本:

在 C++ 中,可以使用 system() 函数来调用 Shell 脚本。

#include <iostream>
#include <cstdlib>

int main() {
    int result = system("./list_files.sh");
    if (result == 0) {
        std::cout << "Script executed successfully." << std::endl;
    } else {
        std::cerr << "Script execution failed." << std::endl;
    }
    return 0;
}

9.3 自动化任务调度:

在 Linux 中,可以使用 cron 服务来实现自动化任务调度。cron 是一个定时任务调度器,可以按照指定的时间间隔执行脚本或命令。编辑 crontab 文件:

crontab -e

在文件中添加以下内容,表示每天凌晨 2 点执行 list_files.sh 脚本:

0 2 * * * /path/to/list_files.sh

保存并退出后,cron 会按照设定的时间自动执行脚本。

十、Linux 学习方法:

10.1 理论与实践结合:

学习 Linux 不能仅仅停留在理论知识的学习上,要多进行实践操作。可以在虚拟机中搭建 Linux 环境,尝试使用各种命令和工具,编写脚本和程序。通过实践,加深对理论知识的理解,提高动手能力。

10.2 制定学习计划:

Linux 系统内容丰富,知识点繁多,制定一个合理的学习计划非常重要。可以将学习过程分为几个阶段,每个阶段设定明确的学习目标和任务。例如,第一阶段学习基础命令和文件系统操作,第二阶段学习进程管理和网络编程等。

10.3 参与开源项目:

参与开源项目是学习 Linux 的一个很好的途径。可以在 GitHub 等平台上找到一些感兴趣的 Linux 相关项目,阅读代码、提交问题和贡献代码。通过参与开源项目,不仅可以学习到其他开发者的优秀经验和技巧,还可以提高自己的编程水平和团队协作能力。

10.4 加入技术社区:

加入 Linux 技术社区,如论坛、微信群、QQ 群等,可以与其他 Linux 爱好者交流学习经验、分享问题和解决方案。在社区中,还可以及时了解到 Linux 领域的最新动态和技术趋势。

十一、Linux 学习推荐书籍:

11.1 《鸟哥的 Linux 私房菜基础学习篇》:

这本书非常适合初学者,内容全面、讲解详细。从 Linux 的基本概念、安装和配置,到文件系统、命令操作、用户管理等方面都有涉及。书中配有大量的实例和操作步骤,易于理解和上手。

11.2 《Linux 命令行与 Shell 脚本编程大全》:

对于想要深入学习 Shell 脚本编程的读者来说,这本书是一个很好的选择。它详细介绍了 Linux 命令行的使用方法和 Shell 脚本的编程技巧,包括变量、条件语句、循环语句、函数等内容。书中还提供了许多实用的脚本示例,可以帮助读者快速掌握 Shell 脚本编程。

11.3 《深入理解 Linux 内核》:

如果想要深入了解 Linux 内核的工作原理,这本书是必读之作。它从内核的架构、内存管理、进程调度、文件系统等方面进行了深入的分析和讲解。虽然内容较为复杂,但对于提升对 Linux 系统的理解和掌握程度非常有帮助。

11.4 《UNIX 网络编程》:

这本书是网络编程领域的经典之作,虽然是基于 UNIX 系统编写的,但其中的很多知识和技巧同样适用于 Linux 系统。它详细介绍了 TCP/IP 协议、套接字编程、网络编程模型等内容,通过大量的代码示例帮助读者掌握网络编程的核心技术。

十二、小结:

通过本文的学习,我们全面了解了 Linux 系统的基础知识和编程技巧,包括环境搭建、文件系统操作、进程管理、网络编程、内存管理、性能优化以及自动化脚本编程等方面。结合 C++ 代码的示例,我们更深入地理解了如何在 Linux 环境下进行开发。同时,我们还介绍了一些实用的学习方法和推荐了相关的学习书籍,希望能够帮助初学者更好地学习和掌握 Linux。

Linux 系统在不断发展和演进,新的技术和应用场景也在不断涌现。未来,我们可以进一步深入学习 Linux 内核原理,了解操作系统的底层机制;探索容器化技术,如 Docker 和 Kubernetes,实现应用的快速部署和管理;研究云计算和大数据领域,利用 Linux 系统构建高效的分布式计算平台。同时,持续关注 Linux 社区的发展动态,参与开源项目,与全球的开发者共同交流和进步。

希望本文能够帮助你开启 Linux 学习的精彩之旅,不断探索和发现 Linux 系统的无限魅力!

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/2401_82648291/article/details/145932250

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--