如今的操作系統都是支持多任務、多用戶的,計算機的資源是各個用戶和任務共享的。操作系統通過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;
...
}
這個版本的內核實現的限制控制不多,現代版本複雜了很多。今天就先分析到這。