Linux 進程編程入門
本文從頭帶着大家一起學習 Linux 進程
Linux 進程篇
一、進程相關概念
瞭解進程的時候先來了解幾個問題,明白以下問題,就懂了進程的概念
1. 什麼是程序,什麼是進程,兩者之間的區別?
-
程序是靜態的概念,gcc xxx.c -o pro 磁盤中生成 pro 文件,叫做程序 程序如:電腦上的圖標
-
進程是程序的一次運行活動, 通俗點說就是程序跑起來了,系統中就多了一個進程
2. 如何查看系統中有哪些進程?
-
使用 ps 指令查看 : ps-aux 在 ubuntu 下查看, 在實際工作中,配合 grep 來查找程序中是否存在某一個進程
grep 過濾進程 :ps -aux | grep init 就只把帶有 init 的進程過濾出來
-
使用 top 指令查看,類似 windows 任務管理器
3. 什麼是進程標識符?
每一個進程都有一個非負整數表示的唯一 ID,叫做 pid,類似身份證
pid =0 :稱爲交換進程(swapper) 作用:進程調度 pid=1 :init 進程 作用:系統初始化
- 編程調用 getpid 函數獲取自身的進程標識符;
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
getpid 示例代碼:
#include<stidio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = getpid();
printf("my pid is %d\n",pid);
return 0;
}
- getppid 獲取父進程的進程標識符;
4. 第一個進程 init 進程
Linux 內核啓動之後,會創建第一個用戶級進程 init,由上圖可知, init 進程 (pid=1) 是除了 idle 進程 (pid=0,也就是 init_task) 之外另一個比較特殊的進程,它是 Linux 內核開始建立起進程概念時第一個通過 kernel_thread 產生的進程,其開始在內核態執行,然後通過一個系統調用,開始執行用戶空間的 / sbin/init 程序。
5. 什麼叫父進程,什麼叫子進程?
進程 A 創建了進程 B,那麼 A 叫做父進程,B 叫做子進程,父進程是相對的概念,理解爲人類中的父子關係
6. c 程序的存儲空間是如何分配的?
gcc xxx.c -o a.out 當執行 ./a.out 時候,操作系統會劃分一塊內存空間,如何分配呢?如下圖:
二、創建進程函數 fork 的使用
==pid_t fork(void);== 功能:使用 fork 函數創建一個進程
fork 函數調用成功,返回兩次 返回值爲 0 ,代表當前進程是子進程 返回值非負數,代表當前進程爲父進程 調用失敗 ,返回 - 1
1. fork(); 示例代碼
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = getpid();
fork();
printf("my pid is %d\n",pid);
return 0;
}
打印出了兩遍 my pid 說明,有了兩個進程!執行了兩次打印 pid
2. 查看父進程 / 子進程代碼:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid_t pid2;
pid = getpid();
printf("brfore fork pid is %d\n",pid);
fork();
pid2 = getpid();
printf("brfore fork pid is %d\n",pid2);
if(pid == pid2){
printf("this is father print\n");
}else{
printf("this is child print , child pid is =%d\n",getpid());
}
return 0;
}
3. 用返回值來判斷父 / 子進程代碼 (1):
返回值爲 0 ,代表當前進程是子進程 返回值非負數,代表當前進程爲父進程
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid;
printf("father: id=%d\n",getpid());
pid = fork();
if(pid > 0){
printf("this is father print ,pid =%d\n",getpid());
}else if (pid == 0){
printf("this is child print, child pid = %d\n",getpid());
}
return 0;
}
4. 用返回值來判斷父子進程代碼 (2):
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid_t pid2;
pid_t retpid;
pid = getpid();
printf("before fork: pid = %d\n",pid);
retpid = fork();
pid2 = getpid();
printf("after fork:pid = %d\n",pid2);
if(pid == pid2){
printf("this is father print :retpid = %d\n",retpid);
}else{
printf("this is child print :retpid =%d,child pid= %d\n",retpid,pid2);
}
return 0;
}
fork 返回值:9915>0 是父進程 父進程號是 9114 fork 返回值:=0 是子進程 子進程號是 9915
三、進程創建後 發生了什麼事?
1 在內存空間中 fork 後發生了什麼?
2. ./demo4 運行的程序父進程是誰?
int main(int argc, const char *argv[])
{
while(1);
return 0;
}
./ demo4 編譯運行後,我們 ps -ef 查看進程 ID
四、創建新進程的實際應用場景
1. fork 創建子進程的一般目的:
-
一個父進程希望複製自己,使父、子進程同時執行不同的代碼段。這在網絡服務進程中是常見的——父進程等待客戶端的服務請求。當這種情求達到時,父進程調用 fork,使子進程處理此請求。父進程則繼續等待下一個服務請求到達。
-
一個進程要執行一個不同的程序。這對 shell 是常見的情況,在這種情況下子進程從 fork 返回後立即調用 exec。
2. 模擬 socket 創建進程(服務器對接客戶端的應用場景)示例代碼:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t pid;
int data;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data ==1 )
{
pid = fork();
if(pid >0){
}
else if(pid == 0){
while(1){
printf("do net request,pid=%d\n",getpid());
sleep(3);
}
}
}
else{
printf("wait, do noting\n");
}
}
return 0;
}
輸入非 1 時候,模擬沒有客戶端進行交互
輸入 1 時候,模擬有客戶端進行交互 ,創建子進程來進行交互,子進程號爲:9756
模擬多個客戶端進行交互時 ,創建多個子進程來進行交互,子進程號爲:9756 / 9758 / 9759
查看系統進程:
3. fork 總結:
一個現有進程可以調用 fork 函數創建一個新進程。
#include cunistd.h> pid_t fork(void); 返回值:子進程中返回 0。父進程中返回子進程 ID. 出錯返回 - 1
由 fork 創建的新進程被稱爲子進程(child process)。fork 函數被調用一次,但返回兩次。兩次返回的唯一區別是子進程的返回值是 0,而父進程的返回值則是新子進程的進程 ID。將子進程 ID 返回給父進程的理由是:因爲一個進程的子進程可以有多個,並且沒有一個函數使一個進程可以獲得其所有子進程的進程 ID。fork 使子進程得到返回值 0 的理由是:一個進程只會有一個父進程,所以子進程總是可以調用 getppid 以獲得其父進程的進程 ID(進程 ID0 總是由內核交換進程使用,所以一個子進程的進程 ID 不可能爲 0)。
子進程和父進程繼續執行 fork 調用之後的指令。子進程是父進程的副本。例如,子進程獲得父進程數據空間、堆和棧的副本。注意,這是子進程所擁有的副本。父、子進程並不共享這些存儲空間部分。父、子進程共享正文段。由於在 fork 之後經常跟隨着 exec,所以現在的很多實現並不執行一個父進程數據段、棧和堆的完全複製。作爲替代,使用了寫時複製(Copy-On-Write,COW) 技術。這些區域由父、子進程共享,而且內核將它們的訪問權限改變爲只讀的。如果父、子進程中的任一個試圖修改些區域,則內核只爲修改區域的那塊內存製作一個副本,通常是虛擬存儲器系統中的一 “頁”。Bach 和 McKusick 等對這種特徵做了更詳細的說明。
五、vfork 創建進程
1. vfork 函數 也可以創建進程,與 fork 有什麼區別?
關鍵區別一: vfork 直接使用父進程存儲空間,不用拷貝
關鍵區別二: vfork 保證子進程先運行,當子進程調用 exit 退出後,父進程才執行
2. fork 進程調度 父子進程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid >0){
while(1){
printf("this is father print pid is %d\n",getpid());
sleep(3);
}
}else if(pid == 0){
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(3);
}
}
return 0;
}
3. vfork 進程調度 父子進程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
pid = vfork();
if(pid >0){
while(1){
printf("this is father print pid is %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(0);
}
}
}
return 0;
}
vfork 保證子進程先運行,當子進程調用 3 次 exit 退出後,父進程才執行
4. 子進程改變 cnt 值,在父進程運行時候也被改變
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
printf("cnt=%d\n",cnt);
pid = vfork();
if(pid >0){
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print pid is %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(0);
}
}
}
return 0;
}
六、ps 常帶的一些參數
下面對 ps 命令選項進行說明:
ps -ef 顯示所有進程,全格式形式查看進程:
ps -ef 的每列的含義是什麼呢?
七、進程退出
1. 子進程退出方式
正常退出:
-
Mian 函數調用 return
-
進程調用 exit(),標準 c 庫
-
進程調用_exit() 或者——Exit(), 屬於系統調用
-
進程最後一個線程返回
-
最後一個線程調用 pthread_exit
異常退出:
-
調用 abort
-
當進程收到某些信號時候,如 ctrl+C
-
最後一個線程對取消(cancellation), 請求作出響應
不管進程如何終止,最後都會執行內核中的同一段代碼。這段代碼爲相應進程關閉所有打開描述符,釋放它所使用的存儲器等。
對上述任意一種終止情形,我們都希望終止進程能夠通知其父進程它是如何終止的。對於三個終止函數(exit、_exit 和_Exit),實現這一點的方法是,將其退出狀態作爲參數傳送給函數。【如上面示例裏面寫到的 cnt==3 情況下,exit(0); 這個 0 就是子進程退出狀態。】在異常終止情況下,內核(不是進程本身)產生一個指示其異常終止原因的終止狀態。在任何一種情況下,該終止進程的父進程都能用 wait 或者 waitpid 取得其終止狀態。
正常退出的三個函數:
#include<stdlib.h>
void exit(int status);
#include<unistd.h>
void _exit(int status);
#include<stdlib.h>
void _Exit(int status);
記得在結束子進程的時候要手動退出,不要使用 break;會導致數據被破壞。 三種退出函數種,更推薦 exit(); exit 是 _exit 和_Exit 的一個封裝, 會清除,沖刷緩衝區,把緩存區數據進程處理在退出。
2. 等待子進程退出
爲什麼要等待子進程退出?
創建子進程的目的就是爲了讓它去幹活,在網絡請求當中來了一個新客戶端介入,創建子進程去交互,幹活也要知道它幹完沒有. 比如正常退出(exit/_exit /_Exit)爲 完成任務 若異常退出 (abort)不想幹了, 或被殺了
所有要等待子進程退出,而且還要收集它退出的狀態 等待就是調用 wait 函數 和 waitpid 函數
3. 殭屍進程
子進程退出狀態不被收集,會變成僵死進程(殭屍進程)
正如以下例子,就是子進程退出沒有被收集,成了殭屍進程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
printf("cnt=%d\n",cnt);
pid = vfork();
if(pid >0){
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print pid is %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(0);
}
}
}
return 0;
}
4. 等待函數:wait(狀態碼); 的使用:
#include<sys/types.h>
#inlcude<sys/wait.h>
pid_t wait(int *status); //參數status 是一個地址
pid_t waitpid(pid_t pid , int *status ,int options);
int waitid(idtype_t idtype ,id_t id ,siginfo_t *infop, int options);
-
如果其所有子進程都還在運行,則阻塞。:通俗的說就是子進程在運行的時候,父進程卡在 wait 位置阻塞,等子進程退出後,父進程開始運行。
-
如果一個子進程已終止,正等待父進程獲取其終止狀態,則會取得該子進程的終止狀態立即返回。
-
如果它沒有任何子進程,則立即出錯返回。
status 參數:是一個整型數指針 非空:子進程退出狀態放在它所指向的地址中。空:不關心退出狀態
檢查 wait 和 waitpid 所返回的終止狀態的宏
5. 收集退出進程狀態
pid = vfork();
if(pid >0){
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print pid is %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
wait(NULL); // 參數:status 是一個地址 爲空 表示不關心退出狀態
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(0);
}
}
}
wait(NULL); // 參數:status 是一個地址 爲空 表示不關心退出狀態
沒有了 11567 子進程,這樣就不是殭屍進程了
int main()
{
pid_t pid;
int cnt=0;
int status =10;
printf("cnt=%d\n",cnt);
pid = vfork();
if(pid >0){
wait(&status); // 參數status是一個地址
printf("child out ,chile status =%d\n",WEXITSTATUS(status)); //要解析狀態碼,需要藉助WEXITSTATUS
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print pid is %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(5);
}
}
}
int status =10;
wait(&status); // 參數 status 是一個地址
printf("child out ,chile status =%d\n",WEXITSTATUS(status)); // 要解析狀態碼,需要藉助 WEXITSTATUS
6. 等待函數:waitpid()的使用;
wait 和 waitpid 的區別之一:
wait 使父進程(調用者)阻塞,waitpid 有一個選項 ,可以使父進程(調用者)不阻塞。
pid_t waitpid(pid_t pid , int *status ,int options);
對於 waitpid 函數種 pid 參數的作用解釋如下:
waitpid 的 options 常量:
waitpid 來使得父進程不阻塞代碼:
int main()
{
pid_t pid;
int cnt=0;
int status =10;
printf("cnt=%d\n",cnt);
pid = vfork();
if(pid >0){
waitpid(pid,&status,WNOHANG); // 參數pid 是子進程號,WNOHANG是若由pid指定的子進程並不是立即可用的,則waitpid不阻塞,此時其返回值爲0;
printf("child out ,chile status =%d\n",WEXITSTATUS(status));
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print pid is %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(5);
}
}
}
八、孤兒進程
1. 孤兒進程的概念:
父進程如果不等待子進程退出,在子進程結束前就了結束了自己的 “生命”,此時子進程就叫做孤兒進程。
2. 孤兒進程被收留:
Linux 避免系統存在過多孤兒進程,init 進程收留孤兒進程,變成孤兒進程的父進程【init 進程 (pid=1) 是系統初始化進程】。init 進程會自動清理所有它繼承的殭屍進程。
孤兒進程的代碼:
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
int status =10;
pid = fork();
if(pid >0){
printf("this is father print pid is %d\n",getpid());
}
else if(pid == 0){
while(1){
printf("this is child print pid is =%d,my father pid is=%d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(5);
}
}
}
return 0;
}
父進程運行結束前,子進程的父進程 pid 還是 13098。父進程運行結束後,子進程的父進程變成了 init 進程 (pid=1)。
九、exec 族函數
1. exec 族函數的作用:
我們用 fork 函數創建新進程後,經常會在新進程中調用 exec 函數去執行另外一個程序。當進程調用 exec 函數時,該進程被完全替換爲新程序因爲調用 exec 函數並不創建新進程,所以前後進程的 ID 並沒有改變。
2. 爲什麼要用 exec 族函數,有什麼作用?
-
一個父進程希望複製自己,使父、子進程同時執行不同的代碼段。這在網絡服務進程中是常見的——父進程等待客戶端的服務請求。當這種請求到達時,父進程調用 fork,使子進程處理此請求。父進程則繼續等待下一個服務請求到達。
-
一個進程要執行一個不同的程序。這對 shell 是常見的情況。在這種情況下,子進程從 fork 返回後立即調用 exec。
3. exec 族函數定義:
功能:
exec 函數族提供了一種在進程中啓動另一個程序執行的方法,它可以根據指定的文件名或目錄名找到可執行文件,並用它來取代原調用進程的數據段、代碼段和堆棧段。在執行完之後,原調用進程的內容除了進程號外,其他全部都被替換了。在調用進程內部執行一個可執行文件,可執行文件既可以是二進制文件,也可以是 linux 下可執行的腳本文件。【通俗理解就是執行 demo1 的同時,執行一半去執行 demo2。】
函數族:
execl、execlp、execle、execv、execvp、execvpe
函數原型:
#include<unistd.h>
extern char **environ;
int execl(char *path , char *arg , ...);
int execlp(char *file , char *arg , ...);
int execle(char *path , char *arg , ... , char *const envp[] );
int execv(char *path , char *const argv[] );
int execvp(char *file , char *const atgv[] );
int execvpe(char *file , char *const argv[] , char *const envp[]);
返回值:
exec 函數族的函數執行成功後不會返回,調用失敗時,會設置 errno 並返回 - 1,然後從原程序的調用點接着往下執行。
參數說明:
path :可執行文件的路徑名字 arg:可執行程序所帶的參數,第一個參數爲可執行文件名字,沒有帶路徑且 arg 必須以 NULL 結束。file:如果參數 file 中包含 /,則就將其視爲路徑名,否則就按 PATH 環境變量,在它所指定的各目錄中搜尋可執行文件。
exec 族函數參數極難記憶和分辨,函數名中的字符會給我們一些幫助:
4. exec 函數 帶 l 帶 p 帶 v 來說明參數特點
先寫一個帶參數的程序,輸入參數 輸出參數,在上一篇 Linux 文件編程裏,main 參數我們學過。
./echoarg 代碼:
#include<stdio.h>
int main(int argc , char *argv[])
{
int i =0;
for(i =0 ;i <argc;i++){
printf("argv[%d]:%s\n",i ,argv[i]);
}
return 0;
}
在執行 a.out 代碼一半的時候,調用上面的代碼 echoarg
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
printf("brfore execl\n");
//int execl(char *path , char *arg , ...);
if(execl("/bin/echoarg","echoarg","abc",NULL)==-1)
{
printf("execl failed!\n");
}
printf("after execl \n");
return 0;
}
exec 函數族的函數執行成功後不會返回,調用失敗時,會設置 errno 並返回 - 1,然後從原程序的調用點接着往下執行。
if(execl("/bin/echoarg","echoarg","abc",NULL)==-1) 源代碼:int execl(char *path , char *arg , ...); // 最後一個參數是:arg 必須以 NULL 結束。
在執行 a.out 代碼一半的時候,調用上面的代碼 echoarg: exec 函數族的函數執行成功後不會返回,調用失敗時,會設置 errno 並返回 - 1,然後從原程序的調用點接着往下執行。
perror("why"); // 用來在執行錯誤時候,查詢錯誤原因
若要調用 ech 執行一般執行 ls ,同理。只需要改動
if(execl("/bin/ls","ls",NULL,NULL)==-1)
if(execl("/bin/ls","ls","-l",NULL)==-1)
execlp 和 execl 的區別
帶 p : 可以通過環境變量 PATH 環境尋找可執行文件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
printf("brfore execl\n");
//int execl(char *path , char *arg , ...);
if(execl("ls",";s",NULL,NULL)==-1)
{
printf("execl failed!\n");
}
printf("after execl \n");
return 0;
}
在路徑中不用寫具體路徑,就可以自動找到文件
execvp 和 execl 的區別
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
printf("brfore execl\n");
char *argv[] = {"ps",NULL,NULL};
if(execvp("ps",argv)==-1)
{
printf("execl failed!\n");
}
printf("after execl \n");
return 0;
}
char *argv[] = {"ps",NULL,NULL}; if(execvp("ps",argv)==-1)
結果與上面相同
5. 任何目錄下執行程序
一個程序在目錄下能運行,換一個目錄就無法運行,如果把程序配置到環境變量裏面去。
==pwd 顯示當前路徑 echo
PATH: [pwd 顯示的當前路徑]==
就可以在任何目錄下執行程序了
6. exec 配合 fork 使用
一個進程要執行一個不同的程序。這對 shell 是常見的情況。在這種情況下,子進程從 fork 返回後立即調用 exec。
1. 不用 exec 的方法: 實現功能,當父進程檢查到輸入爲 1 的時候,創建子進程把配置文件的字段值修改掉。
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid>0)
{
wait(NULL);
}
if(pid == 0){
int fdSrc;
char *readBuf=NULL;
fdSrc = open("config.txt",O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek (fdSrc,0,SEEK_SET);
readBuf =(char *)malloc(sizeof(char)*size+8);
int n_read= read(fdSrc,readBuf,size);
char *p=strstr(readBuf,"LENG="); //找到(要修改的)位置
//參數1 要找的源文件 2.“要找的字符串”
if(p==NULL){
printf("not found\n");
exit(-1);
}
p=p+strlen("LENG="); //移動字符串個字節
*p='0'; //*p 取內容
lseek (fdSrc,0,SEEK_SET);
int n_write =write(fdSrc,readBuf,strlen(readBuf));
close(fdSrc);
exit(0);
}
}else {
printf("do noting\n");
}
}
return 0;
}
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
execl("./changdata","changdata","config.txt",NULL);
exit(0);
}
}else {
printf("do noting\n");
}
}
return 0;
}
使用 execl 和 fork 結合 也能做到上面結果,而且更方便,但是在 ./changdata 可執行文件存在的情況下。
十、system 函數
1. system 函數定義:
函數原型:
#include<stdlib.h>
int system(const char * string);
函數說明:
system() 會調用 fork() 產生子進程,由子進程來調用 / bin/sh-c string 來執行參數 string 字符串所代表的命令,此命令執行完後隨即返回原調用的進程。在調用 system() 期間 SIGCHLD 信號會被暫時擱置,SIGINT 和 SIGQUIT 信號則會被忽略。
返回值:
system() 函數的返回值如下:** 成功,則返回進程的狀態值;當 sh 不能執行時,返回 127;失敗返回 - 1;**
2. system 函數的使用:
用 system 也可以做到 execl 的功能 用 system 實現修改配置 數值代碼:
int main()
{
pid_t pid;
int data = 10;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
execl("./changdata config.txt");
exit(0);
}
}else {
printf("do noting\n");
}
}
return 0;
}
3. system 和 execl 不同的是:
sysem 運行完調用的可執行文件後還會繼續執行源代碼。
*附加說明:
在編寫具有 SUID/SGID 權限的程序時請勿使用 system(),system() 會繼承環境變量,通過環境變量可能會造成系統安全的問題。
十一、popen 函數
1. popen 函數的定義:
函數原型:
#include<stdio.h>
FILE *popen (const char *command ,const char *type);
int pclose(FILE *stream);
參數說明:
command: 是一個指向以 NULL 結束的 shell 命令字符串的指針。這行命令將被傳到 bin/sh 並且使用 -c 標誌 ,shell 將執行這個命令。
type: 只能是讀或者寫中的一種,得到的返回值(標準 I/O 流)也具有和 type 相應 的只讀或只寫類型。如果 type 是”r“則文件指針連接到 command 的標準輸出;如果 type 是”w“則文件指針連接到 command 的標準輸入。
返回值:
如果調用成功,則返回一個讀或者打開文件的指針,如果失敗,返回 NULL,具體錯誤要根據 errno 判斷
int pclose(FILE *stream) 參數說明:stream:popen 返回對丟文件指針 返回值:如果調用失敗,返回 - 1
作用:
popen()函數用於創建一個管道:其內部實現爲調用 fork 產生一個子進程,執行一個 shell 以運行命令來開啓一個進程這個進程必須由 pclose()函數關閉。
popen 比 system 在應用中的好處:== 可以獲取運行的輸出結果 ==
2. popen 函數的使用:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
char ret[1024]={0};
FILE *fp;
fp = popen("ps","r");
int nread = fread(ret,1,1024,fp);
printf("read ret %d byte ,ret =%s\n",nread ,ret);
return 0;
}
結果發現: popen 函數結束後,ps 輸出的內容, 都捕獲到 ret 數組裏面去了。popen 可以獲取運行的輸出結果 ,可以讀取也可以寫入文件中。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/fYaWaF986VvwALXdtmmi3A