摘要:本節要介紹一些有關進程的特殊操作。有了這些操作,就使得進程的編程更加完善,能編製更為實用的程式。主要的內容有得到關於進程的各種ID、對進程的設定使用者ID、改變進程的工作目錄、改變進程的根、改變進程的優先權值等操作。
3.進程的特殊操作
上一節介紹了有關進程的一些基本操作,如進程的產生、進程的終止、進程執行映像的改變、等待子進程終止等。本節要介紹一些有關進程的特殊操作。有了這些操作,就使得進程的編程更加完善,能編製更為實用的程式。
主要的內容有得到關於進程的各種ID、對進程的設定使用者ID、改變進程的工作目錄、改變進程的根、改變進程的優先權值等操作。
3.1 獲得進程相關的ID
與進程相關的ID有:
真正使用者標識號(UID):該標識號負責標識運行進程的使用者。
有效使用者標識號(EUID):該標識號負責標識以什麼使用者身份來給新建立的進程賦所有權、檢查檔案的存取許可權和檢查通過系統調用kill向進程發送非強制中斷訊號的許可許可權。
真正使用者組標識號(GID):負責標識運行進程的使用者所屬的組ID。
有效使用者組標識號(EGID):用來標識目前進程所屬的使用者組。可能因為執行檔案設定set-gid位而與gid不同。
進程標識號(PID):用來標識進程。
進程組標識號(process group ID):一個進程可以屬於某個進程組。可以發送訊號給一組進程。注意,它不同與gid。前面的系統調用wait中指定參數pid時,就用到了進程組的概念。
如果要獲得進程的使用者標識號,用getuid調用。調用geteuid是用來獲得進程的有效使用者標識號。有效使用者ID與真正使用者ID的不同是由於執行檔案設定set-uid位引起的。這兩個調用的格式如下:
uid_t getuid(void);
uid_t geteuid(void);
在使用這兩個調用的程式中加入下列標頭檔:
#include
#include
如果要獲得運行進程的使用者組ID,使用getgid調用來獲得真正的使用者組ID,用getegid獲得有效使用者組ID。標識gid與egid的不同是由於執行檔案設定set-gid位引起的。這兩個調用的格式如下:
gid_t getgid(void);
gid_t getegid(void);
在使用這兩個調用的程式中加入下列標頭檔:
#include
#include
如果要獲得進程的ID,使用getpid調用;要獲得進程的父進程的ID,使用getppid調用。這兩個調用的格式如下:
pid_t getpid(void);
pid_t getppid(void);
在使用這兩個調用的程式中加入下列標頭檔:
#include
如果要獲得進程所屬組的ID,使用getpgrp調用;若要獲得指定PID進程所屬組的ID用getpgid調用。這兩個調用的格式如下:
pid_t getpgrp(void);
pid_t getpgid(pid_t pid);
在使用這兩個調用的程式中加入下列標頭檔:
#include
注意一下gid和pgrp的區別,一般執行該進程的使用者的組ID就是該進程的gid,如果該執行檔案設定了set_gid位,則檔案所屬的組ID就是該進程的gid。對於進程組ID,一般來說,一個進程在shell下執行,shell程式就將該進程的PID賦給該進程的進程組ID,從該進程派生的子進程都擁有父進程所屬的進程組ID,除非父進程將子進程的所屬組ID設定成與該子進程的PID一樣。由於這幾個調用使用很簡單,這裡就不再舉例。
3.2 setuid 和 setgid 系統調用
前面講述了如何得到uid和gid,現在來看看如何設定它們。在講述這兩個調用以前我們先來看看對檔案設定set_uid位會有什麼作用。我們先編了一個小程式來做實驗。這個程式的作用是,列印出進程的uid和euid,然後開啟一個名為tty.c的檔案。如果打不開,就顯示錯誤碼;如果開啟了,就顯示開啟成功。假設該程式名叫uid_test.c:
/* uid_test.c */
#include
#include
#include
#include
#include
#include
extern int errno;
int main()
{
int fd;
printf("This process's uid = %d, euid = %d ",getuid(),geteuid());
if ((fd = open("tty.c",O_RDONLY))==-1)
{
printf("Open error, errno is %d ",errno);
exit(1);
}
else
{
printf("Open success ");
}
}
下面列出這幾個檔案的目錄,可以看到檔案tty.c的存取許可權僅為屬主root可讀寫。
[wap@wapgw /tmp]$ ls -l
total 3
-rw------- 1 root root 0 May 31 16:15 tty.c
-rwxr-xr-x 1 root root 14121 May 31 16:15 uid_test
-rw-r--r-- 1 root root 390 May 31 16:15 uid_test.c
[wap@wapgw /tmp]$
在該系統中的使用者中個使用者wap(500),以root使用者身份執行程式:
[root@wapgw /tmp]# ./uid_test
This process's uid = 0, euid = 0
Open success
[root@wapgw /tmp]#
下面使用su命令,轉到使用者wap下,執行程式
[root@wapgw /tmp]#su wap
[wap@wapgw /tmp]$ ./uid_test
This process's uid = 500, euid = 500
Open error, errno is 13
[wap@wapgw /tmp]$
這是由於進程的uid是500(wap),對檔案tty.c沒有存取權,所以出錯。
給程式檔案設定set-uid位
[root@wapgw /tmp]# chmod 4755 uid_test
再轉到使用者wap下,執行程式uid_test。
[wap@wapgw /tmp]$ ./uid_test
This process's uid = 500, euid = 0
Open success
[wap@wapgw /tmp]$
從上面我們看到,進程列印出的euid是0(root),而運行該進程的使用者是500(wap)。由於進程的euid是root,所以成功開啟了檔案tty.c。
上面的例子說明了兩個事實:第一,核心對進程存取檔案的許可權的檢查,是通過檢查進程的有效使用者ID來實現的;第二,執行一個設定set_uid位的程式時,核心將進程表項中和u區中的有效使用者ID設定為檔案屬主的ID。為了區別進程表項中的euid和u區中的euid,我們將進程表項中的euid 域稱為儲存使用者標識號(saved user ID)。
下面我們來看看這兩個調用。調用的聲明格式如下:
int setuid(uid_t uid);
int setgid(gid_t gid);
在使用這兩個調用的程式中加入下面的標頭檔:
#include
調用setuid為當前發出調用的進程設定真正和有效使用者ID。參數uid是新的使用者標識號(該標識號應該在/etc/passwd檔案中存在)。如果發出調用的進程的有效使用者ID是超級使用者,核心將進程表中和u區中的真正使用者標識號和有效使用者標識號置為參數uid。如果發出調用的進程的有效使用者識別碼而不是超級使用者,那麼核心將根據指定的參數uid來執行,如果這時指定的參數uid的值是真正使用者標識號或者是儲存使用者標識號(saved user ID),則核心將u區中的有效使用者標識號改為參數uid,否則,該調用返回錯誤。該調用成功時,傳回值為0;發生錯誤時,返回-1,並設定相應的錯誤碼 errno,下面是經常可能發生的錯誤碼:
EPERM:使用者不是超級使用者,並且指定的參數uid與發出調用的進程的真正使用者ID或儲存使用者ID不匹配。
調用setgid設定當前發出調用的進程的真正、有效使用者組ID。該調用允許進程指定進程的使用者組ID為參數gid,如果進程的有效使用者ID不是超級使用者,該參數gid必須等於真正使用者組ID、有效使用者組ID中的一個。如果進程的有效使用者ID是超級使用者,可以指定任何存在的使用者組ID(在 /etc/group檔案中存在)。
注意: 對於setuid程式尤其要小心,當進程的euid是超級使用者時,如果將進程setuid到其他使用者,就無法再得到超級使用者的權力。我們可能這樣用這個調用,某個程式,開始需要root權力才能完成開始的工作,但後續的工作不需要root的權力,所以,我們將程式的執行檔案設定set_uid位,並使得執行檔案的屬主是root,這樣進程開始時,就具有了root的許可權,在不再需要root許可權的地方,用setuid(getuid)恢複進程的uid、 euid。對於可執行檔設定set_uid位,一定要注意,尤其是對那些屬主是root的更要注意。因為LINUX系統中root擁有任何權力。使用不當,會對系統安全有極大的損害。
3.3 setpgrp和setpgid 系統調用
這兩個調用是用來設定進程組ID的,其聲明格式如下:
int setpgrp(void);
int setpgid(pid_t pid, pid_t pgid);
在使用這兩個調用的程式中加入下面的標頭檔:
#include
調用setpgrp用來將發出調用的進程的進程組ID設定成與該進程的PID相等。注意,以後由這個進程派生的子進程都擁有該進程組ID(除非修改子進程的進程組ID)。
調用setpgid用來將進程號為參數pid的進程的進程組ID設定為參數pgid。如果參數pid為0,則修改發出調用進程的進程組ID。如果參數pgid為0,將進程號為pid的進程改為與發出調用的進程同組。如果不是超級使用者發出的調用,那麼被指定的進程必須與發出調用的進程有相同的 EUID,或者被指定的進程是發出調用進程的子進程。
進程組可用於訊號的發送,或者終端輸入的仲裁(與終端控制進程有相同的進程組ID且在前台可以讀取終端,其他進程在企圖讀的時候被阻塞並發送訊號給該進程)。
該調用成功時,傳回值為0;如果請求失敗,返回-1,並設定全域變數errno為對應的值。下面是可能遇到的錯誤碼:
ESRCH:參數pid指定的進程不存在。
EINVAL:參數pgid小於0。
EPERM:指定進程的EUID與發出調用進程的euid不同,且指定進程不是發出調用進程的子進程。
3.4 chdir 系統調用系統調用
chdir是用來將進程的當前工作目錄改為由參數指定的目錄。該調用的聲明格式如下:
int chdir(const char *path);
在使用該調用的程式中加入下面的標頭檔:
#include
使用該調用時要注意,發出該調用的進程必須對參數path指定的目錄有搜尋(execute)的權力。調用成功時,傳回值為0;錯誤時,返回-1,並設定相應的錯誤碼。
3.5 chroot 系統調用
系統調用chroot用來改變發出調用進程的根(“/”)目錄。該調用聲明的格式如下:
int chroot(const char *path);
在使用該調用的程式中加入下面的標頭檔:
#include
調用chroot將進程的根目錄改到由參數path所指定的地方。以後該進程中以“/”(根)開始的路徑,都從指定目錄處開始尋找。發出調用進程的子進程都繼承這個根目錄的位置。該調用只能由超級使用者(root)發出。注意,該調用並不改變當前工作目錄,所以有可能當前工作目錄“.”在根目錄“/” 之外。調用成功時,傳回值為0;錯誤時,返回-1,並設定相應的錯誤碼。
注意: 如果用chroot調用改變根後,不能由調用chroot(“/”)來返回真正的根,因為調用中的參數“/”會被理解成新設定的根。該調用一般可以用在 login程式中,或者現在國內常見的BBS系統等應用程式中,使用者登入後執行系統的一個程式,該程式將根改變成使用者登入的目錄(例如 /home/bbs)。這樣使用的好處是,利於調試和安裝;也利於安全。