进程控制:从创建到终结的完整指南

文章目录

    • 一、进程创建:从`fork`开始的 "分身术"
      • [1.1 `fork`函数:一次调用,两次返回](#1.1 fork函数:一次调用,两次返回)
      • [1.2 写时拷贝:高效的内存共享策略](#1.2 写时拷贝:高效的内存共享策略)
      • [1.3 `fork`的常见用法与失败原因](#1.3 fork的常见用法与失败原因)
    • 二、进程终止:优雅结束与资源释放
      • [2.1 进程退出的三种场景](#2.1 进程退出的三种场景)
      • [2.2 正常终止的三种方式](#2.2 正常终止的三种方式)
      • [2.3 退出码:进程的 "遗言"](#2.3 退出码:进程的 "遗言")
    • 三、进程等待:避免僵尸进程,获取退出状态
      • [3.1 为什么需要进程等待?](#3.1 为什么需要进程等待?)
      • [3.2 两种等待函数:`wait`与`waitpid`](#3.2 两种等待函数:waitwaitpid)
        • [3.2.1 `wait`函数:简单的阻塞等待](#3.2.1 wait函数:简单的阻塞等待)
        • [3.2.2 `waitpid`函数:更灵活的等待](#3.2.2 waitpid函数:更灵活的等待)
      • [3.3 解析`status`参数:子进程的 "最后消息"](#3.3 解析status参数:子进程的 "最后消息")
      • [3.4 阻塞与非阻塞等待](#3.4 阻塞与非阻塞等待)
    • 四、进程程序替换:让子进程执行全新程序
      • [4.1 替换原理:不换进程换 "内容"](#4.1 替换原理:不换进程换 "内容")
      • [4.2 `exec`函数簇:6 个函数的命名密码](#4.2 exec函数簇:6 个函数的命名密码)
      • [4.3 替换后的注意事项](#4.3 替换后的注意事项)
    • 总结:进程控制的核心逻辑

进程是操作系统的基本执行单位,而进程控制则是管理进程生命周期的核心技术。无论是创建新进程、终止运行中的进程,还是回收子进程资源、替换进程执行的程序,都离不开关键的系统调用。本文将以通俗易懂的方式,带你梳理进程控制的四大核心环节: 进程创建进程终止进程等待程序替换 ,让你轻松掌握 forkwaitexec等核心函数的工作原理与实践技巧。

一、进程创建:从fork开始的 "分身术"

当你需要一个进程 "分身" 执行不同任务时,fork函数就是实现这一功能的关键。它能从已存在的进程(父进程)中创建一个全新的进程(子进程),二者如同 "双胞胎" 却又各自独立。

1.1 fork函数:一次调用,两次返回

fork函数的神奇之处在于:一次调用会产生两个返回值。父进程会得到子进程的 PID(进程 ID),而子进程则返回 0。如果调用失败,返回 - 1。

c 复制代码
#include <unistd.h>
pid_t fork(void); // 父进程返回子进程PID,子进程返回0,失败返回-1

来看一个简单例子:

c 复制代码
int main() {
    printf("Before: pid is %d\n", getpid()); // 父进程ID
    pid_t pid = fork();
    if (pid == -1) { perror("fork failed"); return 1; }
    printf("After: pid is %d, fork return %d\n", getpid(), pid);
    return 0;
}

运行结果会出现一行 "Before" 和两行 "After":

plaintext 复制代码
Before: pid is 1234
After: pid is 1234, fork return 1235  // 父进程输出
After: pid is 1235, fork return 0      // 子进程输出

为什么子进程不打印 "Before"?

因为fork是在 "Before" 之后调用的,子进程从fork返回后才开始执行,相当于复制了父进程在fork时刻的执行状态。所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

1.2 写时拷贝:高效的内存共享策略

你可能会疑惑:子进程是完全复制父进程的内存吗?如果是这样,内存消耗也太大了。实际上,操作系统采用了写时拷贝(Copy-On-Write) 技术优化这一过程:

  • 未写入时:父子进程共享同一份代码和数据(只读),不占用额外内存。
  • 需要写入时:操作系统才会为修改的部分创建副本,仅复制被修改的数据,避免不必要的内存浪费。
  • 修改前:父子进程的页表(记录虚拟内存与物理内存映射),都指向同一块物理内存(粉色、蓝色块 ),且标记为"只读",防止直接修改共享内容。
  • 修改后:假设父进程要改"页表项100"对应的数据,系统会拷贝一份物理内存(新蓝色块 ) 给父进程,父进程页表指向新拷贝,子进程仍指向原物理内存。这样父子数据分离,互不干扰。

这种 "延时复制" 策略大幅提高了内存利用率,也是进程独立性的技术保障(父子进程修改数据互不影响)。

1.3 fork的常见用法与失败原因

fork的典型应用场景有两个:

  1. 父子分工:父进程等待客户端请求,子进程处理具体请求(如 Web 服务器)。
  2. 执行新程序 :子进程通过fork创建后,调用exec系列函数执行全新程序(后续会详细讲解)。

fork失败的情况较少见,主要原因包括:

  • 系统进程数量超过上限(如 Linux 的pid_max限制)。
  • 普通用户创建的进程数超过系统对该用户的限制。

二、进程终止:优雅结束与资源释放

进程终止的本质是释放系统资源(包括内核数据结构、内存、文件描述符等)。无论是正常结束还是异常崩溃,进程最终都会走向终止。

2.1 进程退出的三种场景

  • 正常结束,结果正确:如程序完成计算并输出正确结果。
  • 正常结束,结果错误:如逻辑错误导致计算结果错误,但程序未崩溃。
  • 异常终止 :如被信号杀死(如ctrl+c触发的SIGINT信号)、访问非法内存等。

2.2 正常终止的三种方式

方式 特点 适用场景
return n(从main函数) 等同于exit(n),由运行时库处理 主程序退出
exit(int status) 先执行清理函数(atexiton_exit定义),刷新缓冲区,再调用_exit 需要释放资源的正常退出
_exit(int status) 直接终止进程,不刷新缓冲区,不执行清理函数 紧急退出(如内核态)

关键区别exit会确保缓冲区数据写入文件,而_exit直接丢弃缓冲区数据。例如:

c 复制代码
// 使用exit:会打印"hello"(缓冲区被刷新)
printf("hello");
exit(0);

// 使用_exit:不会打印"hello"(缓冲区数据被丢弃)
printf("hello");
_exit(0);

2.3 退出码:进程的 "遗言"

进程终止时会返回一个退出码(status),用于告知父进程执行结果。Linux 中:

  • 退出码为0表示执行成功。
  • 非 0 表示执行失败(具体含义由程序定义)。

可以通过echo $?在 Shell 中查看上一个进程的退出码,常见退出码及含义如下:

退出码 含义 示例
0 成功执行 ls命令正常列出文件
1 通用错误 无权限执行命令、除以 0
127 命令未找到 执行不存在的程序
130 SIGINTctrl+c)终止 手动中断运行中的程序
143 SIGTERM终止 kill命令默认信号
255/* 退出码超过了零到255的范围,因此重新计算 void _exit(int status),虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现 返回值是255。

通过strerror(int errnum)函数可获取退出码对应的描述文本,例如strerror(1)返回 "Operation not permitted"。

三、进程等待:避免僵尸进程,获取退出状态

如果父进程不处理子进程的终止,子进程会变成僵尸进程(Zombie)------ 保留 PID 和退出状态,占用系统资源且无法被杀死。进程等待就是父进程回收子进程资源、获取退出状态的关键机制。

3.1 为什么需要进程等待?

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

3.2 两种等待函数:waitwaitpid

3.2.1 wait函数:简单的阻塞等待
c 复制代码
#include<sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status); 
// 返回值:成功返回子进程PID,失败返回-1
// 参数:输出型参数,获取子进程退出状态,不关心则可设为NULL

wait阻塞等待任意子进程终止,并回收其资源。例如:

c 复制代码
pid_t pid = fork();
if (pid == 0) { exit(10); } // 子进程退出码10
else {
    int status;
    wait(&status); // 等待子进程
    if (WIFEXITED(status)) { // 判断是否正常退出
        printf("子进程退出码:%d\n", WEXITSTATUS(status)); // 输出10
    }
}
3.2.2 waitpid函数:更灵活的等待

waitpidwait的增强版,支持指定等待的子进程、非阻塞等待等:

c 复制代码
pid_t waitpid(pid_t pid, int *status, int options);
参数 含义
pid pid=-1:等待任意子进程(同wait);pid>0:等待 PID 为pid的子进程
status wait,通过宏解析退出状态(如WIFEXITEDWEXITSTATUS
options 0:阻塞等待;WNOHANG:非阻塞等待(若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。)

返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
  1. "如果子进程已经退出,调用 wait / waitpid 时,会立即返回,释放资源,拿到子进程退出信息"
  • 比如:子进程早就跑完、退出了,父进程这时候调用 wait ,会 立刻得到结果(不用等),还能拿到子进程的退出状态(比如 exit(n) 里的 n ),同时系统会回收子进程占的资源。
  1. "如果任意时刻调用 wait / waitpid ,子进程还在正常运行,则父进程可能阻塞"
  • 比如:子进程还在忙活着(没执行 exit ),父进程调用 wait ,就会 卡住(阻塞),直到子进程退出,父进程才会"被唤醒",继续往下执行。
  1. "如果不存在该子进程,则立即出错返回"
  • 比如:父进程要等的子进程压根没创建,或者已经被回收了,调用 wait 就会直接报错(返回错误码),告诉你"没这个子进程可以等"。
  1. 左边:父进程阻塞在 wait
  • 父进程执行到 parent_code() 里的 wait(&s) 时,子进程还在 child_code() 里跑(没 exit ),所以父进程会卡在 wait 这里不动(阻塞)。
  1. 右边:子进程退出后,父进程继续
  • 子进程执行 exit(n) 退出后,父进程的 wait(&s) 被"唤醒",拿到子进程的退出状态(存在 s 里 ),然后父进程继续往下执行 parent_code() 后续代码(比如图里的 exit(&s) ,实际更常见的是处理退出状态后干别的活 )。

3.3 解析status参数:子进程的 "最后消息"

status是一个 32 位整数,其低 16 位存储退出信息,结构如下:

  • 高 8 位:正常退出时存储退出码(仅当低 7 位为 0 时有效)。
  • 低 7 位:存储终止信号(若进程被信号杀死,则非 0)。

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

2.如果传递NULL,表示不关心子进程的退出状态信息。

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

4.status不能简单的当作整形来看待,要按 二进制位(比特位)拆分 理解,低 16 位藏着关键信息,分两种场景:

(1)子进程"正常退出"(比如自己调用 exit(n) 或 return n )

  • 低 16 位里,高 8 位(第 8~15 位) 存的是 exit(n) 的 n (退出状态码 );
  • 低 8 位(第 0~7 位) 是 0 (标记"正常退出" )。

(2)子进程"被信号杀死"(比如被 kill 命令、段错误信号终止 )

  • 低 16 位里,高 8 位(第 8~15 位) 没用(标记为"未用" );
  • 低 7 位(第 0~6 位) 存的是"终止信号编号"(比如 9 是 SIGKILL ,强制杀死 );
  • 第 7 位 是 core dump 标志( 1 表示程序崩溃时生成了 core 文件,可用来调试;0表示未生成 )。

测试代码:

c 复制代码
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main( void )
{
    pid_t pid;
    
    if ( (pid=fork()) == -1 )
    	perror("fork"),exit(1);
    if ( pid == 0 )
    {
        sleep(20);
        exit(10);
    }
    else 
    {
        int st;
        int ret = wait(&st);
        if ( ret > 0 && ( st & 0X7F ) == 0 )
        { 	// 正常退出 
       	 	printf("child exit code:%d\n", (st>>8)&0XFF);//把st右移8位,再取低8位,就得到子进程的退出码
        } 
        else if( ret > 0 ) 
        { 	// 异常退出 
        	printf("sig code : %d\n", st&0X7F );// st的低7位,这里存的是信号编号,(比如9表示SIGKILL,强制杀死)
        }
    }
}

测试结果:

bash 复制代码
# ./a.out #等20秒退出
child exit code:10 
# ./a.out #在其他终端kill掉
sig code : 9

通过以下宏可解析status

  • WIFEXITED(status):若为真,表示子进程正常退出。

    • 组成及含义: W 是 "wait" 的缩写,表明这是与等待子进程状态相关的操作; IF 表示 "是否",用来进行条件判断; EXITED 意为 "已退出"。
    • 功能对应:这个宏用于判断子进程是否正常退出,从命名上就很清晰地表达出,是在等待子进程状态的过程中,判断子进程是否处于已正常退出的状态 。
  • WEXITSTATUS(status):若WIFEXITED为真,返回退出码。

    • 组成及含义:同样开头的 W 代表 "wait" ; EXIT 表示 "退出" , STATUS 表示 "状态、码值" 。
    • 功能对应:该宏的作用是在确认子进程正常退出(即 WIFEXITED 为真时 ),获取子进程的退出码,命名直观地体现了它是获取与子进程退出相关状态码的功能 。
  • WIFSIGNALED(status):若为真,表示子进程被信号杀死。

    • 组成及含义: W 还是 "wait" 的意思 ; IF 依旧是 "是否" 的判断含义 ; SIGNALED 表示 "被信号终止" 。
    • 功能对应:它用于判断子进程是否是被信号杀死的,命名直接反映出是在等待子进程状态时,判断子进程是否处于被信号终止的状态 。
  • WTERMSIG(status):若WIFSIGNALED为真,返回终止信号编号。

    • 组成及含义: W 为 "wait" ; TERM 是 "terminate(终止 )" 的缩写 , SIG 是 "signal(信号 )" 的缩写。
    • 功能对应:当确认子进程是被信号杀死(即 WIFSIGNALED 为真时 ),这个宏用于获取导致子进程终止的信号编号 ,从名字能看出其与获取子进程终止信号相关 。

WEXITSTATUS解析status时,只关注低8位(二进制最后8位),超出部分会被截断; WTERMSIG是取低7位。

3.4 阻塞与非阻塞等待

  • 阻塞等待 :父进程暂停执行,直到子进程终止(如waitpid(-1, &status, 0))。

进程的阻塞等待方式:

c 复制代码
int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0)
    {
        printf("%s fork error\n",__FUNCTION__);//__FUNCTION__是当前函数名的快捷方式,这里是"main"
        return 1;
    } 
    else if( pid == 0 )
    { 	//child
        printf("child is run, pid is : %d\n",getpid());
        sleep(5);
        exit(257);
    } 
    else
    {
        int status = 0;
        pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S 
        printf("this is test for wait\n");
        if( WIFEXITED(status) && ret == pid )
        {
            printf("wait child 5s success, child return code is 
            :%d.\n",WEXITSTATUS(status));
        }
        else
        {
            printf("wait child failed, return.\n");
            return 1;
        }
    }
    return 0;
}                

运行结果:

c 复制代码
[root@localhost linux]# ./a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is :1.//257的二进制是1 0000 0001,但WEXITSTATUS解析status时只关注低8位,即0000 0001 所以是1
  • 非阻塞等待 :父进程不暂停,若子进程未退出则立即返回 0,可用于 "轮询" 等待(如waitpid(-1, &status, WNOHANG))。

进程的非阻塞等待方式:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>  // 提供waitpid等进程等待函数
#include <unistd.h>    // 提供fork、sleep、sleep等函数
#include <vector>      // 提供C++的vector容器

// 定义函数指针类型,用于指向指向无参数无返回值的函数
typedef void (*handler_t)(); 

// 存储函数指针的vector容器,用于管理需要执行的任务
std::vector<handler_t> handlers; 

// 任务函数1
void fun_one() 
{
	printf("这是一个临时任务1\n");
}

// 任务函数2
void fun_two() 
{
    printf("这是一个临时任务2\n");
}

// 加载任务函数到vector容器中
void Load() 
{
    handlers.push_back(fun_one);  // 添加任务1
    handlers.push_back(fun_two);  // 添加任务2
}

// 执行所有已加载的任务
void handler() 
{
    // 如果任务容器为空,则先加载任务
    if (handlers.empty())
        Load();
    
    // 遍历容器,执行每个任务函数
    for (auto iter : handlers)
        iter();  // 调用函数指针指向的任务
}

int main() 
{
    pid_t pid;  // 用于存储fork的返回值(进程ID)
    
    // 创建子进程
    pid = fork();
    
    // fork失败的情况
    if (pid < 0) 
    {
        // __FUNCTION__宏表示当前函数名(此处为"main")
        printf("%s fork error\n", __FUNCTION__);
        return 1;  // 异常退出
    } 
    // 子进程执行的代码
    else if (pid == 0) 
    { 
        printf("child is run, pid is : %d\n", getpid());  // 输出子进程ID
        sleep(5);  // 子进程休眠5秒,模拟执行任务
        exit(1);   // 子进程退出,退出码为1
    } 
    // 父进程执行的代码
    else 
    {
        int status = 0;    // 用于存储子进程的退出状态
        pid_t ret = 0;     // 用于存储waitpid的返回值
        
        // 循环进行非阻塞等待
        do {
            // 非阻塞等待任意子进程:
            // -1表示等待任意子进程
            // status用于接收退出状态
            // WNOHANG表示非阻塞模式(子进程未退出则立即返回0)
            ret = waitpid(-1, &status, WNOHANG);
            
            // 如果ret为0,说明子进程还在运行
            if (ret == 0) 
            {
                printf("child is running\n");  // 提示子进程正在运行
            }
            
            // 执行预设的任务(无论子进程是否退出,每次循环都执行)
            handler();
            
        } while (ret == 0);  // 当子进程还在运行时,继续循环等待
        
        // 子进程退出后,判断退出状态
        // WIFEXITED(status):检查子进程是否正常退出
        // ret == pid:确保回收的是目标子进程
        if (WIFEXITED(status) && ret == pid) 
        {
            // WEXITSTATUS(status):提取子进程的退出码
            printf("wait child 5s success, child return code is :%d.\n",
                   WEXITSTATUS(status));
        } 
        // 等待失败的情况(如子进程被信号终止等)
        else 
        {
            printf("wait child failed, return.\n");
            return 1;  // 异常退出
        }
    }
    
    return 0;  // 正常退出
}

四、进程程序替换:让子进程执行全新程序

fork创建的子进程默认执行与父进程相同的代码,若想让子进程执行全新程序(如从磁盘加载新的可执行文件),需通过程序替换实现。

4.1 替换原理:不换进程换 "内容"

程序替换通过exec系列函数实现,其核心是:

  • 替换用户空间的代码和数据:丢弃原进程的代码和数据,加载新程序的代码、数据和堆栈。
  • 不创建新进程:进程 PID 保持不变,仅替换进程的执行内容。

例如:子进程通过exec加载/bin/ls,之后就会执行ls命令,而非原父进程的代码。

4.2 exec函数簇:6 个函数的命名密码

exec系列有 6 个函数,均以exec开头,区别在于参数格式和是否自动搜索路径:

函数 特点 示例
execl(path, arg0, arg1, ..., NULL) 参数列表形式(l=list),需指定程序全路径 execl("/bin/ls", "ls", "-l", NULL)
execlp(file, arg0, ..., NULL) 自动搜索PATH环境变量(p=path),无需全路径 execlp("ls", "ls", "-l", NULL)
execle(path, arg0, ..., NULL, envp) 自定义环境变量(e=env) char* env[] = {"PATH=/bin", NULL}; execle("/bin/ls", "ls", NULL, env);
execv(path, argv) 参数数组形式(v=vector) char* argv[] = {"ls", "-l", NULL}; execv("/bin/ls", argv);
execvp(file, argv) 结合vp,参数数组 + 自动搜路径 execvp("ls", argv);
execve(path, argv, envp) 系统调用原函数(其他函数均调用它),自定义环境变量 内核级实现,用户态少直接使用

命名解释

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

命名规律

  • l(list):参数以列表形式传递(如arg0, arg1, NULL)。
  • v(vector):参数以数组形式传递(如argv[])。
  • p(path):自动从PATH环境变量搜索程序路径。
  • e(env):允许自定义环境变量(默认继承父进程环境)。

exec调用举例如下:

c 复制代码
#include <unistd.h>
int main()
{
    char *const argv[] = {"ps", "-ef", NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
    
    execl("/bin/ps", "ps", "-ef", NULL);
   
    // 带p的,可以使⽤环境变量PATH,⽆需写全路径 
    execlp("ps", "ps", "-ef", NULL);
   
    // 带e的,需要⾃⼰组装环境变量 
    execle("ps", "ps", "-ef", NULL, envp);
    
    execv("/bin/ps", argv);
    
    // 带p的,可以使⽤环境变量PATH,⽆需写全路径 
    execvp("ps", argv);
   
    // 带e的,需要⾃⼰组装环境变量 
    execve("/bin/ps", argv, envp);
    
    exit(0);
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用execve,所以execveman手册第2节, 其它函数在man手册第3节。

  • execve 是 "真·系统调用":直接和操作系统内核交互,负责加载新程序替换当前进程,在 man 手册第 2 节(系统调用分类 )。
  • 其他 5 个( execlp / execl / execle / execvp / execv )是 "库函数封装":它们最终会调用 execve ,只是帮你简化了参数准备(比如自动找路径、传环境变量 ),在 man 手册第 3 节(库函数分类 )。

这些函数之间的关系如下图所示。 下图exec函数簇一个完整的例子:

4.3 替换后的注意事项

  • 替换成功后,原进程的代码和数据被完全丢弃,不会返回(若返回则表示替换失败)。
  • 替换不改变进程的 PID、文件描述符(已打开的文件仍可访问)等内核属性。
  • 通常与fork配合使用:父进程fork后,子进程调用exec执行新程序,父进程继续等待或执行其他任务。

总结:进程控制的核心逻辑

进程控制围绕生命周期管理展开,四大环节环环相扣:

  • 创建fork通过写时拷贝高效创建子进程,父子进程 PID 不同是关键标识。
  • 终止exit_exit实现正常退出,退出码传递执行结果。
  • 等待wait/waitpid解决僵尸进程问题,通过status解析子进程状态。
  • 替换exec系列函数让子进程执行全新程序,实现进程功能的灵活扩展。

掌握这些机制,你就能轻松应对进程管理的各种场景,从简单的多进程程序到复杂的服务器架构,都能游刃有余。记住:进程控制的核心是资源管理生命周期把控 ,理解这一点,技术细节便迎刃而解。

相关推荐
Doris_LMS1 小时前
在Linux下安装nginx(保姆级别)
linux·运维·nginx·centos
超級二蓋茨3 小时前
在 CentOS 上安装 FFmpeg
linux·ffmpeg·centos
obboda4 小时前
Linux基础复习:字符输入与输出
linux·运维·服务器
*wj5 小时前
【linux驱动开发】Vscode + Remote SSH + clangd + bear=内核源码阅读环境搭建
linux·驱动开发·vscode
FED_AF7 小时前
Linux救援模式之应用篇
linux·运维
拾心217 小时前
【运维基础】Linux 进程调度管理
linux·运维·服务器
橘颂TA7 小时前
【Linux】的起源 and 3秒学习11个基本指令
linux·指令·linux的历史
~狂想家~8 小时前
Ubuntu20.04安装和配置Samba实现Win11下共享文件夹
linux·ubuntu·samba
ansondroider8 小时前
Ubuntu 抽取系统制作便于chroot的镜像文件
linux·ubuntu·chroot
羚羊角uou8 小时前
【Linux】编辑器vim和编译器gcc/g++
linux·运维·编辑器