Linux的进程| 雨打湿了眼眶
Cobb 711 1

fork新体验

知识储备 备注: 进程的执行没有先后顺序 1、 什么是进程?? 可执行程序运行以后成为了进程,每个进程在内核上都维护了一个叫PCB进程控制块的数据结构,记录了进程的所有信息。 也是因为每个进程都有一个PCB,进程才能受Linux内核调度。 2、 perror(). 任意的系统API出错,都会把错误码写入一个全局变量errno中,perror()打印出errno的具体信息。 3、 操作系统划分资源的单位:进程控制块(PCB),Linux内核代码中用task_struct表示PCB, 每个进程都有独立的地址空间。

int main()
{
    int data = 10;
    // fork创建一个子进程
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        exit(-1);
    }

    // 父进程返回子进程的pid,子进程返回0
    // 父进程执行的pid > 0, 子进程执行的pid = 0;
    if (pid > 0)
    {
        // 父进程执行的地方
        cout << "fathre pid:" << getpid() << endl;
        data++;
        sleep(100); 
    }
    else
    {
        // 子进程执行的地方
        cout << "child pid:" << getpid() << endl;
        data++;
        sleep(100);
    }
    cout << "data:" << data << endl;
}

僵尸进程

知识储备 僵尸进程的原理: ------子进程先结束了,但是它的父进程没有结束,此时子进程虽然结束了,但是子进程内核的PCB并没有释放,涉及资源泄露,系统的僵尸进程如果过多,会导致系统无法创建更多的进程出来 --------如果父进程先结束了,子进程的父进程会被内核修改成init 1号进程。 init进程的作用: 1. 是启动Linux系统必要的服务进程 2. 回收僵尸进程的PCB资源

解决僵尸进程的方案: 1、创建出来的子进程的父进程应该是init进程,子进程执行完,会自动回收资源 2、父进程调用wait() 或 waitpid(),等待子进程结束。这两个函数读取了子进程的PCB信息(包含了子进程的退出状态,子进程就可以释放资源了)


int main()
{
    // fork创建一个子进程
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        exit(-1);
    }

    if (pid > 0)
    {
        // 父进程执行的地方
        cout << "father pid:" << getpid() << endl;

        // wait(NULL) => waitpid(-1, NULL, 0) 等待任一子进程结束,该函数返回
        // 阻塞等待子进程执行完,子进程就可以直接释放PCB资源,防止僵尸进程出现
        waitpid(pid, NULL, 0);
        //cout << "hehe" << endl;
        sleep(100);
    }
    else 
    {
        // 子进程执行的地方
        cout << "child pid:" << getpid() << endl;
        //sleep(10);
        //exit(0);
    }
}

单向通信的匿名管道

知识储备 1、为什么无法直接共享数据?因为进程的用户空间是完全独立的。

2、进程之间的通信方式: ---匿名管道 pipe :进程的内核空间是共享的,所以匿名管道就是在内核空间分配一块内存,父子进程一人写,一人读,半双工通信。 ---UNIX domain socket : 全双工通信 ---命名管道、---消息队列、---共享内存、---信号量 的方式,这几个效率低,代码复杂,使用少。

3、定义宏: 最好是定义为do{}while();格式,定义内容可能是多行的,防止使用if语句的时候,没有加{},宏展开后导致出错

4、read和write第一个参数如果是数字表示: ** 0:标准输入 STDIN ** 1:标准输出 STDOUT ** 2:标准错误 STDERR

#define CHECK_ERR(str) \
    do \
    { \
        perror(str); \
        exit(-1); \
    } while (0)

