Linux下C語言編程-進程的建立
作者:hoyt
1.進程的概念
Linux作業系統是面向多使用者的.在同一時間可以有許多使用者向作業系統發出各種命令.那麼作業系統是怎麼實現多使用者的環境呢?在現代的作業系統裡面,都有程式和進程的概念.那麼什麼是程式,什麼是進程呢?通俗的講程式是一個包含可以執行代碼的檔案,是一個靜態檔案.而進程是一個開始執行但是還沒有結束的程式的執行個體.就是可執行檔的具體實現.
一個程式可能有許多進程,而每一個進程又可以有許多子進程.依次迴圈下去,而產生子孫進程.當程式被系統調用到記憶體以後,系統會給程式分配一定的資源(記憶體,裝置等等)然後進行一系列的複雜操作,使程式變成進程以供系統調用.在系統裡面只有進程沒有程式,為了區分各個不同的進程,系統給每一個進程分配了一個ID(就象我們的身份證)以便識別.
為了充分的利用資源,系統還對進程區分了不同的狀態.將進程分為建立,運行,阻塞,就緒和完成五個狀態.建立表示進程正在被建立,運行是進程正在運行,阻塞是進程正在等待某一個事件發生,就緒是表示系統正在等待CPU來執行命令,而完成表示進程已經結束了系統正在回收資源.關於進程五個狀態的詳細解說我們可以看《作業系統》上面有詳細的解說。
2.進程的標誌
上面我們知道了進程都有一個ID,那麼我們怎麼得到進程的ID呢?系統調用getpid可以得到進程的ID,而getppid可以得到父進程(建立調用該函數進程的進程)的ID.
pid_t getpid(void);
pid_t getppid(void);
進程是為程式服務的,而程式是為了使用者服務的.系統為了找到進程的使用者名稱,還為進程和使用者建立聯絡.這個使用者稱為進程的所有者.相應的每一個使用者也有一個使用者ID.通過系統調用getuid可以得到進程的所有者的ID.由於進程要用到一些資源,而Linux對系統資源是進行保護的,為了擷取一定資源進程還有一個有效使用者ID.這個ID和系統的資源使用有關,涉及到進程的許可權.通過系統調用geteuid我們可以得到進程的有效使用者ID.和使用者ID相對應進程還有一個組ID和有效組ID系統調用getgid和getegid可以分別得到組ID和有效組ID.
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
git_t getegid(void);
有時候我們還會對使用者的其他資訊感興趣(登入名稱等等),這個時候我們可以調用getpwuid來得到.
struct passwd {
char *pw_name; /* 登入名稱稱 */
char *pw_passwd; /* 登入口令 */
uid_t pw_uid; /* 使用者ID */
gid_t pw_gid; /* 使用者組ID */
char *pw_gecos; /* 使用者的真名 */
char *pw_dir; /* 使用者的目錄 */
char *pw_shell; /* 使用者的SHELL */
};
#include
#include
struct passwd *getpwuid(uid_t uid);
下面我們學習一個執行個體來實踐一下上面我們所學習的幾個函數:
#include
#include
#include
#include
int main(int argc,char **argv)
{
pid_t my_pid,parent_pid;
uid_t my_uid,my_euid;
gid_t my_gid,my_egid;
struct passwd *my_info;
my_pid=getpid();
parent_pid=getppid();
my_uid=getuid();
my_euid=geteuid();
my_gid=getgid();
my_egid=getegid();
my_info=getpwuid(my_uid);
printf("Process ID:%ld\n",my_pid);
printf("Parent ID:%ld\n",parent_pid);
printf("User ID:%ld\n",my_uid);
printf("Effective User ID:%ld\n",my_euid);
printf("Group ID:%ld\n",my_gid);
printf("Effective Group ID:%ld\n",my_egid):
if(my_info)
{
printf("My Login Name:%s\n" ,my_info->pw_name);
printf("My Password :%s\n" ,my_info->pw_passwd);
printf("My User ID :%ld\n",my_info->pw_uid);
printf("My Group ID :%ld\n",my_info->pw_gid);
printf("My Real Name:%s\n" ,my_info->pw_gecos);
printf("My Home Dir :%s\n", my_info->pw_dir);
printf("My Work Shell:%s\n", my_info->pw_shell);
}
}
3.進程的建立
建立一個進程的系統調用很簡單.我們只要調用fork函數就可以了.
pid_t fork();
當一個進程調用了fork以後,系統會建立一個子進程.這個子進程和父進程不同的地方只有他的進程ID和父進程ID,其他的都是一樣.就象符進程複製(clone)自己一樣.當然建立兩個一模一樣的進程是沒有意義的.為了區分父進程和子進程,我們必須跟蹤fork的傳回值.
當fork掉用失敗的時候(記憶體不足或者是使用者的最大進程數已到)fork返回-1,否則fork的傳回值有重要的作用.對於父進程fork返回子進程的ID,而對於fork子進程返回0.我們就是根據這個傳回值來區分父子進程的.
父進程為什麼要建立子進程呢?前面我們已經說過了Linux是一個多使用者作業系統,在同一時間會有許多的使用者在爭奪系統的資源.有時進程為了早一點完成任務就建立子進程來爭奪資源.一旦子進程被建立,父子進程一起從fork處繼續執行,相互競爭系統的資源.有時候我們希望子進程繼續執行,而父進程阻塞直到子進程完成任務.這個時候我們可以調用wait或者waitpid系統調用.
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid,int *stat_loc,int options);
wait系統調用會使父進程阻塞直到一個子進程結束或者是父進程接受到了一個訊號.如果沒有父進程沒有子進程或者他的子進程已經結束了wait回立即返回.成功時(因一個子進程結束)wait將返回子進程的ID,否則返回-1,並設定全域變數errno.stat_loc是子進程的退出狀態.子進程調用exit,_exit 或者是return來設定這個值. 為了得到這個值Linux定義了幾個宏來測試這個傳回值.
WIFEXITED:判斷子進程退出值是非0
WEXITSTATUS:判斷子進程的退出值(當子進程退出時非0).
WIFSIGNALED:子進程由於有沒有獲得的訊號而退出.
WTERMSIG:子進程沒有獲得的訊號號(在WIFSIGNALED為真時才有意義).
waitpid等待指定的子進程直到子進程返回.如果pid為正值則等待指定的進程(pid).如果為0則等待任何一個組ID和調用者的組ID相同的進程.為-1時等同於wait調用.小於-1時等待任何一個組ID等於pid絕對值的進程. stat_loc和wait的意義一樣. options可以決定父進程的狀態.可以取兩個值 WNOHANG:父進程立即返回當沒有子進程存在時.WUNTACHED:當子進程結束時waitpid返回,但是子進程的退出狀態不可得到.
父進程建立子進程後,子進程一般要執行不同的程式.為了調用系統程式,我們可以使用系統調用exec族調用.exec族調用有著5個函數.
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const char *arg,...);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]):
exec族調用可以執行給定程式.關於exec族調用的詳細解說可以參考系統手冊(manexecl).下面我們來學習一個執行個體.注意編譯的時候要加 -lm以便串連數學函數庫.
#include
#include
#include
#include
#include
#include
void main(void)
{
pid_t child;
int status;
printf("This will demostrate how to get child status\n");
if((child=fork())==-1)
{
printf("Fork Error :%s\n",strerror(errno));
exit(1);
}
else if(child==0)
{
int i;
printf("I am the child:%ld\n",getpid());
for(i=0;i<1000000;i++) sin(i);
i=5;
printf("I exit with %d\n",i);
exit(i);
}
while(((child=wait(&status))==-1)&(errno==EINTR));
if(child==-1)
printf("Wait Error:%s\n",strerror(errno));
else if(!status)
printf("Child %ld terminated normally return status is zero\n",
child);
else if(WIFEXITED(status))
printf("Child %ld terminated normally return status is %d\n",
child,WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("Child %ld terminated due to signal %d znot caught\n",
child,WTERMSIG(status));
}
strerror函數會返回一個指定的錯誤號碼的錯誤資訊的字串.
4.守護進程的建立
如果你在DOS時代編寫過程式,那麼你也許知道在DOS下為了編寫一個常駐記憶體的程式我們要編寫多少代碼了.相反如果在Linux下編寫一個"常駐記憶體"的程式卻是很容易的.我們只要幾行代碼就可以做到. 實際上由於Linux是多任務作業系統,我們就是不編寫代碼也可以把一個程式放到後台去執行的.我們只要在命令後面加上&符號SHELL就會把我們的程式放到後台去啟動並執行.這裡我們"開發"一個後台檢查郵件的程式.這個程式每個一個指定的時間回去檢查我們的郵箱,如果發現我們有郵件了,會不斷的警示(通過機箱上的小喇叭來發出聲音).後面有這個函數的加強版本.後台進程的建立思想:首先父進程建立一個子進程.然後子進程殺死父進程(是不是很無情?). 訊號處理所有的工作由子進程來處理.
#include
#include
#include
#include
#include
#include
#include
/* Linux 的默任個人的郵箱地址是 /var/spool/mail/使用者的登入名稱 */
#define MAIL "/var/spool/mail/hoyt"
/* 睡眠10秒鐘 */
#define SLEEP_TIME 10
main(void)
{
pid_t child;
if((child=fork())==-1)
{
printf("Fork Error:%s\n",strerror(errno));
exit(1);
}
else if(child>0)
while(1);
if(kill(getppid(),SIGTERM)==-1)
{
printf("Kill Parent Error:%s\n",strerror(errno));
exit(1);
}
{
int mailfd;
while(1)
{
if((mailfd=open(MAIL,O_RDONLY))!=-1)
{
fprintf(stderr,"%s","\007");
close(mailfd);
}
sleep(SLEEP_TIME);
}
}
}
你可以在預設的路徑下建立你的郵箱檔案,然後測試一下這個程式.當然這個程式還有很多地方要改善的.我們後面會對這個小程式改善的,再看我的改善之前你可以嘗試自己改善一下.比如讓使用者指定郵相的路徑和睡眠時間等等.相信自己可以做到的.動手吧,勇敢的探險者.
進程是一個非常重要的概念,許多的程式都會用子進程.建立一個子進程是每一個程式員的基本要求!