当前位置: 首页 > news >正文

Day29 基于fork+exec的minishell实现与pthread多线程

day29 基于fork+exec的minishell实现与pthread多线程

使用fork+exec实现minishell

核心功能说明

本程序实现了一个简易的shell,具有以下功能:

  • 显示当前工作目录提示符
  • 接收并解析用户输入的命令
  • 使用fork创建子进程执行命令
  • 使用execvp执行系统命令
  • 特别处理cdll命令
  • 避免产生僵尸进程(通过wait(NULL))
#include <stdio.h>     // 标准输入输出库
#include <stdlib.h>    // 标准库(包含exit等函数)
#include <string.h>    // 字符串操作库
#include <sys/wait.h>  // 等待子进程相关函数
#include <unistd.h>    // POSIX系统调用(getcwd, fork等)// 显示命令提示符函数
void show_prompt()
{char path[512] = {0};        // 用于存储当前工作目录的缓冲区getcwd(path, sizeof(path));  // 获取当前工作目录printf("linux@ubuntu:%s$", path);  // 打印标准提示符格式
}int main(int argc, char **argv)
{while (1)  // 无限循环,保持shell持续运行{show_prompt();  // 显示命令提示符char cmd_line[256] = {0};  // 存储用户输入的命令行fgets(cmd_line, sizeof(cmd_line), stdin);  // 读取用户输入cmd_line[strlen(cmd_line) - 1] = '\0';     // 移除输入末尾的换行符// 如果输入为空行,跳过处理if (0 == strlen(cmd_line)){continue;}// 用于存储命令参数的指针数组(最多4个参数)char *args[5] = {NULL};// 使用strtok分割命令行,以空格为分隔符args[0] = strtok(cmd_line, " ");  // 第一个参数(命令名)args[1] = strtok(NULL, " ");      // 第二个参数args[2] = strtok(NULL, " ");      // 第三个参数args[3] = strtok(NULL, " ");      // 第四个参数// 特殊处理cd命令if (0 == strcmp(args[0], "cd")){// 如果没有参数,切换到/home/linux目录if (NULL == args[1]){chdir("/home/linux");}else  // 有参数,切换到指定目录{chdir(args[1]);}continue;  // cd命令处理完毕,跳过后续执行}// 创建子进程pid_t pid = fork();if (0 == pid)  // 子进程代码{// 特殊处理ll命令(将其转换为ls -alhF)if (0 == strcmp(args[0], "ll")){if (NULL == args[1])  // 如果没有参数{args[0] = "ls";     // 命令名改为"ls"args[1] = "-alhF";  // 添加参数"-alhF"}else  // 有参数{args[0] = "ls";      // 命令名改为"ls"args[2] = "-alhF";   // 将"-alhF"作为第三个参数}}// 执行命令,如果失败则退出子进程execvp(args[0], args);exit(0);}// 父进程等待子进程结束,避免僵尸进程wait(NULL);}return 0;
}

理想运行结果

linux@ubuntu:/home/linux$ ls /home/linux
file1.txt  file2.c  directory1
linux@ubuntu:/home/linux$ cd directory1
linux@ubuntu:/home/linux/directory1$ ll
total 12
drwxr-xr-x 2 user user 4096 Aug 18 10:00 .
drwxr-xr-x 3 user user 4096 Aug 18 09:00 ..
-rw-r--r-- 1 user user   23 Aug 18 10:00 file3.txt
linux@ubuntu:/home/linux/directory1$ ls -l
total 4
-rw-r--r-- 1 user user 23 Aug 18 10:00 file3.txt
linux@ubuntu:/home/linux/directory1$ 

关键说明

  • 程序会显示类似linux@ubuntu:/当前目录$的提示符
  • 输入ls /home/linux会列出指定目录下的文件
  • 输入cd directory1会切换到子目录
  • 输入ll会被自动转换为ls -alhF并执行
  • 程序通过wait(NULL)确保子进程结束后才继续,避免僵尸进程

pthread 线程

基本概念

线程定义:线程是轻量级进程,是进程中的多个并发执行任务。
核心区别

  • 进程:系统中最小的资源分配单位
  • 线程:系统中最小的执行单位

主要优点

  • 比多进程节省系统资源
  • 线程间可以直接共享变量(除栈区外的内存区域)

主要缺点

  • 稳定性比进程稍差
  • 调试相对复杂(需使用info thread等GDB命令)

线程与进程对比

特性进程线程
资源开销约3GB约8MB
数据共享需要IPC机制除栈区外全部共享
稳定性相对较低
适用场景独立大任务同一任务中的小任务
归属关系独立实体属于某个进程

共同点:都支持并发执行。

线程核心操作

1. 线程创建

int pthread_create(pthread_t *thread,           // 线程ID存储位置const pthread_attr_t *attr,  // 线程属性(NULL表示默认)void *(*start_routine)(void *), // 线程执行函数void *arg                    // 传递给执行函数的参数
);

