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

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

struct rlimit rlim[RLIM_NLIMITS]; 
#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的定义如下

struct rlimit {
	// 当前的限制
	long	rlim_cur;
	// 最大的限制,即调整值不能大于max
	long	rlim_max;
};

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

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信号。然后被终止。我们看看代码的实现。

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);
	}
// 达到硬限制,发送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。

	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代表一个进程能打开文件个数的最大值。触发校验的时机是打开一个文件的时候。

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;
	...
}

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章