前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Linux探秘坊-------9.进程控制

Linux探秘坊-------9.进程控制

作者头像
hope kc
发布2025-03-22 09:05:09
发布2025-03-22 09:05:09
5800
代码可运行
举报
文章被收录于专栏:学习学习
运行总次数:0
代码可运行

1.进程终止

1.终止情况

终止情况只有三种:

2.终止方式
  1. 从main函数返回
  • exit(n)的参数n就是退出码!!!!!!!!!!!!!

2.进程等待

  • 子进程在退出后会进入僵尸模式,只保留PCB,如果父进程不回收,那么就会造成内存泄漏。父进程会通过进程等待进行回收子进程的pcb
1. 等待方式
1. wait函数
  • 类似scanf,如果没有输入则进程会一直卡着不动,wait如果子进程不退出,父进程就会阻塞
2. waitpid()
  • 我们只关注pid -1和>0,这两种类型
    1. -1-----------------------和上面的的wait函数一致,等待所有的子进程
    1. .>0---------------------只等待和传参中的pid一致的子进程,即等待特定的子进程
  • status就是子进程的main函数返回的退出码决定的

为何status和exit()的参数不一致呢?

  • 因为status是int类型,32个比特位,但高16位并不使用,只有低16位使用,而低16位中次八位用来存储退出信息,剩余8位全是0
  • 所以status是100000000(二进制),即256(十进制)。想要获取退出码的值可以使用
2. 等待方式(宏)
3. 阻塞等待与非阻塞等待
1. 阻塞等待
代码语言:javascript
代码运行次数:0
运行
复制
int main() {
    pid_t pid;
    pid = fork();
    if (pid < 0) {
        printf("%s fork error\n", __FUNCTION__);
        return 1;
    } else if (pid == 0) { // child
        printf("child is run, pid is : %d\n", getpid());
        sleep(5);//子进程等待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) {//fork函数运行后,父进程得到的pid就是子进程的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;
}
  • 可以看到,父进程会等待子进程结束才会继续走,这就是阻塞等待
2. 非阻塞等待
代码语言:javascript
代码运行次数:0
运行
复制
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
typedef void (*handler_t)();     // 函数指针类型
std::vector<handler_t> handlers; // 函数指针数组
void fun_one() { printf("这是⼀个临时任务1\n"); }
void fun_two() { printf("这是⼀个临时任务2\n"); }
void Load() {
    handlers.push_back(fun_one);
    handlers.push_back(fun_two);
}
void handler() {
    if (handlers.empty())
        Load();
    for (auto iter : handlers)
        iter();
}
int main() {
    pid_t pid;
    pid = fork();
    if (pid < 0) {
        printf("%s fork error\n", __FUNCTION__);
        return 1;
    } else if (pid == 0) { // child
        printf("child is run, pid is : %d\n", getpid());
        sleep(5);
        exit(1);
    } else {
        int status = 0;
        pid_t ret = 0;
        do {
            ret = waitpid(-1, &status, WNOHANG); // ⾮阻塞式等待,WNOHANG表示若子进程没结束,就返回0,若正常结束,则返回该⼦进程的ID。
            if (ret == 0) {
                printf("child is running\n");
            }
            handler();
        } while (ret == 0);
        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;
}
  • 父进程不用一直等待子进程结束,可以先做自己的事,这就是非阻塞等待

3.进程程序替换

运行此代码后输出如下:

  • 发现指令运行了,但运行指令下一句后篮筐内的内容却没有打印,为什么呢?

当进程调⽤⼀种exec函数时,该进程的 ⽤⼾空间代码和数据完全被新程序替换 ,从新程序的启动例程开始执⾏。调⽤exec并不创建新进程,所以调⽤exec前后该进程的 id并未改变

  • 运行到exec函数时,会直接把exec函数的内容直接替换当前进程的代码数据,所以 原来位于exec后的printf("我的程序运行完毕了\n")直接被覆盖了,自然无法打印
  • 即exec函数只要有返回值,那么就是exec函数运行失败了
1.execl函数(需要路径)

参数详解:

那么如何使用exec函数也不改变原来的代码呢?这个时候子进程就有用啦:

  • 可以看到,exec只替换了子进程的内容,子进程运行结束后会被父进程的waitpid函数回收。
2.execlp 函数(不需要路径)
3.execv 函数(需要路径,和指针数组)
4.execvp 函数(不需要路径,需要指针数组)
5.execvpe 函数(不需要路径,需要指针数组,需要环境变量)
  • 即使不需要路径,但强制写路径./other,也是可以的。

other的代码如下所示:

运行结果为:

  • 可见 命令行参数和环境变量全被父进程中execvpe的参数替代了

那么如何新增环境变量给子进程呢?

可以使用putenv函数:

  • newnew是我自己新建的环境变量,putenv会把这个环境变量导入到当前进程
  • 又因为,子进程的环境变量是继承父进程的,所以子进程也会有父进程的newnew环境变量。

4.自定义shell编写尝试

myshell.cc函数:

代码语言:javascript
代码运行次数:0
运行
复制
#include<iostream>
#include<cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//自己的shell

#define COMMAND_SIZE 1024

#define FORMAT "[%s@%s %s]# "
//1.命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0; 


char cwd[1024];
char cwdenv[1024];

// last exit code 退出码
int lastcode = 0;

// 2. 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;


const char* GetUserName()//从环境变量中拿到User
{
  const char* name = getenv("USER");
  return name== NULL?"None":name;
}


const char* GetHostName()//从环境变量中拿到hostname              
{                                      
  const char* hostname = getenv("HOSTNAME");   
  return hostname== NULL?"None":hostname;      
}  

const char* GetPwd()//从环境变量中拿到路径              
{                                      
    // const char* pwd = getenv("PWD");
   const char *pwd = getcwd(cwd, sizeof(cwd));//不再从环境变量中拿,而是直接获取当前进程的地址,避免因环境变量未更新导致的地址不变
    if(pwd != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);//格式要是name=value的格式,即PWD=。。。
        putenv(cwdenv);//修改原来的环境变量,如果不存在就新建一个
    }
  return pwd== NULL?"None":pwd;      
} 


const char *GetHome()//得到home的地址
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}

