Linux进程控制(个人笔记)


1.进程创建

1.1fork函数

在Linux中fork函数,从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做的事:

  1. 分配新的内存块和内核数据结构给子进程
  2. 将父进程部分数据结构内容拷贝至子进程
  3. 添加子进程到系统进程列表当中
  4. fork返回,开始调度器调度
    当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。

1.2写时拷贝

父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
在这里插入图片描述

2.进程终止

2.1进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

2.2进程退出方法

正常终止(可以通过 echo $? 查看进程退出码)

  1. main函数的return返回
  2. 调用exit
  3. 调用_exit
    异常退出:
    ctrl+c,信号终止

2.3_exit函数和exit函数的区别

  1. exit是库函数,_exit是系统调用
  2. exit终止进程的时候,会自动刷新缓存区。_exit终止进程的时候,不会自动刷新缓冲区。
    在这里插入图片描述
    return退出是一种常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当作exit的参数

3.进程等待

通过wait/waitpid的方式,让父进程对子进程进行资源回收的等待过程
解决子进程僵尸问题带来的内存泄露问题
父进程为什么要创建子进程?要让子进程来完成的如何,父进程要不要知道?要知道----需要通过进程等待的方式,获取子进程退出的信息—两个数字!—不是必须的,但是操作系统需要提供这样的基础功能!
wait的作用
能让进程等待能回收子进程僵尸状态,Z->x
如果子进程根本没有退出,父进程必须在wait上进行阻塞等待,直到子进程僵尸,wait自动回收,返回!

3.1进程等待的方法

wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
//返回值:
// 成功返回被等待进程pid,失败返回-1。
//参数:
// 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
//返回值:
// 当正常返回的时候waitpid返回收集到的子进程的进程ID;
// 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
// 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
//参数:

// pid:
// Pid=-1,等待任一个子进程。与wait等效。
// Pid>0.等待其进程ID与pid相等的子进程。

// status:
// WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
// WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

// options:
// WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退
出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。

3.2获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
位)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

//int status = 0;
void Worker(int number)
{
    int *p = NULL;
    int cnt = 10;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d, number: %d\n", getpid(), getppid(), cnt--, number);
        sleep(1);

        //*p = 100;
        //int a = 10;
        //a /= 0;
    }
}
const int n = 10;


int main()
{
    for(int i = 0;i < n; i++)
    {
        pid_t id = fork();
        if(id == 0)
        {
            Worker(i);
            //status = i;
            exit(0);
        }
    }

    //等待多个子进程?
    for(int i = 0; i < n; i++)
    {
        int status = 0;
        pid_t rid = waitpid(-1, &status, 0); // pid>0, -1:任意一个退出的子进程
        if(rid > 0){
            printf("wait child %d success, exit code: %d\n", rid, WEXITSTATUS(status));
        }
    }
    return 0;
}

3.3父进程等待子进程的状态

在这里插入图片描述

options有两种阻塞方式:

  1. 0:阻塞等待
  2. WNOHANG:等待的时候,以非阻塞的方式等待!

阻塞式调用,子进程不退出,wait不返回

如果我们等待的条件不满足,wait/waitpid不阻塞,而是立即返回!!非阻塞等待,往往要进行重复调用,轮询+非阻塞方案==非阻塞轮询方案,进行进程等待!!—好处?当我们进行等待的过程中,可以顺便做一下自己的事,占据时间并不多的事情!!!

4.进程程序替换

4.1替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

环境变量被子进程继承下去是一种默认行为,不受程序替换的影响,其原因是通过地址空间可以让子进程继承父进程的环境变量数据,程序替换只替换新程序的代码和数据,环境变量不会被替换

子进程执行的时候,获得的环境变量有以下三种情况:

  1. 将父进程的环境变量原封不动传递给子进程
  2. 可以直接构造环境变量表,给子进程传递
  3. 新增传递

4.2替换函数

有六种以exec开头的函数,统称exec函数:

#include <unistd.h>`
//库函数
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
//系统调用
int execve(const char *path, char *const argv[], char *const envp[]);

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

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

#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);

    //带v的需要自己组装命令行参数数组
	execv("/bin/ps", argv);
	
	// 带p的,可以使用环境变量PATH,无需写全路径
	execvp("ps", argv);
	// 带e的,需要自己组装环境变量
	execve("/bin/ps", argv, envp);
	exit(0);
}

5.简易的shell

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define NUM 1024
#define SIZE 64
#define SEP " "
//#define Debug 1

char cwd[1024];
char enval[1024]; // for test
int lastcode = 0;

char *homepath()
{
    char *home = getenv("HOME");
    if(home) return home;
    else return (char*)".";
}

const char *getUsername()
{
    const char *name = getenv("USER");
    if(name) return name;
    else return "none";
}
const char *getHostname()
{
    const char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "none";
}
const char *getCwd()
{
    const char *cwd = getenv("PWD");
    if(cwd) return cwd;
    else return "none";
}
int getUserCommand(char *command, int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin); // 最终你还是会输入\n
    if(r == NULL) return -1;
    // "abcd\n" "\n"
    command[strlen(command) - 1] = '\0';
    return strlen(command);
}

void commandSplit(char *in, char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while( out[argc++] = strtok(NULL, SEP));

#ifdef Debug
    for(int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

int execute(char *argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0) //child
    {
        // exec command
        execvp(argv[0], argv); // cd ..
        exit(1);
    }
    else // father
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0){
            lastcode = WEXITSTATUS(status);
        }
    }

    return 0;
}

void cd(const char *path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp, sizeof(tmp));
    sprintf(cwd, "PWD=%s", tmp); // bug
    putenv(cwd);
}

// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char *argv[])
{
    if(strcmp(argv[0], "cd") == 0)
    {
        char *path = NULL;
        if(argv[1] == NULL) path=homepath();
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if(strcmp(argv[0], "export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);
        putenv(enval); // ???
        return 1;
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        if(argv[1] == NULL){
            printf("\n");
            return 1;
        }
        if(*(argv[1]) == '$' && strlen(argv[1]) > 1){ 
            char *val = argv[1]+1; // $PATH $?
            if(strcmp(val, "?") == 0)
            {
                printf("%d\n", lastcode);
                lastcode = 0;
            }
            else{
                const char *enval = getenv(val);
                if(enval) printf("%s\n", enval);
                else printf("\n");
            }
            return 1;
        }
        else {
            printf("%s\n", argv[1]);
            return 1;
        }
    }
    else if(0){}

    return 0;
}

int main()
{
    while(1){
        char usercommand[NUM];
        char *argv[SIZE];
        // 1. 打印提示符&&获取用户命令字符串获取成功
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if(n <= 0) continue;
        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);
        // 3. check build-in command
        n = doBuildin(argv);
        if(n) continue;
        // 4. 执行对应的命令
        execute(argv);
    }
}
  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

索隆43

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值