// 单项通信的匿名管道
int main()
{
    // 默认0号用来读,1号用来写
    int pipefds[2] = {0};

    // 在内核上创建一个匿名管道,成功会返回两个文件描述符,
    // 放入参数数组中,0号负责read,1号负责write
    if (pipe(pipefds) < 0)
        CHECK_ERR("pipe err");

    pid_t pid = fork();
    if (pid < 0)
        CHECK_ERR("fork err");

    // 实现:父进程write,子进程read
    if (pid > 0)
    {
        // father只做write,所以read没有用
        close(pipefds[0]);
        write(pipefds[1], "hello child!", strlen("hello child!"));
        close(pipefds[1]);
        // 等待子进程结束 第一个参数pid等待指定子进程结束,-1表示等待任意子进程结束
        waitpid(-1, NULL, 0);
    }
    else
    {
        // child只做read,所以write没有用
        close(pipefds[1]);

        char buf[1024] = {0};
        // read会阻塞进程,直到管道中有可读的数据,子进程才继续往下运行
        read(pipefds[0], buf, 1024);
        write(1, buf, 1024);

        close(pipefds[0]);
    }
}

UNIX domain socket双向通信

知识储备 做项目的时候发现需要重新理解一下socketpair : socketpair 是父进程写,子进程能直接收到,子进程写,父进程能直接收到. 是这样的: sv[0],能读能写,在父/子中使用一个就行 创建一个双向管道,两个进程,一边拿一个就可以了 不用区分0是读,1是写了。。。


#define CHECK_ERR(str) \
    do \
    {  \
        perror(str); \
        exit(-1); \
    } while (0)


// UNIX domain socket只是在本地工作,效率高。   socketpair实现进程间的双向通信
int main()
{
    int sv[2] = {0};

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
        CHECK_ERR("socketpair err");

    pid_t pid = fork();
    if (pid < 0)
        CHECK_ERR("fork err");

    // 实现:1 - 1亿 之间的数据和,父子分工,一人计算一半。
    // 父亲用sv[0]  好儿子用sv[1]
    if (pid > 0)
    {
        // father告诉子进程两个数
        int begin = 50000001;
        int end = 100000000;
        string sendstr = std::to_string(begin) + "|" + std::to_string(end);
        // num1|num2
        write(sv[0], sendstr.c_str() , sendstr.size());

        // 自己计算0 - begin-1的和
        unsigned long long sum = 0;
        for (int i = 1; i < begin; i++)
        {
            sum += i;
        }

        // 等待子进程发送过来的计算结果
        char buf[100] = {0};
        read(sv[0], buf, 100);
        sum += std::stoull(buf);

        cout << "sum:" << sum << endl;

        close(sv[0]);
        // 等待子进程结束 第一个参数pid等待指定子进程结束,-1表示等待任意子进程结束
        waitpid(-1, NULL, 0);
    }
    else
    {
        char buf[100] = {0};
        read(sv[1], buf, 100);   // 一上来先阻塞这里
        string str = buf;
        int pos = str.find('|');
        string str1 = str.substr(0, pos);
        string str2 = str.substr(pos+1, str.size()-pos-1);

        unsigned long long sum = 0;
        for (int i = stoi(str1); i <= stoi(str2); i++)
        {
            sum += i;
        }

        write(sv[1], to_string(sum).c_str(), to_string(sum).size());

        close(sv[1]);
    }
}

execve 叛徒

知识储备 在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。 exec函数一共有六个,其中execve为内核级系统调用, 其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。

fork是分身,execve是变身。 exec系列的系统调用是把当前程序替换成要执行的程序, 而fork用来产生一个和当前进程一样的进程(虽然通常执行不同的代码流)。 通常运行另一个程序,而同时保留原程序运行的方法是,fork+exec。

替换子进程的.text .data .bss段

// 随便写个sort文件,跑起来
int main()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        exit(-1);
    }

    if (pid > 0)
    {
        // 父进程执行的地方
        cout << "father pid:" << getpid() << endl;
        waitpid(pid, NULL, 0);
    }
    else // pid == 0
    {
        // 子进程执行的地方
        cout << "child pid:" << getpid() << endl;
        // execve在这里用sort程序的.text .data .bss替换了子进程相应的image(镜像)
        // execve成功后,就从sort程序的main函数开始运行了
        if (execve("./sort", NULL, NULL) < 0)
        {
            perror("execve err");
        }
        // execve成功后,这里后面的代码就不执行了
    }

    cout << "main done..." << endl;
}
评论区

索引目录