摘要:本節要介紹一些有關進程的特殊操作。有了這些操作,就使得進程的編程更加完善,能編制更為實用的程序。主要的內容有得到關於進程的各種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。如果發出調用的進程的有效用戶ID而不是超級用戶,那麼內核將根據指定的參數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)。這樣使用的好處是,利於調試和安裝;也利於安全。 3.6 nice 系統調用 系統調用nice用來改變進程的優先權。該調用的聲明格式如下: int nice(int inc); 在使用該調用的程序中加入下面的頭文件: #include 調用nice將發出調用進程的優先權值增加inc大小。只有超級用戶才有權指定一個負的增加量inc。發出調用的進程的子進程都繼承該優先權。注意,進程的優先權值越低,優先權越高,即優先權值越低,調度上CPU的機會越大。所以只有root才能指定負值,一般用戶只能指定正值,該值降低了進程的優先權,使得進程使用更少的CPU時間。 該調用成功返回時,返回值為0;錯誤時,返回-1,並設相應的錯誤代碼: EPERM:非超級用戶指定參數inc為負值,企圖增加進程的優先權。 這個調用適用於你的程序需要長時間運行,你不希望它對別的進程影響過大。而且執行的快慢對你來說並不十分重要這種情況。