自学内容网 自学内容网

Linux:进程控制

一、进程创建

1、fork函数

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

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

2、写时拷贝

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

在这里插入图片描述
因为有写时拷⻉技术的存在,所以⽗⼦进程得以彻底分离离!完成了进程独⽴性的技术保证!
写时拷⻉,是⼀种延时申请技术,可以提⾼整机内存的使⽤率

  • 在父进程没有创建子进程时,数据段是可读可写的
  • 在父进程创建子进程后,操作系统就会把数据段的权限设为只读的
  • 改成只读之后,父子进程其中一个进程修改数据段时,因为是只读的所以就会报错,操作系统知道是数据段只读就会进行写时拷贝。

写时拷贝的好处:
● 1、减少创建时间
● 2、减少内存浪费

二、进程终止

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

1、进程退出
  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

main函数的返回值,通常表明程序的执行情况

2、echo $? 查看程序返回结果

在这里插入图片描述
打印最近一个程序(进程)退出时的退出码

3、进程常⻅退出⽅法
  • 1、main函数返回

  • 2、调用exit

  • 3、_exit(系统级别的进程退出,那个进程调用那个进程退出)

  • 任何地方调用exit,都表示进程结束

exit是C语言提供的函数,_exit是系统提供的函数
区别:

  • exit当缓冲区有内容没刷新会在先刷新再结束
  • _exit当缓冲区有内存没刷新也直接结束进程

三、进程等待

1、进程等待的必要性

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

2、wait 进程等待

系统调用:
在这里插入图片描述
在这里插入图片描述

  • 如果等待子进程,子进程没有退出,父进程会阻塞在wait调用处
  • 等待成功,返回值是子进程的pid
    在这里插入图片描述
3、waitpid 进程等待
pid_t waitpid(pid_t pid, int *status, int options);

int *status: 输出型参数
int options: 阻塞控制
在这里插入图片描述
pid 为-1 时 是等待任意进程退出,options设为0默认阻塞等待
在这里插入图片描述
在这里插入图片描述

4、获取⼦进程status

通过父进程的变量,传地址让waitpid()把子进程结束时的返回值写入父进程的变量
在这里插入图片描述
这个status的值不仅仅表示退出码

• wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。
• 如果传递NULL,表⽰不关⼼⼦进程的退出状态信息。
• 否则,操作系统会根据该参数,将⼦进程的退出信息反馈给⽗进程。
• status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16⽐特位):

在这里插入图片描述

  • 高16位不用用,8~15位存的位退出状态,0~7位保存异常时对应的信号
    真正的退出码是status右移八位
    在这里插入图片描述

四、进程的程序替换

1、替换函数

在这里插入图片描述
在这里插入图片描述

  • 在程序替换的工程中,并没有创建新的进程
  • 只是:把当前进程的代码和数据用新的程序的代码和数据覆盖式的进行替换!
  • 1、一旦程序替换成功,就去执行新代码了,原始代码的后半部分,已经不存在了!
  • 2、exec*函数,只有失败返回值,没有成功返回值!
2、execl函数
 int execl(const char *path, const char *arg, ...);

第一个参数:路径+程序名,作用:我要执行谁
三个点表示可变参数列表,我想怎么执行它,命令行怎么写,你就怎么传

在这里插入图片描述

execl 后面 l 表示list 单链表的意思,所以最后一个参数必须时NULL

通过让子进程去完成程序替换,不影响父进程的代码:
在这里插入图片描述
为什么没有影响父进程呢?
1.进程具有独立性
2.数据和代码发生写时拷贝

程序替换也可以替换我们自己写的程序:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3、execlp
int execlp(const char *file, const char *arg, ...);

在这里插入图片描述

p代表PATH:只用告诉我你要执行的文件名就行了
因为execlp会自动在环境变量PATH中查找指定的命令

4、execv
 int execv(const char *path, char *const argv[]);

第一个参数:告诉程序的绝对或相对路径
这里的 v 表示:vector 提供一个命令行参数表,就是一个指针数组

在这里插入图片描述

5、execvp
int execvp(const char *file, char *const argv[]);

第一个参数告诉我你要执行的文件名就行了会自动在环境变量PATH中查找指定的命令
第二个参数传入一个vector的指针数组

6、execvpe
 int execvpe(const char *file, char *const argv[],char *const envp[]);

v:代表传vector
p:不用传路径,通过找环境变量PATH中找
e:环境变量!

在这里插入图片描述
在这里插入图片描述

