关于进程使用资源的限制(基于linux1.2.13)

2023-10-30 15:50:13 浏览数 (1)

如今的操作系统都是支持多任务、多用户的,计算机的资源是各个用户和任务共享的。操作系统通过setrlimit系统调用提供控制资源使用的方法。该函数的实现在各版本的内核里不尽相同,现在也支持了更多的能力,本文通过1.2.13的内核大致分析资源使用限制的一些原理。 首先在PCB中加了一个字段记录了限制信息。

代码语言:javascript复制
struct rlimit rlim[RLIM_NLIMITS]; 
代码语言:javascript复制
#define RLIMIT_CPU    0       /* CPU time in ms */
#define RLIMIT_FSIZE    1       /* Maximum filesize */
#define RLIMIT_DATA    2       /* max data size */
#define RLIMIT_STACK    3       /* max stack size */
#define RLIMIT_CORE    4       /* max core file size */
#define RLIMIT_RSS    5       /* max resident set size */
#define RLIMIT_NPROC    6       /* max number of processes */
#define RLIMIT_NOFILE    7       /* max number of open files *

rlimit的定义如下

代码语言:javascript复制
struct rlimit {
    // 当前的限制
    long    rlim_cur;
    // 最大的限制,即调整值不能大于max
    long    rlim_max;
};

看完数据结构我们再看一下修改这个数据结构的函数。

代码语言:javascript复制
asmlinkage int sys_setrlimit(unsigned int resource, struct rlimit *rlim)
{
    struct rlimit new_rlim, *old_rlim;
    int err;

    if (resource >= RLIM_NLIMITS)
        return -EINVAL;
    err = verify_area(VERIFY_READ, rlim, sizeof(*rlim));
    if (err)
        return err;
    memcpy_fromfs(&new_rlim, rlim, sizeof(*rlim));
    old_rlim = current->rlim   resource;
    // 超级用户可以随便修改阈值,非超级用户修改值的时候不能大于之前设置的最大值
    if (((new_rlim.rlim_cur > old_rlim->rlim_max) ||
         (new_rlim.rlim_max > old_rlim->rlim_max)) &&
        !suser())
        return -EPERM;
    // RLIMIT_NOFILE代表进程能打开的文件大小,这个是操作系统本身的限制(NR_OPEN),无法突破
    if (resource == RLIMIT_NOFILE) {
        if (new_rlim.rlim_cur > NR_OPEN || new_rlim.rlim_max > NR_OPEN)
            return -EPERM;
    }
    *old_rlim = new_rlim;
    return 0;
}

看完资源限制的表示和设置方法,我们来看看各种限制的实现。

1 RLIMIT_CPU

RLIMIT_CPU代表某个进程使用CPU的时间限制,包括用户态的时间和内核态的时间。当进程的CPU使用时间达到rlim_cur的值的时候,他会收到SIGXCPU信号,这个信号默认的处理是终止进程,但是用户可以设置处理该信号的处理函数,防止进程退出。这样进程可以继续执行,往后的每5秒该进程都会受到SIGXCPU 信号,直到CPU时间达到rlim_max的值,进程会收到SIGKILL信号。然后被终止。我们看看代码的实现。

代码语言:javascript复制
if (
    (current->rlim[RLIMIT_CPU].rlim_cur != RLIM_INFINITY) &&
    (((current->stime   current->utime) % HZ) == 0)
) {
        psecs = (current->stime   current->utime) / HZ;
        // 达到软限制,发送信号
        if (psecs == current->rlim[RLIMIT_CPU].rlim_cur)
            send_sig(SIGXCPU, current, 1);
        // 超过了软限制,每5秒再次发送SIGXCPU信号
        else if ((psecs > current->rlim[RLIMIT_CPU].rlim_cur) &&
                ((psecs - current->rlim[RLIMIT_CPU].rlim_cur) % 5) == 0)
            send_sig(SIGXCPU, current, 1);
    }
代码语言:javascript复制
// 达到硬限制,发送SIGKILL终止进程
if ((current->rlim[RLIMIT_CPU].rlim_max != RLIM_INFINITY) &&
  (((current->stime   current->utime) / HZ) >= current->rlim[RLIMIT_CPU].rlim_max)
)
    send_sig(SIGKILL, current, 1);

2 RLIMIT_FSIZE

RLIMIT_FSIZE代表进程创建文件大小的限制。当进程创建文件的时候,会触发这个判断,如果达到了阈值。会返回-EFBIG错误码(文档注释说还会收到SIGXFSZ 信号,但是这个版本的内核没有实现)。

3 RLIMIT_DATA

RLIMIT_DATA代表数据使用空间的限制,包括数据段,bss段和堆。因为数据段和bss段在编译的时候已经确认大小,只有堆可以修改大小。所以在修改堆大小的时候会触发这个校验。brk系统调用可以修改堆的大小。

4 RLIMIT_STACK

RLIMIT_STACK代表栈的大小限制。比如我们在栈上访问了一个还没有映射到物理内存的虚拟地址,然后触发缺页中断,正常来说系统会映射一块物理内存到该虚拟地址,但是如果达到了阈值。则进程会收到SIGSEGV信号。

5 RLIMIT_RSS,

进程驻留内存的页数的大小限制

6 RLIMIT_NPROC

RLIMIT_NPROC代表当前进程所属的真实id对应的用户所能创建的最大进程数(线程)。触发校验的时机在fork。

代码语言:javascript复制
    this_user_tasks = 0;
    free_task = -EAGAIN;
    i = NR_TASKS;
    // 遍历所有进程,找出uid和当前进程进程的uid一样的
    while (--i > 0) {
        if (!task[i]) {
            free_task = i;
            tasks_free  ;
            continue;
        }
        if (task[i]->uid == current->uid)
            this_user_tasks  ;
    }
    // 达到阈值
    if (this_user_tasks > current->rlim[RLIMIT_NPROC].rlim_cur)
        if (current->uid)
            return -EAGAIN;

7 RLIMIT_NOFILE

RLIMIT_NOFILE代表一个进程能打开文件个数的最大值。触发校验的时机是打开一个文件的时候。

代码语言:javascript复制
int do_open(const char * filename,int flags,int mode)
{
    struct inode * inode;
    struct file * f;
    int flag,error,fd;
    // 找到一个可用的文件描述符,值小于NR_OPEN和RLIMIT_NOFILE(初始化的值大于NR_OPEN,表示用户没有设置过)。
    for(fd=0; fd<NR_OPEN && fd<current->rlim[RLIMIT_NOFILE].rlim_cur; fd  )
        // 还没被使用则找到可用的
        if (!current->files->fd[fd])
            break;
    // 找不到可用的
    if (fd>=NR_OPEN || fd>=current->rlim[RLIMIT_NOFILE].rlim_cur)
        return -EMFILE;
    ...
}

这个版本的内核实现的限制控制不多,现代版本复杂了很多。今天就先分析到这。

0 人点赞