2019獨角獸企業重金招聘Python工程師標準>>>
一、創建一個進程
進程是系統中最基本的執行單位。Linux系統允許任何一個用戶進程創建一個子進程,創建之后,子進程存在于系統之中并獨立于父進程。
關于父進程與子進程這兩個概念,除了0號進程以外(由系統創建),Linux系統中任何一個進程都是由其它進程創建的。創建新進程的進程,即調用fork()函數的進程就是父進程。
Linux中使用fork()函數創建一個新進程,函數原型如下:
#include <unistd.h>
pid_t fork(void);
fork()函數不需要參數,返回值是一個進程ID。對于返回值,有以下3中情況:
- 對于父進程,fork()函數返回新創建的子進程的ID。
- 對于子進程,fork()函數返回0.由于系統的0號進程是內核進程,所以子進程的進程號不可能是0,由此可區分父進程和子進程。
- 如果出錯,fork()函數返回-1.
fork()函數會創建一個新的進程,并從內核中為此進程得到一個新的可用進程ID。之后為這個新進程分配進程空間并將父進程的進程空間中的內容復制到子進程空間中,包括父進程的數據段和堆棧段,并且和父進程共享代碼段。
由于復制了父進程的堆棧段,所以兩個進程都停留在fork()函數中,等待返回。因此,fork()函數會返回兩次,一次是在父進程中返回,另一次是在子進程中返回,兩次的返回值是不一樣的。
下面示例創建一個子進程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[])
{pid_t pid;pid = fork();if(pid < 0) { //fork出錯printf("fail to fork.\n");exit(1);} else if(pid == 0) { //子進程printf("this is child, pid is : %u\n", getpid());} else {printf("this is parent, pid is : %u, child-pid is %u\n", getpid(), pid);}return 0;
}
運行結果:
?
二、父子進程的共享資源
子進程完全復制了父進程的地址空間的內容,包括堆棧段和數據段的內容。子進程并沒有復制代碼段,而是和父進程共用代碼段。
下面的實例定義了一個全局百變量、一個局部變量和一個指針。之后該程序創建一個子進程,在子進程中修改上面定義的值,并打印出來。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int global; //全局變量,在數據段中int main(int argc, char *argv[])
{pid_t pid;int stack = 1; //局部變量,在棧中int *heap;heap = (int *)malloc(sizeof(int)); //動態分配的內存,在堆中*heap = 2;pid = fork();if(pid < 0) {printf("fail to fork\n");exit(1);} else if(pid == 0) { //子進程global++;stack++;(*heap)++;printf("the child, data: %d, stack: %d, heap: %d\n", global, stack, *heap);exit(0); //子進程結束}sleep(2); //父進程休眠2秒printf("the parent, data: %d, stack: %d, heap: %d\n", global, stack, *heap);return 0;
}
運行結果:
?
三、創建一個共享空間的子程序
進程在創建一個新的子進程之后,子進程的地址空間完全和父進程分開,父子進程是兩個完全獨立的進程,接受系統調度和分配系統資源的機會均等。如果父子進程共用父進程的地址空間,則子進程就不是獨立于父進程的。Linux下提供了一個和fork()函數類似的函數,可以用來創建一個共用父進程地址空間的子進程,函數原型如下:
#include <unistd.h>
pid_t vfork();
vfork()和fork()函數的區別如下:
- vfork()函數產生的子進程和父進程完全共享地址空間,包括代碼段、數據段和堆棧段,子進程對這些資源所做的修改可以影響父進程。vfork()函數產生的進程更像一個線程。
- vfork()函數產生的子進程一定比父進程先運行,即父進程調用vfork函數后,會等待子進程運行后再運行。
使用vfork()函數還應注意不要在非main函數的函數中調用vfork()函數。
?
四、退出進程
當一個進程需要退出時,需要調用退出函數:
#include <stdlib.h>
void exit(int status);
exit()函數的參數表示退出的狀態,這個狀態的值是一個整型。在shell中可以檢查這個退出的狀態值。
C程序中的return語句會被翻譯為調用exit()函數:
return 1;
翻譯為:
exit(1);
exit()函數與內核函數的關系
exit()函數是一個標準的庫函數,其內部封裝了Linux系統調用的_exit()函數。兩者的主要區別在于exit()函數會在用戶空間做一些善后工作,例如清理用戶的I/O緩沖區將其內容寫入磁盤文件等,之后再進入內核釋放用戶進程的地址空間;而_exit()函數直接進入內核釋放用戶進程的地址空間,所以用戶空間的緩沖區內容都將丟失。
?
五、設置進程所有者
每一個進程都有兩個用戶ID,實際用戶ID和有效用戶ID。通常這兩個ID的值是相等的,其值為進程所有者的用戶ID。但是有些場合需要改變進程的有效用戶ID。
Linux下使用setuid()函數改變一個進程的實際用戶ID和有效用戶ID,其函數原型如下:
#include <unistd.h>
int setuid(uid_t uid);
setuid()函數的參數表示改變后的新用戶ID,如果成功修改當前進程的實際用戶ID和有效用戶ID,setuid()函數返回0,失敗返回-1。
兩種用戶可以修改進程的實際用戶ID和有效用戶ID:
- 根用戶。根用戶可以將進程的實際用戶ID和有效用戶ID更改。
- 其他用戶,且該用戶的用戶ID等于進程的實際用戶ID或者保存的ID。
下面的程序演示修改當前進程的用戶ID:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[])
{FILE *fp;uid_t uid, euid;uid = getuid(); //實際用戶IDeuid = geteuid(); //有效用戶IDprintf("the uid is : %d\n", uid);printf("the euid is : %d\n", euid);if(setuid(8000) == -1){perror("fail to set uid");exit(1);}printf("after changing\n");uid = getuid(); //實際用戶IDeuid = geteuid(); //有效用戶IDprintf("the uid is : %d\n", uid);printf("the euid is : %d\n", euid);return 0;
}
運行結果:
Linux下還提供只修改有效用戶ID的函數:
int seteuid(uid_t uid);
以及修改實際組ID和有效組ID的函數:
#include <unistd.h>
int setgid(gid_t gid);
int setegid(gid_t gid);
?