功能:创建新线程
返回值:成功返回0,失败返回错误码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>// 线程1执行函数
void *th1(void *arg)
{while(1){printf("th1 发送视频\n");sleep(1);}return NULL;  // 线程正常退出
}// 线程2执行函数
void *th2(void *arg)
{while(1){printf("th2 接收控制\n");sleep(1);}return NULL;  // 线程正常退出
}int main(int argc, char **argv)
{pthread_t tid1, tid2;  // 线程ID变量// 创建两个线程,分别执行th1和th2pthread_create(&tid1, NULL, th1, NULL);pthread_create(&tid2, NULL, th2, NULL);while(1) sleep(1);  // 主线程保持运行return 0;
}

关键说明

  • 一次pthread_create只能创建一个线程
  • 每个进程至少有一个线程(主线程)
  • 主线程退出会导致所有子线程退出
  • 使用ps -eLfps -eLo pid,ppid,lwp,stat,comm可查看线程信息

2. 获取线程ID

pthread_t pthread_self(void);

功能:获取当前线程的ID
返回值:当前线程ID

// 在线程函数中获取并打印线程ID
printf("th1 发送视频,tid :%lu\n", pthread_self());
printf("th2 接收控制,tid :%lu\n", pthread_self());
printf("main_th tid: %lu\n", pthread_self());

3. 线程退出方式

3.1 自行退出(自杀)
void pthread_exit(void *retval);

功能:线程自行退出,可返回状态值

void* th(void* arg)
{int i = 3;while (i--){printf("th tid:%lu\n", pthread_self());sleep(1);}pthread_exit(NULL);  // 线程退出
}
3.2 强制退出(他杀)
int pthread_cancel(pthread_t thread);

功能:请求结束指定线程

