十、进阶 | Linux 内核中的系统调用(6)

Linux中的资源限制

系统中的每个进程都会使用一定数量的不同资源,比如文件、CPU时间、内存等。

这些资源并不是无限的,每个进程都应该有一个工具来管理它们。有时了解某个资源的当前限制或更改其值是有用的。在这篇文章中,我们将考虑这样的工具,它们允许我们获取有关进程限制的信息,并增加或减少这些限制。

我们将从用户空间视图开始,然后看看它在Linux内核中的实现。

有三个主要的基本系统调用来管理进程的资源限制:

  • getrlimit
  • setrlimit
  • prlimit

前两个允许进程读取和设置系统资源的限制。最后一个是对前面函数的扩展。prlimit允许设置和读取由PID指定的进程的资源限制。这些函数的定义如下:

getrlimit是:

int getrlimit(int resource, struct rlimit *rlim);

setrlimit是:

int setrlimit(int resource, const struct rlimit *rlim);

prlimit的定义是:

int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
            struct rlimit *old_limit);

在前两种情况下,函数接受两个参数:

  • resource - 表示资源类型(稍后我们将看到可用类型);
  • rlim - softhard限制的组合。

有两种类型的限制:

  • soft
  • hard

第一个提供了进程资源的实际限制。第二个是soft限制的上限值,只能由超级用户设置。因此,soft限制永远不能超过相关的hard限制。

这两个值都结合在rlimit结构中:

struct rlimit {
    rlim_t rlim_cur;
    rlim_t rlim_max;
};

最后一个函数看起来有点复杂,接受4个参数。除了resource参数,它还接受:

  • pid - 指定应在其上执行prlimit的进程ID;
  • new_limit - 如果不是NULL,则提供新的限制值;
  • old_limit - 如果不是NULL,则当前的softhard限制将放在这里。

正是prlimit函数被ulimit实用工具使用。我们可以用strace实用工具来验证这一点。

例如:

~$ strace ulimit -s 2>&1 | grep rl

prlimit64(0, RLIMIT_NPROC, NULL, {rlim_cur=63727, rlim_max=63727}) = 0
prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=1024, rlim_max=4*1024}) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0

在这里我们可以看到prlimit64,而不是prlimit。事实上,我们在这里看到的是底层系统调用,而不是库调用。

现在让我们来看看可用资源的列表:

资源 描述
RLIMIT_CPU 以秒为单位给出的CPU时间限制
RLIMIT_FSIZE 进程可能创建的文件的最大大小
RLIMIT_DATA 进程的数据段的最大大小
RLIMIT_STACK 进程堆栈的最大大小(以字节为单位)
RLIMIT_CORE 核心 文件的最大大小
RLIMIT_RSS 进程在RAM中可以分配的字节数
RLIMIT_NPROC 用户可以创建的最大进程数
RLIMIT_NOFILE 进程可以打开的最大文件描述符数量
RLIMIT_MEMLOCK 通过mlock锁定到RAM的内存最大字节数
RLIMIT_AS 虚拟内存的最大大小(以字节为单位)
RLIMIT_LOCKS 最大flock和锁定相关的fcntl调用次数
RLIMIT_SIGPENDING 可以为调用进程的用户排队的信号的最大数量
RLIMIT_MSGQUEUE 可以为POSIX消息队列分配的字节数
RLIMIT_NICE 进程可以设置的最大nice
RLIMIT_RTPRIO 最大实时优先级值
RLIMIT_RTTIME 进程可以在实时调度策略下被调度的最大微秒数,而无需执行阻塞系统调用

如果你查看开源项目的源代码,你会发现读取或更新资源限制是一个相当广泛使用的操作。

例如:systemd

/* 不限制coredump大小 */
(void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY));

haproxy:

getrlimit(RLIMIT_NOFILE, &limit);
if (limit.rlim_cur < global.maxsock) {
	Warning("[%s.main()] FD限制(%d)太低,maxconn=%d/maxsock=%d。请将'ulimit-n'提高到%d或更高以避免任何问题。\n",
		argv[0], (int)limit.rlim_cur, global.maxconn, global.maxsock, global.maxsock);
}

我们刚刚看到了用户空间中与资源限制相关的一些内容,现在让我们看看Linux内核中的相同系统调用。

Linux内核中的资源限制

getrlimit系统调用和setrlimit的实现看起来相似。它们都执行do_prlimit函数,这是prlimit系统调用的核心实现,从用户空间复制给定的rlimit

getrlimit

SYSCALL_DEFINE2(getrlimit, unsigned int, resource, struct rlimit __user *, rlim)
{
	struct rlimit value;
	int ret;

	ret = do_prlimit(current, resource, NULL, &value);
	if (!ret)
		ret = copy_to_user(rlim, &value, sizeof(*rlim)) ? -EFAULT : 0;

	return ret;
}

setrlimit

SYSCALL_DEFINE2(setrlimit, unsigned int, resource, struct rlimit __user *, rlim)
{
	struct rlimit new_rlim;

	if (copy_from_user(&new_rlim, rlim, sizeof(*rlim)))
		return -EFAULT;
	return do_prlimit(current, resource, &new_rlim, NULL);
}

这些系统调用的实现定义在kernel/sys.c内核源代码文件中。

首先do_prlimit函数执行一个检查,以确保给定的资源是有效的:

if (resource >= RLIM_NLIMITS)
	return -EINVAL;

如果失败,则返回-EINVAL错误。在此检查成功通过并且新的限制作为非NULL值传递后,进行以下两个检查:

if (new_rlim) {
	if (new_rlim->rlim_cur > new_rlim->rlim_max)
		return -EINVAL;
	if (resource == RLIMIT_NOFILE &&
			new_rlim->rlim_max > sysctl_nr_open)
		return -EPERM;
}

检查给定的soft限制是否没有超过hard限制,并且如果给定的资源是最大文件描述符数量,则hard限制不大于sysctl_nr_open值。sysctl_nr_open的值可以通过procfs找到:

~$ cat /proc/sys/fs/nr_open
1048576

在所有这些检查之后,我们锁定tasklist以确保在更新给定资源的限制时,与[信号](https://en.wikipedia.org/wiki/Signal_(操作系统)处理程序相关的内容不会被破坏:

read_lock(&tasklist_lock);
...
...
...
read_unlock(&tasklist_lock);

我们需要这样做,因为prlimit系统调用允许我们通过给定的pid更新另一个任务的限制。由于任务列表被锁定,我们获取负责给定进程给定资源限制的rlimit实例:

rlim = tsk->signal->rlim + resource;

其中tsk->signal->rlim只是一个表示某些资源的struct rlimit数组。如果new_rlim不是NULL,我们只是更新它的值。如果old_rlim不是NULL,我们填充它:

if (old_rlim)
    *old_rlim = *rlim;

就是这样。

结论

这是描述Linux内核系统调用实现的第二部分的结尾。如果您有任何问题或建议,请在Twitter上联系我0xAX,给我发一封电子邮件,或者在问题中提出。

请注意,英语不是我的母语,如果有任何不便,我深表歉意。如果您发现任何错误,请向我发送linux-insides的PR。

链接

Linux嵌入式必考必会 文章被收录于专栏

&quot;《Linux嵌入式必考必会》专栏,专为嵌入式开发者量身打造,聚焦Linux环境下嵌入式系统面试高频考点。涵盖基础架构、内核原理、驱动开发、系统优化等核心技能,实战案例与理论解析并重。助你快速掌握面试关键,提升竞争力。

全部评论

相关推荐

点赞 评论 收藏
分享
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务