關於進程使用資源的限制(基於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;
	...
}

這個版本的內核實現的限制控制不多,現代版本複雜了很多。今天就先分析到這。

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