void InitEnv()
{
    extern char **environ;//environ会自动获取父进程的环境变量
    memset(g_env, 0, sizeof(g_env));//把g_env全部清空
    g_envs = 0;

    //本来要从配置文件来,自己的shell从父进程中获取环境变量
    //1. 获取环境变量
    for(int i = 0; environ[i]; i++)
    {
        // 1.1 申请空间
        g_env[i] = (char*)malloc(strlen(environ[i])+1);//需要申请空间,因为每一个元素都是复制父进程的
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    //g_env[g_envs++] = (char*)"HAHA=for_test"; //for_test
   // g_env[g_envs] = NULL;

    //2. 导成环境变量
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}


bool Cd()//处理cd命令----内键命令
{
    // cd argc = 1
    if(g_argc == 1)//只有一个cd 表示返回家目录
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());//chdir函数,改变当前的工作目录到家目录
    }
    else
    {
        std::string where = g_argv[1];//cd后有路径或选项
        // cd - / cd ~
        if(where == "-")
        {
            // Todu
        }
        else if(where == "~")
        {
            // Todu
        }
        else
        {
            chdir(where.c_str());//这就是cd后有路径的选项
        }
    }
    return true;
}

void Echo()
{
    if(g_argc == 2)
    {
        // echo "hello world"
        // echo $?
        // echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")//echo $?--------打印退出码
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;//打印一次之后要把退出码清0
        }
        else if(opt[0] == '$')//打印环境变量
        {
            std::string env_name = opt.substr(1);//去掉$后的内容,即环境变量名
            const char *env_value = getenv(env_name.c_str());//getenv获取环境变量的具体内容
            if(env_value)
                std::cout << env_value << std::endl;
        }
        else
        {
            std::cout << opt << std::endl;//单纯打印字符串
        }
    }
}