p:默认自己找,传了路径就按传的路径找

7、putenv

在这里插入图片描述
那个进程调用它就在谁的环境变量列表里新增一个环境变量

在这里插入图片描述
在这里插入图片描述

五、⾃主Shell命令⾏解释器

1、chdir 更改工作路径

在这里插入图片描述
那个进程调用就更改那个进程的路径,把新的路径传进来就行了

2、getcwd() 获取进程当前工作路径

在这里插入图片描述

3、自主shell模拟实现代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

#define MAXARGC 128
int g_argc = 0;
char* g_argv[MAXARGC];

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

// last exit code 
int lastcode = 0;

const char* GetUserName()
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

char cwd[1024];
char cwdenv[1024];
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);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()
{
    const char *home = getenv("HOME");
    return home == NULL ? NULL : home;
}
const char* DirName(const char* pwd)
{
    if(strcmp("/",pwd) == 0)
    {
        return "/"; 
    }

    int n = strlen(pwd);
    
    const char* ret = nullptr;

    for(int i = n - 1; i >= 0; i--)
    {
        if(pwd[i] == '/')
        {
            ret = pwd + i + 1;
            break;
        }
    }
    return ret;
}

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


void PrintCommandPrompt()
{
    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;//清理\n
    if(strlen(out) == 0) return false;
    return true;
}

bool CommandParse(char *commandline)
{
    
    //命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    //切割
#define SEP " "
    g_argc = 0;

    g_argv[g_argc++] = strtok(commandline,SEP);

    while((bool)(g_argv[g_argc++] = strtok(nullptr,SEP)));
    g_argc--;
    return g_argc > 0 ? true:false;
}

void PrintArgv()
{
    for(int i = 0; g_argv[i]; i++)
    {
        printf("g_argv[%d]:%s\n",i,g_argv[i]);
    }
    printf("argc:%d\n",g_argc);
}
bool Cd()
{
    
    if(g_argc == 1)
    {
        const char* home = GetHome();
        if(home == NULL) return true;
        chdir(home);
    }
    else 
    {
        std::string where = g_argv[1];
        if(where == "-")
        {
            // 回到家目录
        }
        else if(where == "~")
        {
            // 回到上一次目录
        }
        else 
        {
            chdir(where.c_str());
        }
    }
    return true;
}

bool Echo()
{
    if(g_argc == 2)
    {
        // echo "hello world"
        // echo $?
        // echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            printf("%d\n",lastcode);
            lastcode = 0;
            return true;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
            {
                std::cout << env_value <<std::endl;
                lastcode = 0;
                return true;
            }
        }
    }
    return false;
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(strcmp(g_argv[0],"cd") == 0)
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        if(Echo())
            return true;
    }
    else if(cmd == "export")
    {

    }
    else if(cmd == "alias")
    {

    }
    return false;
}

int Execute()
{

    pid_t id = fork();
    if(id == 0)
    {
        //child
       execvp(g_argv[0],g_argv); 
        exit(0);
    }

    int status;
    // father
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);
    }
    return 0;
}

void InitEnv()
{
    extern char **environ;
    memset(g_env,0,sizeof(g_env));
    g_envs = 0;

    // 本来是要从配置文件来
    for(int i = 0; environ[i]; i++)
    {
        // 1.1 申请空间
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        if(g_env[i] == NULL)
        {
            exit(1);
        }
        strcpy(g_env[i],environ[i]);
        g_envs++;
    }
    g_env[g_envs] = NULL;

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


void cleannup()
{
    for(int i = 0; i < g_envs; i++)
    {
        free(g_env[i]);
        g_env[i]=NULL;
    }
}

int main()
{
    // shell 启动的时候,从系统中获取环境变量
    // 我们的环境变量信息应该从父shell统一来
    InitEnv();

    while(true)
    {

        // 1. 输出命令行提示符
        //printf("[%s@%s %s]# ",GetUserName(),GetHostName(),GetPwd());
        PrintCommandPrompt();
    
        // 2. 获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline,sizeof(commandline)))
        {
            continue;
        }
        //printf("echo %s\n",commandline);
        
        // 3. 命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))
            continue;
        //PrintArgv();
        
        // 检查别名
        // 4.检查并处理内键命令
        if(CheckAndExecBuiltin())
            continue;


        // 5.执行命令
        Execute();

    }
    
    //清理内存空间
    cleannup();
    return 0;
}

原文地址:https://blog.csdn.net/2401_83305953/article/details/144032025

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!