Linux进程控制(改)

Linux进程控制

进程 = 内核数据结构(struct task_struct,struct mm_struct,页表)+ 代码和数据

在Linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程

1.进程创建

  • ./程序
  • fork()创建子进程

2.fork函数

功能创建一个新的进程
头文件#include <unistd.h>
原型pid_t fork(void);
返回值成功: 0 或者大于 0 的正整数,失败:-1
备注该函数执行成功之后,将会产生一个新的子进程,在新的子进程中其返回值为0 ,在原来的父进程中其返回值为大于 0 的正整数,该正整数就是子进程的PID

fork可以用来创建一个子进程,一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值,而父进程中返回子进程ID

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  1. 分配新的内存块和内核数据结构给子进程
  2. 将父进程部分数据结构内容拷贝至子进程
  3. 添加子进程到系统进程列表当中
  4. fork返回,开始调度器调度

fork调用失败的原因

  1. 系统中有太多的进程
  2. 实际用户的进程数超过了限制

3.进程终止(退出码和退出信号)

进程终止做的事:释放曾经代码和数据占据的空间,释放内核数据结构


进程正常终止时会返回进程的退出码(异常除外,无退出码):

  • 0:成功
  • !0:失败-------1.2.3.4.5.6….不同数字值代表不同失败原因对应相应的string错误信息

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

进程终止的3种情况

  1. 代码跑完,结果正确
  2. 代码跑完,结果错误
  3. 代码无法正确执行,出现异常(野指针…),这时候无法得到退出码,因此需要抛出异常

代码异常被操作系统终止本质是操作系统给进程抛出信号

例如: kill -l

在这里插入图片描述

如图所示退出信号

衡量一个进程退出,需要两个数字:退出码 + 退出信号!!


如何正常终止进程?

  1. 从main函数直接return返回
  2. 代码中调用void exit(int status)
  3. _exit:void _exit(int status);

exit()_exit()

前者会在进程退出时候冲刷缓冲区,后者不会

原因:前者是C库函数,后者是进行系统调用,而exit()实质上是调用_exit()这一个操作系统接口

如何异常终止进程?

  1. kill进程
  2. ctrl + c

任何子进程,在退出的情况下,一般必须被父进程进行等待

使进程退出的正确和错误方法:

  • 正确的方法
    • 在main函数中调用return:这会导致程序正常退出,并返回值给操作系统。
    • 在程序的任意位置调用exit接口exit函数会终止当前进程,并将控制权返回给操作系统。exit还会执行一些清理操作,比如调用注册的atexit函数,关闭所有标准I/O流等。
    • 在程序的任意位置调用_exit接口_exit_Exit函数会立即终止调用进程,但与exit不同,它不会执行任何清理操作,如关闭文件描述符,或刷新stdio缓冲区。
  • 错误的方法
    • 在程序的任意位置调用returnreturn语句只能用在函数内部来结束函数的执行,并返回一个值。如果在main函数中使用return,它会导致程序退出,但如果在其他函数中使用,它只会结束当前函数的执行,并不会导致进程退出。

4.进程等待的方法

wait

#include<sys/types.h>
#include<sys/wait.h>

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

waitpid

#include<sys/types.h>
#include<sys/wait.h>

pid_ t waitpid(pid_t pid, int *status, int options);

waitpid函数参数的含义:

  • pid:要等待的进程的ID。特定值有特殊含义:

    • 如果pid-1waitpid会等待任何子进程
    • 如果pid大于0waitpid会等待进程ID与之相匹配的特定子进程
    • 如果pid等于0waitpid会等待与调用进程相同进程组的任何子进程
    • 如果pid小于-1waitpid会等待进程组ID等于pid绝对值的任何子进程
  • status:一个指向整数的指针,用于存储结束的子进程的状态信息。通过这个参数,调用者可以得知子进程是如何结束的(例如,是正常退出还是因为信号而结束)

    • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出,查看退出信号)
    • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options:修改waitpid行为的选项。这些选项可以是以下几个值的组合:

    • WNOHANG即使没有子进程退出,它也会立即返回(非阻塞模式)
    • WUNTRACED:除了返回终止的子进程信息外,还返回因信号而停止的子进程信息
    • WCONTINUED:返回那些已经停止(因为job control信号)并且已经继续执行的子进程状态
  • waitpid函数的返回值是:

    • 如果成功,返回收集到状态信息的子进程的PID
    • 如果设置了WNOHANG且没有子进程退出,返回0
    • 如果出错,返回-1,并且errno会被设置为相应的错误代码

获取子进程status

  • wait,waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充
  • 如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
  • status不能简单的当作整形来看待,可以当作位图来看待

进程等待实列:

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

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork fail");
        exit(-1);
    }
    // child run
    if (id == 0)
    {
        printf("i am child process,pid = %d\n", getpid());
        sleep(5);
        // 子进程调用exit(0)来退出,并传递退出状态码0
        exit(0);
    }
    // father run and write the child status
    int status = 0;
    //使用wait方法
    pid_t ret = wait(&status);
    if (ret == -1)
    {
        perror("wait child fail");
        exit(-1);
    }
    // 如果进程正常退出,提取进程的退出码
    if (WIFEXITED(status))
    {
        // WEXITSTATUS(status)提取进程退出码
        // 当进程不是正常退出时候用WEXITSTATUS(status)是一种不可靠的行为
        printf("wait child exit success,exit_code:%d\n", WEXITSTATUS(status));
    }
    else
    {
        printf("wait child exit fail\n");
    }
    return 0;
}

进程非阻塞等待

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

int main()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork fail");
        exit(0);
    }
    else if (pid == 0)
    { 
        // child
        int cnt = 5;
        while (cnt--)
        {
            printf("child is run, pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    else
    {
        // father
        int status = 0;
        pid_t ret = 0;
        do
        {
            ret = waitpid(-1, &status, WNOHANG); // 非阻塞式等待
            if (ret == 0)
            {
                // 子进程还没结束
                printf("Do father things\n");
            }
            sleep(1);
        } while (ret == 0);
        // 
        if (WIFEXITED(status) && ret == pid)
        {
            printf("wait child success, child exit code:%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("wait child failed\n");
            return 1;
        }
    }
    return 0;
}

进程阻塞等待

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

int main()
{
    pid_t 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());
        int cnt = 5;
        while (cnt)
        {
            printf("child[%d] is running, cnt = %d\n", getpid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(1);
    }
    else
    {
        //father
        int status = 0;
        pid_t ret = waitpid(-1, &status, 0); // 阻塞式等待
        printf("wait child\n");
        if (WIFEXITED(status) && ret == pid)
        {
            printf("wait child success, child exit code:%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("wait child failed,\n");
            return 1;
        }
    }

    return 0;
}

5.进程程序替换

替换原理:

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

总结:老进程的壳子执行新程序的代码


替换函数:

其实有六种以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[]);

实质上前五种函数是系统调用接口execve的封装


函数解释

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

命名理解

  1. l(list) : 表示参数采用列表
  2. v(vector) : 参数用数组
  3. p(path) : 有p自动搜索环境变量PATH
  4. e(env) : 表示自己维护环境变量
  • 30
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jamo@

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

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

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

打赏作者

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

抵扣说明:

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

余额充值