void* th(void* arg)
{while (1){printf("th tid:%lu\n", pthread_self());sleep(1);}
}int main(int argc, char** argv)
{pthread_t tid;pthread_create(&tid, NULL, th, NULL);int i = 0;while (1){printf("main_th tid:%lu\n", pthread_self());sleep(1);i++;if(3 == i)  // 3秒后取消子线程{pthread_cancel(tid);}}return 0;
}

4. 线程回收

int pthread_join(pthread_t thread, void **retval);

功能:阻塞等待线程结束并回收资源
参数

  • thread:要回收的线程ID
  • retval:接收线程退出状态的指针
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>void* th(void* arg)
{   int i = 5;while(i--){printf("th processing\n");sleep(1);}// char str[] = "我要结束了";  // 错误:局部变量,函数返回后空间释放// static char str[] = "我要结束了";  // 不推荐:多线程可能导致数据覆盖char* str = malloc(20);  // 正确:在堆上分配内存strcpy(str, "我要结束了");  // 复制字符串到堆内存pthread_exit(str);  // 退出线程并返回堆内存地址
}int main(int argc, char **argv)
{pthread_t tid;  // 线程ID变量pthread_create(&tid, NULL, th, NULL);  // 创建线程void* ret = NULL;  // 用于接收线程返回值pthread_join(tid, &ret);  // 阻塞等待线程结束printf("ret %s\n", (char*)ret);  // 强制类型转换并打印free(ret);  // 释放线程分配的堆内存return 0;
}

线程回收策略

  1. 预估线程能有限时间内结束:使用pthread_join等待
  2. 线程可能休眠或阻塞:等待一定时间后强制处理
  3. 线程需长时间运行:不回收资源或设置分离属性

5. 线程参数与返回值

5.1 基本参数传递
void* th(void* arg)
{char* tmp = (char*)arg;strcpy(tmp, "hello,world");return tmp;
}int main(int argc, char** argv)
{pthread_t tid = 0;char* p = malloc(20);pthread_create(&tid, NULL, th, p);  // 传递堆内存地址void* ret = NULL;pthread_join(tid, &ret);printf("ret is %s\n", (char*)ret);return 0;
}
5.2 结构体参数传递
typedef struct
{char name[50];   // 姓名字段(50字节缓冲区)int age;         // 年龄字段char addr[100];  // 地址字段(100字节缓冲区)
} PER;void* th(void* arg)
{PER* per = (PER*)arg;  // 将参数转换为结构体指针printf("input name:");fgets(per->name, sizeof(per->name), stdin);  // 读取姓名(含换行符)per->name[strlen(per->name)-1] = '\0';  // 移除换行符printf("input age:");char tmp[5] = {0};  // 临时缓冲区存储年龄fgets(tmp, sizeof(tmp), stdin);per->age = atoi(tmp);  // 转换为整数printf("input addr:");fgets(per->addr, sizeof(per->addr), stdin);  // 读取地址return per;  // 返回结构体指针
}int main(int argc, char **argv)
{PER per;  // 在主线程栈上创建结构体pthread_t tid;pthread_create(&tid, NULL, th, &per);  // 传递结构体地址void* ret = NULL;pthread_join(tid, &ret);// 打印线程填充的结构体数据printf("name:%s age:%d addr:%s\n", ((PER*)ret)->name, ((PER*)ret)->age, ((PER*)ret)->addr);return 0;
}

参数传递方式

  • 整数:直接传递(需注意类型转换)
  • 字符串
    • 栈区字符数组:不推荐(线程结束后失效)
    • 字符串常量:全局有效但不可修改
    • 堆区字符串:推荐(需手动释放)
  • 结构体
    1. 定义结构体类型
    2. 创建结构体变量
    3. 传递结构体地址
    4. 在线程函数中访问结构体数据

返回值处理

  • 返回指针必须指向有效内存(堆内存或全局/静态变量)
  • 栈区变量返回会导致未定义行为
  • 主线程需负责释放堆内存

6. 线程分离属性

int pthread_attr_init(pthread_attr_t *attr);    // 初始化线程属性
int pthread_attr_destroy(pthread_attr_t *attr); // 销毁线程属性
void* th(void* arg)
{pthread_detach(pthread_self());  // 设置线程为分离状态return NULL;
}int main(int argc, char **argv)
{pthread_t tid;int ret = pthread_create(&tid, NULL, th, NULL);// 无需pthread_join,线程结束自动回收资源return 0;
}

作用:线程消亡后自动回收资源,无需pthread_join

7. 线程清理函数

void pthread_cleanup_push(void (*routine)(void *), void *arg); // 注册清理函数
void pthread_cleanup_pop(int execute);                        // 调用清理函数
#include <stdio.h>
#include <pthread.h> 
#include <stdlib.h>
#include <string.h>// 清理函数使用的结构体
typedef struct 
{FILE* fp;char* p;
} TH_ARGS;// 清理函数
void clean(void *arg)
{TH_ARGS* tmp = (TH_ARGS*)arg;printf("this clean, p is %s\n", tmp->p);free(tmp->p);fclose(tmp->fp);
}void* th(void* arg)
{FILE* fp = fopen("1.txt", "w");  // 打开文件char* p = malloc(20);            // 分配堆内存TH_ARGS args = {0};args.fp = fp;args.p = p;pthread_cleanup_push(clean, &args);  // 注册清理函数fputs("hello", fp);      // 写入文件strcpy(p, "hello");      // 复制字符串printf("th well done\n");pthread_cleanup_pop(1);  // 执行清理函数return NULL;
}int main(int argc, char **argv)
{pthread_t tid;pthread_create(&tid, NULL, th, NULL);pthread_join(tid, NULL);printf("main th will exit...\n");return 0;
}

功能:在线程退出时自动执行清理操作(如释放资源)

线程与进程函数对比

进程操作线程操作
forkpthread_create
getpid, ppidpthread_self
exitpthread_exit
wait, waitpidpthread_join
killpthread_cancel
atexitpthread_cleanup
execsystem(内部使用fork+exec)

线程核心要点回顾

  • 线程本质:轻量级进程,属于某个进程
  • 核心优势
    • 资源开销小(约8MB vs 进程3GB)
    • 数据共享方便(除栈区外全部共享)
  • 适用场景:并发执行同一任务中的小任务
  • 关键函数
    1. 创建:pthread_create
    2. 回收:pthread_join
    3. 传参:通过void*指针传递任意数据
  • 内存管理:线程返回值必须指向有效内存(堆内存推荐)
  • 调试技巧:GDB中使用info thread查看线程状态
http://www.dtcms.com/a/353354.html

相关文章:

  • 【Linux】基本指令学习3
  • IBMS集成管理系统与3D数字孪生智能服务系统的应用
  • Linux驱动 — 导出proc虚拟文件系统属性信息
  • LabVIEW 音频信号处理
  • 【ElasticSearch】原理分析
  • opencv+yolov8n图像模型训练和推断完整代码
  • django注册app时两种方式比较
  • PyTorch图像预处理完全指南:从基础操作到GPU加速实战
  • jQuery版EasyUI的ComboBox(下拉列表框)问题
  • 通义万相音频驱动视频模型Wan2.2-S2V重磅开源
  • 聊一聊 单体分布式 和 微服务分布式
  • Package.xml的字段说明
  • 前端架构知识体系:css架构模式和代码规范
  • 趣味学习Rust基础篇(用Rust做一个猜数字游戏)
  • PAT 1087 All Roads Lead to Rome
  • 嵌入式学习资料分享
  • java中的数据类型
  • 《FastAPI零基础入门与进阶实战》第14篇:ORM之第一个案例改善-用户查询
  • 【图文介绍】PCIe 6.0 Retimer板来了!
  • 快速上手对接币安加密货币API
  • 《Linux 网络编程四:TCP 并发服务器:构建模式、原理及关键技术(以select )》
  • 3 无重复字符的最长子串
  • Windows系统之不使用第三方软件查看电脑详细配置信息
  • 基于linux系统的LIRC库学习笔记
  • Ubuntu 的磁盘管理
  • [java] 控制三个线程按顺序交替输出数字1、2、3
  • 【新版发布】Apache DolphinScheduler 3.3.1 正式上线:更稳、更快、更安全!
  • TensorFlow 面试题及详细答案 120道(21-30)-- 模型构建与神经网络
  • 数据结构:创建堆(或者叫“堆化”,Heapify)
  • 增强CD47检查点免疫治疗:高通量发现增强巨噬细胞吞噬作用的小分子协同剂