linux 程序控制總結筆記
阿新 • • 發佈:2019-01-02
1,時間和空間是計算機裡的兩個基本概念,作業系統將這兩個概念實現為程序和檔案。
首先理解程序的概念:
程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,是動態的,是系統進行資源分配和排程的一個獨立單位。
2,程序ID是程序的一個基本屬性,下面介紹程序中六個重要的ID,及獲取的函式原型
程序ID pid_t getpid();
父程序ID pid_t getppid();
程序的使用者ID uid_t getuid();
有效使用者ID uid_t geteuid();
程序的組ID gid_t getgid();
有效組ID gid_t getegid();
實現程式碼如下:
#include <stdio.h>
#include <unistd.h>
void main()
{
pid_t pid,ppid;
uid_t uid,euid;
gid_t gid,egid;
pid = getpid();
ppid = getppid();
uid = getuid();
euid = geteuid();
gid = getgid();
egid = getegid();
printf("pid: %u\n",pid);
printf("ppid: %u\n",ppid);
printf("uid: %u\n",uid);
printf("euid: %u\n",euid);
printf("gid: %u\n",gid);
printf("egid: %u\n",egid);
}
3,說到程序,我們該怎麼建立程序呢?這裡使用fork()函式建立程序。
#include <unistd.h> fork函式包含庫
pid_t fork(void); fork函式原型
利用fork函式建立程序:
pid_t pid;
pid = fork();
fork函式不需要引數,他的返回值有三種情況:
1,父程序返回值大於0 即pid > 0
2,子程序返回值等於0 即pid == 0
3,建立失敗 返回值為-1 小於0
既然有父子程序,那麼父子程序之間又有啥關係呢?(也有三點)
1,父程序返回值為子程序ID,子程序返回的是0
2,父子程序是兩個獨立的程序,排程機會均等
3,父子程序,fork之後,作業系統會複製一個與父程序完全相同的子程序,雖說是父子關係,但更像是兄弟關係,這兩個程序共享程式碼空間,但資料空間是相互獨立的(重點記住)。
接下來,程式碼實現一個程序的建立,如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork(); //建立程序
if(pid < 0) //建立失敗
{
printf("fork create error...\n");
return -1;
}
else if(pid > 0) //父程序
{
printf("parents pid:%d child pid :%d\n",getpid(),pid);
}
else // 子程序
{
printf("child pid:%d\n",getpid());
}
return 0;
}
4,上面所說fork函式建立父子程序,他們是相互獨立的,那怎麼建立一個共享空間的子程序呢?下面用類似fork函式的vfork()函式實現:
vfork 函式原型如下:
#include <unistd.h>
pid_t vfork(void);
vfork()函式與fork()函式有啥區別呢?
1,vfork函式建立的父子程序其子程序和父程序完全共享地址空間,包括程式碼段,資料段和堆疊段,子程序對這些共享資源的修改,可以影響到父程序,而fork函式不會。
2,vfork函式產生的子程序一定比父程序先執行,而fork函式產生的父子程序是由系統決定先後,呼叫機會均等。
5,程序的退出
一般用exit函式進行程序的退出操作,如果程序正常退出,引數為0 即exit(0),如果異常退出則為非0.
6.Linux環境下使用exec函式執行一個新程式
在程序中使用exec函式會將原程序內容取代,即完全執行exec函式程式內容,原始碼將不會執行。
exec函式原型有六個,但其作用都相同,只是使用方法及引數不同而已。
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char * const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[],...,char * const envp[]);
int execvp(const char *file, char *const argv[]);
接下來,通過一些簡單方法來區別他們的使用。
1,含有‘l’字母 的,其引數以列表方式提供
2,含有‘v’字母的,引數以二維陣列形式提供
3,末尾帶有‘e’字母的,表示傳給新程式環境變數列表,二維陣列形式提供
4,帶有‘p’字母的,表示第一個引數不是路徑名和程式名,而是隻是一個程式名
通過不同的例項對比來進行exec函式的解析使用。
1,帶p字母與不帶p字母的相比較
例:使用ls -l 命令作為例子
execl("/bin/ls","ls","-l",NULL);
其中第一個引數包括路徑名稱及程式名稱。
execlp("ls","ls","-l",NULL);
其中第一個引數只是程式名,沒有包含路徑
2,帶l字母的與帶v字母的比較。
execl("/bin/ls","ls","-l",NULL);
其中引數是以列表方式提供
char *const argv[] = {"ls","ls","-l",NULL};
execv("/bin/ls",argv);
其中引數是以陣列形式提供
3,帶e字母與不帶e字母的相比較
execl("/bin/ls","ls","-l",NULL);
其中沒有設定新程序環境變數
char *const envp[] = {"PATH:/bin:/usr/bin",NULL};
execle("/bin/ls","ls","-l",NULL,envp);
其中加上了環境變數列表 以二維陣列形式提供
7,wait函式的使用
wait 函式的原型
#include <sys/wait.h>
pid_t wait(int *staloc);
呼叫wait函式的程序會堵塞,直到該程序的任意一個子程序結束,wait函式會取得結束的子程序的資訊並且返回該子程序的ID,結束資訊儲存在引數staloc所指向的記憶體空間中。
我們通過判斷巨集和取值巨集來判斷哪些狀態有效,並且取得相應的狀態值。
狀態 判斷巨集 取值巨集
程序正常結束 WIFEXITED(status) WEXITSTATUS(status)
程序異常終止 WIFSIGNALED(status) WTERMSIG(status)
接下來通過一個例項來運用這兩個巨集進行比較:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t pid;
int status;
int a;
pid = fork(); //建立第一個子程序
if(pid < 0)
{
perror("create fork error...");
exit(1);
}
else if(pid == 0)
{
printf("the first,exit normally...\n"); //第一個子程序正常退出 返回0值
exit(0);
}
else
{
printf("the parent run...\n");
wait(&status); //wait函式
if(WIFEXITED(status) == 1) //判斷巨集 若為真 即1 值 子程序正常退出
{
printf("the first child is normally exit...\n");
printf("the fist status is :%d\n",WEXITSTATUS(status));//取值巨集,列印返回值
}
}
pid = fork(); //建立第二個子程序
if(pid < 0)
{
perror("the second process fork create error..");
exit(1);
}
else if(pid == 0)
{
printf("the second process ,exit abnormally...\n");
a = 1 / 0; //0做除數會產生SIGFPE異常訊號 訊號值為8
}
else
{
wait(&status); //wait函式
if(WIFSIGNALED(status) == 1) //判斷巨集 子程序是否異常結束
{
printf("the second abnormally exit..\n");
printf("the second signal is :%d\n",WTERMSIG(status)); //列印返回的訊號值 應為8
}
}
return 0;
}
下面是執行結果:
8,等待指定的程序waitpid()函式的使用
waitpid()函式原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *staloc, int options);
options選項常用的為WNOHANG
在使用WNOHANG引數時,父程序不會等待子程序結束,是非阻塞狀態。
通過程式來進行理解:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子程序
{
printf("child run...\n");
sleep(10); //睡眠十秒
printf("child run over...\n");
exit(0);
}
else //父程序
{
printf("the parent run..\n");
do
{
ret = waitpid(pid,NULL,WNOHANG);
//waitpid函式使用 WNOHANG引數 非阻塞,父程序不會等待子程序結束
if(ret == 0)
{
printf("the child is not exit\n");
sleep(2); //休眠2秒
}
}while(ret == 0);
if(ret == pid) //當子程序結束 waitpid返回值為子程序id
{
printf("child process success exit..and ret is :%d\n",ret);
}
}
return 0;
}
執行結果如下所示:
通過程式可以看出,加上引數,父程序不會阻塞,不會等待子程序結束。當子程序結束時會返回 子程序的ID值
9,殭屍程序的概念
在LINUX系統中,一個程序結束了,但是他的父程序沒有等待(呼叫wait / waitpid)他, 那麼他將變成一個殭屍程序。 但是如果該程序的父程序已經先結束了,那麼該程序就不會變成殭屍程序, 因為每個程序結束的時候,系統都會掃描當前系統中所執行的所有程序, 看有沒有哪個程序是剛剛結束的這個程序的子程序,如果是的話,就由Init 來接管他,成為他的父程序……
一個程序在呼叫exit命令結束自己的生命的時候,其實它並沒有真正的被 銷燬, 而是留下一個稱為殭屍程序(Zombie)的資料結構
通過一個程式我們製造一個殭屍程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子程序
{
printf("child run...\n");
sleep(5); //睡眠五秒
printf("child run over...\n");
exit(0);
}
else //父程序
{
printf("the parent run..\n");
sleep(30);
wait(NULL); //呼叫wait函式回收子程序狀態訊息 防止產生殭屍程序
printf("parent run over...\n");
}
return 0;
}
程式中子程序睡眠5秒鐘結束,而父程序睡眠三十秒沒有呼叫wait函式回收,在這25秒內就產生了一個殭屍程序。
執行結果為:
使用ps -aux 命令檢視程序狀態
我們可以看到一個Z+ 識別符號,就代表一個殭屍程序。當父程序睡眠三十秒後 呼叫wait函式,再檢視系統程序就發現殭屍程序消失了。
10,殭屍程序的危害及如何避免殭屍程序的產生呢?
危害:linux系統程序數是有限制的,若有大量殭屍程序,使得系統 無法產生新程序,這就是殭屍程序最大的危害。
避免殭屍程序產生的方法
1,父程序通過wait和waitpid等函式等待子程序結束(程式碼參考上面)
3,fork兩次,建立一個子程序,子程序再建立一個子程序,孫子程序做實際工作,子程序退出,讓Init來接管孫子程序。
實現程式碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //建立一個子程序
{
printf("the child run....\n");
pid = fork(); //再建立子程序
if(pid < 0)
{
perror("the child child error...");
exit(1);
}
else if(pid == 0) //建立一個孫子程序
{
printf("do something you want...\n");
sleep(5);
printf("child child run over..\n");
exit(0);
}
else
{
exit(0); //子程序退出 將孫子程序託付Init程序
}
}
else
{
printf("the parent ...\n"); //父程序
}
return 0;
}
3,父程序中呼叫signal函式,signal(SIGCHLD,SIG_IGN);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
pid_t pid;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //建立一個子程序
{
printf("the child run....\n");
sleep(5);
printf("the child run over..\n");
exit(0);
}
else
{
signal(SIGCHLD,SIG_IGN); //呼叫signal函式 避免殭屍程序的產生
sleep(30);
printf("the parent ...\n"); //父程序
}
return 0;
}
首先理解程序的概念:
程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,是動態的,是系統進行資源分配和排程的一個獨立單位。
2,程序ID是程序的一個基本屬性,下面介紹程序中六個重要的ID,及獲取的函式原型
程序ID pid_t getpid();
父程序ID pid_t getppid();
程序的使用者ID uid_t getuid();
有效使用者ID
程序的組ID gid_t getgid();
有效組ID gid_t getegid();
實現程式碼如下:
#include <stdio.h>
#include <unistd.h>
void main()
{
pid_t pid,ppid;
uid_t uid,euid;
gid_t gid,egid;
pid = getpid();
ppid = getppid();
uid = getuid();
euid = geteuid();
gid = getgid();
egid = getegid();
printf("pid: %u\n",pid);
printf("ppid: %u\n",ppid);
printf("uid: %u\n",uid);
printf("euid: %u\n",euid);
printf("gid: %u\n",gid);
printf("egid: %u\n",egid);
}
3,說到程序,我們該怎麼建立程序呢?這裡使用fork()函式建立程序。
#include <unistd.h> fork函式包含庫
pid_t fork(void); fork函式原型
利用fork函式建立程序:
pid_t pid;
pid = fork();
fork函式不需要引數,他的返回值有三種情況:
1,父程序返回值大於0 即pid > 0
2,子程序返回值等於0 即pid == 0
3,建立失敗 返回值為-1 小於0
既然有父子程序,那麼父子程序之間又有啥關係呢?(也有三點)
1,父程序返回值為子程序ID,子程序返回的是0
2,父子程序是兩個獨立的程序,排程機會均等
3,父子程序,fork之後,作業系統會複製一個與父程序完全相同的子程序,雖說是父子關係,但更像是兄弟關係,這兩個程序共享程式碼空間,但資料空間是相互獨立的(重點記住)。
接下來,程式碼實現一個程序的建立,如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork(); //建立程序
if(pid < 0) //建立失敗
{
printf("fork create error...\n");
return -1;
}
else if(pid > 0) //父程序
{
printf("parents pid:%d child pid :%d\n",getpid(),pid);
}
else // 子程序
{
printf("child pid:%d\n",getpid());
}
return 0;
}
4,上面所說fork函式建立父子程序,他們是相互獨立的,那怎麼建立一個共享空間的子程序呢?下面用類似fork函式的vfork()函式實現:
vfork 函式原型如下:
#include <unistd.h>
pid_t vfork(void);
vfork()函式與fork()函式有啥區別呢?
1,vfork函式建立的父子程序其子程序和父程序完全共享地址空間,包括程式碼段,資料段和堆疊段,子程序對這些共享資源的修改,可以影響到父程序,而fork函式不會。
2,vfork函式產生的子程序一定比父程序先執行,而fork函式產生的父子程序是由系統決定先後,呼叫機會均等。
5,程序的退出
一般用exit函式進行程序的退出操作,如果程序正常退出,引數為0 即exit(0),如果異常退出則為非0.
6.Linux環境下使用exec函式執行一個新程式
在程序中使用exec函式會將原程序內容取代,即完全執行exec函式程式內容,原始碼將不會執行。
exec函式原型有六個,但其作用都相同,只是使用方法及引數不同而已。
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char * const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[],...,char * const envp[]);
int execvp(const char *file, char *const argv[]);
接下來,通過一些簡單方法來區別他們的使用。
1,含有‘l’字母 的,其引數以列表方式提供
2,含有‘v’字母的,引數以二維陣列形式提供
3,末尾帶有‘e’字母的,表示傳給新程式環境變數列表,二維陣列形式提供
4,帶有‘p’字母的,表示第一個引數不是路徑名和程式名,而是隻是一個程式名
通過不同的例項對比來進行exec函式的解析使用。
1,帶p字母與不帶p字母的相比較
例:使用ls -l 命令作為例子
execl("/bin/ls","ls","-l",NULL);
其中第一個引數包括路徑名稱及程式名稱。
execlp("ls","ls","-l",NULL);
其中第一個引數只是程式名,沒有包含路徑
2,帶l字母的與帶v字母的比較。
execl("/bin/ls","ls","-l",NULL);
其中引數是以列表方式提供
char *const argv[] = {"ls","ls","-l",NULL};
execv("/bin/ls",argv);
其中引數是以陣列形式提供
3,帶e字母與不帶e字母的相比較
execl("/bin/ls","ls","-l",NULL);
其中沒有設定新程序環境變數
char *const envp[] = {"PATH:/bin:/usr/bin",NULL};
execle("/bin/ls","ls","-l",NULL,envp);
其中加上了環境變數列表 以二維陣列形式提供
7,wait函式的使用
wait 函式的原型
#include <sys/wait.h>
pid_t wait(int *staloc);
呼叫wait函式的程序會堵塞,直到該程序的任意一個子程序結束,wait函式會取得結束的子程序的資訊並且返回該子程序的ID,結束資訊儲存在引數staloc所指向的記憶體空間中。
我們通過判斷巨集和取值巨集來判斷哪些狀態有效,並且取得相應的狀態值。
狀態 判斷巨集 取值巨集
程序正常結束 WIFEXITED(status) WEXITSTATUS(status)
程序異常終止 WIFSIGNALED(status) WTERMSIG(status)
接下來通過一個例項來運用這兩個巨集進行比較:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t pid;
int status;
int a;
pid = fork(); //建立第一個子程序
if(pid < 0)
{
perror("create fork error...");
exit(1);
}
else if(pid == 0)
{
printf("the first,exit normally...\n"); //第一個子程序正常退出 返回0值
exit(0);
}
else
{
printf("the parent run...\n");
wait(&status); //wait函式
if(WIFEXITED(status) == 1) //判斷巨集 若為真 即1 值 子程序正常退出
{
printf("the first child is normally exit...\n");
printf("the fist status is :%d\n",WEXITSTATUS(status));//取值巨集,列印返回值
}
}
pid = fork(); //建立第二個子程序
if(pid < 0)
{
perror("the second process fork create error..");
exit(1);
}
else if(pid == 0)
{
printf("the second process ,exit abnormally...\n");
a = 1 / 0; //0做除數會產生SIGFPE異常訊號 訊號值為8
}
else
{
wait(&status); //wait函式
if(WIFSIGNALED(status) == 1) //判斷巨集 子程序是否異常結束
{
printf("the second abnormally exit..\n");
printf("the second signal is :%d\n",WTERMSIG(status)); //列印返回的訊號值 應為8
}
}
return 0;
}
下面是執行結果:
8,等待指定的程序waitpid()函式的使用
waitpid()函式原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *staloc, int options);
options選項常用的為WNOHANG
在使用WNOHANG引數時,父程序不會等待子程序結束,是非阻塞狀態。
通過程式來進行理解:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子程序
{
printf("child run...\n");
sleep(10); //睡眠十秒
printf("child run over...\n");
exit(0);
}
else //父程序
{
printf("the parent run..\n");
do
{
ret = waitpid(pid,NULL,WNOHANG);
//waitpid函式使用 WNOHANG引數 非阻塞,父程序不會等待子程序結束
if(ret == 0)
{
printf("the child is not exit\n");
sleep(2); //休眠2秒
}
}while(ret == 0);
if(ret == pid) //當子程序結束 waitpid返回值為子程序id
{
printf("child process success exit..and ret is :%d\n",ret);
}
}
return 0;
}
執行結果如下所示:
通過程式可以看出,加上引數,父程序不會阻塞,不會等待子程序結束。當子程序結束時會返回 子程序的ID值
9,殭屍程序的概念
在LINUX系統中,一個程序結束了,但是他的父程序沒有等待(呼叫wait / waitpid)他, 那麼他將變成一個殭屍程序。 但是如果該程序的父程序已經先結束了,那麼該程序就不會變成殭屍程序, 因為每個程序結束的時候,系統都會掃描當前系統中所執行的所有程序, 看有沒有哪個程序是剛剛結束的這個程序的子程序,如果是的話,就由Init 來接管他,成為他的父程序……
一個程序在呼叫exit命令結束自己的生命的時候,其實它並沒有真正的被 銷燬, 而是留下一個稱為殭屍程序(Zombie)的資料結構
通過一個程式我們製造一個殭屍程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子程序
{
printf("child run...\n");
sleep(5); //睡眠五秒
printf("child run over...\n");
exit(0);
}
else //父程序
{
printf("the parent run..\n");
sleep(30);
wait(NULL); //呼叫wait函式回收子程序狀態訊息 防止產生殭屍程序
printf("parent run over...\n");
}
return 0;
}
程式中子程序睡眠5秒鐘結束,而父程序睡眠三十秒沒有呼叫wait函式回收,在這25秒內就產生了一個殭屍程序。
執行結果為:
使用ps -aux 命令檢視程序狀態
我們可以看到一個Z+ 識別符號,就代表一個殭屍程序。當父程序睡眠三十秒後 呼叫wait函式,再檢視系統程序就發現殭屍程序消失了。
10,殭屍程序的危害及如何避免殭屍程序的產生呢?
危害:linux系統程序數是有限制的,若有大量殭屍程序,使得系統 無法產生新程序,這就是殭屍程序最大的危害。
避免殭屍程序產生的方法
1,父程序通過wait和waitpid等函式等待子程序結束(程式碼參考上面)
3,fork兩次,建立一個子程序,子程序再建立一個子程序,孫子程序做實際工作,子程序退出,讓Init來接管孫子程序。
實現程式碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //建立一個子程序
{
printf("the child run....\n");
pid = fork(); //再建立子程序
if(pid < 0)
{
perror("the child child error...");
exit(1);
}
else if(pid == 0) //建立一個孫子程序
{
printf("do something you want...\n");
sleep(5);
printf("child child run over..\n");
exit(0);
}
else
{
exit(0); //子程序退出 將孫子程序託付Init程序
}
}
else
{
printf("the parent ...\n"); //父程序
}
return 0;
}
3,父程序中呼叫signal函式,signal(SIGCHLD,SIG_IGN);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
pid_t pid;
pid = fork(); //建立子程序
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //建立一個子程序
{
printf("the child run....\n");
sleep(5);
printf("the child run over..\n");
exit(0);
}
else
{
signal(SIGCHLD,SIG_IGN); //呼叫signal函式 避免殭屍程序的產生
sleep(30);
printf("the parent ...\n"); //父程序
}
return 0;
}