std::string DirName(const char *pwd)//对路径进行修改
{
#define SLASH "/"//定义分隔符
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);
}

void MakeCommandLine(char cmd_prompt[],int size)
{
  snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
}

void PrintCommandPrompt()//bin
{
  char prompt[COMMAND_SIZE];
  MakeCommandLine(prompt,sizeof(prompt));
  printf("%s",prompt);
  fflush(stdout);//刷新一下缓冲区
}

bool GetCommandLine(char *out,int size)
{
  
  char *c = fgets(out,size,stdin);
  if (c==NULL) return false;
  out[strlen(out)-1]=0;//去掉/0,避免打印空行
  if(strlen(out)==0) return false;
  return true;
}

bool CommandParse(char* commandline)
{

    //3.命令行分析 "ls -a -l"-> "ls" "-a" "-l"
#define SEP " "//CONST类型,需要使用双引号
     g_argc=0;
     g_argv[g_argc++]=strtok(commandline,SEP);//strtok函数用于切割字符串,SEP为分割符,切割完的返回值是切割下来的字符串
    while(g_argv[g_argc++]=strtok(NULL,SEP));//接着第一次切需要把第一个参数设置为nullptr
    g_argc--;//记得要--,因为是后置++
     return true;
}


void PrintArgv()//打印得到的命令行参数
{
  for(int i=0;g_argv[i];i++)
  {
    printf("argv[%d]->%s\n",i,g_argv[i]);//打印所有的命令行参数
  }
  printf("argc:%d\n",g_argc);//打印命令行参数的个数
}


bool CheckAndExecBuiltin()//判断是哪一种内键命令,不是返回false
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")//执行cd命令
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")//执行echo命令
    {
        Echo();
        return true;
    }
    else if(cmd == "export")
    {
    }
    else if(cmd == "alias")
    {
       // std::string nickname = g_argv[1];
       // alias_list.insert(k, v);
    }

    return false;
}

int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        execvp(g_argv[0], g_argv);//所有的命令都由子进程执行,但cd不可,需要父进程执行,因为先cd后子进程确实跳转了,但父进程的pwd并没有被改变,.      一次执行命令时会生成新的子进程会沿用父进程的pwd,所以pwd并没有改变
        exit(1);
    }
    int status = 0;//定义状态
    // father
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);//使用WEXITSTATUS(状态)获得退出码
    }
    return 0;
}


int main()
{
  while(true)
  {
    //1.输出命令行提示符
    PrintCommandPrompt();
    //2.获取用户输入的命令
    char commandline[COMMAND_SIZE];
    if(!GetCommandLine(commandline,sizeof(commandline)))
    {
         continue;
    }
    //3.命令行分析 "ls -a -l"-> "ls" "-a" "-l"
     if(!CommandParse(commandline))
      continue;
   // PrintArgv();
   
   //4.检测内键命令
   if(CheckAndExecBuiltin())  
     continue;
   
   // 5.执行命令
   Execute();
  }
  return 0;
}
  • 1.进程中有两个表, 命令行参数表环境变量表
  • 2.内建命令必须由父进程执行
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.进程终止
    • 1.终止情况
    • 2.终止方式
  • 2.进程等待
    • 1. 等待方式
      • 1. wait函数
      • 2. waitpid()
    • 2. 等待方式(宏)
    • 3. 阻塞等待与非阻塞等待
      • 1. 阻塞等待
      • 2. 非阻塞等待
  • 3.进程程序替换
    • 1.execl函数(需要路径)
    • 2.execlp 函数(不需要路径)
    • 3.execv 函数(需要路径,和指针数组)
    • 4.execvp 函数(不需要路径,需要指针数组)
    • 5.execvpe 函数(不需要路径,需要指针数组,需要环境变量)
  • 4.自定义shell编写尝试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档