关于【setrlimit函数在设置RLIMIT_AS与进程替换】和【重定向与异常信息】的两个问题及解释

前言

        写这篇博客的初衷,仅是记录自己遇到的问题和让自己逻辑自洽的解释。

        这是我在实现 【力扣和牛客的OJ子系统】 时遇到的问题,具体是在运行模块,因为我们都知道在OJ提交代码的时候,无非就是结果正确、结果错误、异常、运行超时、内存过大等,所以我在设置资源限制的时候,使用了setrlimit函数,也就只限制了进程在CPU调度的时间和允许使用地址空间的大小。但是就在设置进程地址空间大小时,出现了问题!并且在重定向stderr的时候,也出现了问题。

        在往下看的时候,必须要知道setrlimit函数,是在执行期间有效,并只对在调用该函数的以后的字段有效,并不会对调用该函数之前的字段产生限制

一、RLIMIT_AS与进程替换的问题

【问题一】

「设置的允许使用进程地址空间的大小 是 无法加载库文件的数据时」:

当我设置允许使用进程地址空间大小为1byte,实现进程替换的时候,出现了链接报错

【原因】

        setrlimit函数是在执行期间生效,不会对生效之前的资源做限制,只对生效之后的字段做限制。在子进程调用该函数的时候,已经加载过的数据,是不会被影响的。

        而在程序替换的时候,是将新程序的 代码和数据使用到的库 重新覆盖到地址空间上的。所以当我设置很小的空间限制,就会出现动态库加载不完整,链接报错的情况

【问题二】

        「设置的允许使用进程地址空间的大小 可以完全加载库文件数据,但是无法完全加载程序代码和数据」:

        当我提高了允许使用进程地址空间的大小,实现进程替换的时候,出现了被SIGSEGV信号(11号信号)杀死的情况

【原因】

        此时空间限制提高到,新程序的库可以完全加载,但是代码和数据加载不完整的时候,而CPU在调度这个进程,是通过寄存器,一个用于解析,一个用于保存该条指令的下一条指令的地址。

        正好就是下一条指令,是没有被加载的,但是CPU不知道,CPU就会继续去拿这个未被加载内容的地址上的指令,就发生了越界访问,告诉操作系统是越界访问,OS发送SIGSEGV信号给当前进程,直接杀死!

        这也就表明了每个进程都要有一个表示该进程结束的指令,告诉CPU,CPU才会将其脱离调度。所以当加载不完整的代码和数据的时候,CPU就拿不到这个终止的指令,就拿到了没有内容的地址,就越界了。

【总结】

        所以我们推荐的是,在使用进程替换的时候,使用setrlimit时,一定要有足够的空间来满足替换后的程序的库文件、代码和数据都完整的加载,防止出现不完整,使得运行出现问题。

(切记堆栈上都是程序运行时申请的,我们代码虽然那么写,但是只是将指令的长度加载到地址空间,真当申请堆栈空间时,是CPU执行到这条指令时才申请的,这也就是为什么不是一开始就内存不足了,而是运行过程中出现的不足)

二、 重定向与异常信息的问题

【问题】

        在使用dup2重定向stderr时,会发现有的异常信息可以重定向,有的却不可以

【测试程序】

#include <iostream>
#include <vector>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void SetRlimit(int cpu, int as)
{
    // 1. 设置运行时长上限
    struct rlimit r_cpu;
    r_cpu.rlim_cur = cpu;
    r_cpu.rlim_max = RLIM_INFINITY;
    setrlimit(RLIMIT_CPU, &r_cpu);

    // 2. 设置可占用内存上限
    rlimit r_as;
    r_as.rlim_cur = as * 1024 * 1024;
    r_as.rlim_max = RLIM_INFINITY;
    setrlimit(RLIMIT_AS, &r_as);
}
int main()
{
    umask(0);
    int fd = open("debug.stderr", O_CREAT | O_WRONLY, 0644);
    dup2(fd, stderr->_fileno);
    SetRlimit(1, 20);
    while(1)
    {
        int* arr = new int[1024 * 1024 * 1024];
    }
    return 0;
}

【结果截图】

我们发现有的异常信息可以重定向到stderr文件中,有的异常信息则不可以

【解释】

" Aborted (core dumped) "

        是由操作系统或调试器生成的信息,表示进程因为某种异常情况(比如内存访问错误)而被操作系统终止,并且生成了一个核心转储文件(core dump)。

" terminate called after throwing an instance of 'std::bad_alloc "

        是由程序中的异常处理部分生成的消息,表明程序在尝试分配内存时抛出了 std::bad_alloc 异常。

【必须明确】

  • "Aborted (core dumped)"这条信息是操作系统生成的信息
  • "terminate called after throwing an instance of 'std::bad_alloc'"程序内部异常处理部分生成的信息
  • 在dup2重定向之前,操作系统和进程指向的是同一个struct file流,但重定向之后,进程的stderr就被重定向到file.stderr这个struct file中了,操作系统不变

        "Aborted (core dumped)" 这条信息通常是由操作系统生成的,而不是程序本身生成的输出。操作系统的fd没有被重定向,因此,无法将其重定向到文件中。

        "terminate called after throwing an instance of 'std::bad_alloc'" 这条信息则是由程序生成的标准错误输出,可以被重定向到文件中。

        而为什么达到CPU的资源限制时,进程没有抛异常,是因为你能使用的CPU资源都没了,根本就不允许你继续抛异常,所以就让操作系统来直接发送信号杀死该进程,这也就是为什么,每个信号都有不同名字的原因,其实也是告诉用户,你被哪个信号杀死了,当有些异常不允许进程自己抛出异常信息的时候,名字也是异常原因。

        (如:使用子进程进行程序替换的时候,我们发生了SIGXCPU异常,我们无法看到操作系统的异常输出,所以只能通过父进程waitpid的时候获得子进程退出信息,从而得知子进程被哪个信号杀死了,名字就是原因!)

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

终将向阳而生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值