天天看點

Linux 程序程式設計入門

關于程序和線程的關系,之前一口君寫過這幾篇文章,大家可以參考下。

本文從頭帶着大家一起學習Linux程序

《​​搞懂程序組、會話、控制終端關系,才能明白守護程序幹嘛的?​​》

《​​[粉絲問答6]子程序程序的父程序關系​​》

《​​多線程詳解,一篇文章徹底搞懂多線程中各個難點​​》

《​​一個多線程的簡單例子讓你看清線程排程的随機性​​》

Linux 程序篇

一、程序相關概念

了解程序的時候先來了解幾個問題,明白以下問題,就懂了程序的概念

1.什麼是程式,什麼是程序,兩者之間的差別?

  1. 程式是靜态的概念,gcc xxx.c -o pro 磁盤中生成pro檔案,叫做程式 程式如:電腦上的圖示
  2. 程序是程式的一次運作活動, 通俗點說就是程式跑起來了,系統中就多了一個程序

2.如何檢視系統中有哪些程序?

  1. 使用ps指令檢視 : ps-aux 在ubuntu下檢視,

    在實際工作中,配合grep來查找程式中是否存在某一個程序

    grep 過濾程序 : ps -aux | grep init 就隻把帶有init的程序過濾出來
    Linux 程式程式設計入門
  2. 使用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 程式程式設計入門
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 時候,作業系統會劃分一塊記憶體空間,如何配置設定呢?

如下圖:

Linux 程式程式設計入門

二、建立程序函數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

Linux 程式程式設計入門

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;

}      
Linux 程式程式設計入門

父子程序都會進入if 中,但是輸出結果會不同

在fork之前的pid 是8915 是父程序 ,fork之後pid是子程序 8916

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;
}      
Linux 程式程式設計入門

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;
}      
Linux 程式程式設計入門

這樣更清楚明了的看到

fork 傳回值:9915>0 是父程序 父程序号是9114

fork 傳回值:=0 是子程序 子程序号是9915

三、程序建立後 發生了什麼事?

Linux 程式程式設計入門

1 在記憶體空間中fork後發生了什麼?

Linux 程式程式設計入門

2. ./demo4 運作的程式父程序是誰?

int main(int argc, const char *argv[])
{
        while(1);
        return 0;
}      

./ demo4 編譯運作後,我們ps -ef 檢視程序ID

Linux 程式程式設計入門

由上圖可知,./demo4 程序的程序ID是12677,父程序ID是12587,即程序bash:

bash的父程序是gnome-terminal,是以我們打開1個Linux終端,其實就是啟動了1個gnome-terminal程序。我們在這個終端上執行./a.out其實就是利用gnome-terminal的子程序bash通過execve()将建立的子程序裝入a.out:

四、建立新程序的實際應用場景

1. fork建立子程序的一般目的:

  • 一個父程序希望複制自己,使父、子程序同時執行不同的代碼段。這在網絡服務程序中是常見的——父程序等待用戶端的服務請求。當這種情求達到時,父程序調用fork,使子程序處理此請求。父程序則繼續等待下一個服務請求到達。
  • 一個程序要執行一個不同的程式。這對shell是常見的情況,在這種情況下子程序從fork傳回後立即調用exec。
Linux 程式程式設計入門

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時候,模拟沒有用戶端進行互動

Linux 程式程式設計入門

輸入1時候,模拟有用戶端進行互動 ,建立子程序來進行互動,子程序号為:9756

Linux 程式程式設計入門

模拟多個用戶端進行互動時 ,建立多個子程序來進行互動,子程序号為:9756 / 9758 / 9759

Linux 程式程式設計入門

檢視系統程序:

Linux 程式程式設計入門

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;

}      
Linux 程式程式設計入門

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退出後,父程序才執行

Linux 程式程式設計入門

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;
}      
Linux 程式程式設計入門

六、ps 常帶的一些參數

下面對ps指令選項進行說明:

指令參數 說明
-e 顯示所有程序.
-f 全格式。
-h 不顯示标題。
-l 長格式。
-w 寬輸出。
-a 顯示終端上的所有程序,包括其他使用者的程序。
-r 隻顯示正在運作的程序。
-u 以使用者為主的格式來顯示程式狀況。
-x 顯示所有程式,不以終端機來區分。

ps -ef 顯示所有程序,全格式形式檢視程序:

ps -ef 的每列的含義是什麼呢?

Linux 程式程式設計入門
指令參數 說明
UID: 程式被該 UID 所擁有,指的是使用者ID
PID: 就是這個程式的 ID
PPID : PID的上級父程序的ID
C : CPU使用的資源百分比
STIME : 系統啟動時間
TTY: 登入者的終端機位置
TIME : 使用掉的 CPU時間。
CMD: 所下達的指令為何

七、程序退出

1. 子程序退出方式

正常退出:

  1. Mian函數調用return
  2. 程序調用exit(),标準c庫
  3. 程序調用_exit()或者——Exit(),屬于系統調用
  4. 程序最後一個線程傳回
  5. 最後一個線程調用pthread_exit

異常退出:

  1. 調用abort
  2. 當程序收到某些信号時候,如ctrl+C
  3. 最後一個線程對取消(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;
}      
Linux 程式程式設計入門

運作三次子程序後,退出,父程序一直運作

Linux 程式程式設計入門

結果:在檢視程序時發現,父程序11314正在運作 “S+” 而子程序11315 停止運作 “z+” z表示zombie(僵屍)

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 所傳回的終止狀态的宏

說明
WIFEXITED (status) 若為正常終止子程序傳回的狀态,則為真。對于這種情況可執行WEXITSTATUS(status),取子程序傳送給exit、_exit 或_Exit參數的低8位
WIFSIGNALED (status) 若為異常終止子程序傳回的狀态,則為真(接到一個不捕捉的信号)。對于這種情況,可執行WTERMSIG(status),取使子程序終止的信号編号。另外,有些實作(非Single UNIX Specification)宏義宏WCOREDUMP(status),若已産生終止程序的core檔案,則它傳回真
WIFSTOPPED (status) 若為目前暫停子程序的傳回的狀态,則為真,對于這種情況,可執行WSTIOPSIG(status),取使子程序暫停的信号編号
WIFCONTINUED (status) 若在作業控制暫停後已經繼續的子程序傳回了狀态,則為真。(POSIX.1的XSI擴充,僅用于waitpid。)
比如說:exit(3) wait (狀态碼); 要通過宏來解析狀态碼

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子程序,這樣就不是僵屍程序了

Linux 程式程式設計入門

收集子程序退出狀态示例代碼:

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

Linux 程式程式設計入門

結果顯示:exit(5); 就能看到子程序退出的狀态 status=5

6. 等待函數:waitpid()的使用;

wait和waitpid的差別之一:

wait使父程序(調用者)阻塞,waitpid有一個選項 ,可以使父程序(調用者)不阻塞。

pid_t waitpid(pid_t pid , int *status ,int options);

對于waitpid函數種pid參數的作用解釋如下:

pid == -1 等待任一子程序。就這一方面而言,waitpid與wait等效。
pid > 0 等待其程序ID與pid相等的子程序。
pid == 0 等待其組ID等于調用程序組ID的任一子程序
pid <-1 等待其組ID等于pid絕對值的任一子程序。

waitpid 的 options 常量:

WCONTINUED 若實作支援作業控制,那麼由pid指定的任一子程序在暫停後已經繼續,但其狀态尚未報告,則傳回其狀态(POSIX.1的XSI擴充)
WNOHANG 若由pid指定的子程序并不是立即可用的,則waitpid不阻塞,此時其傳回值為0;
WUNTRACED 若某實作支援作業控制,而由pid指定的任一子程序已處于暫停狀态。

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);
               }
          }
   }      
Linux 程式程式設計入門

子程序和父程序同時進行

Linux 程式程式設計入門

但是發現子程序12275 在系統查詢程序中 還是變成了僵屍程序

原因是 WNOHANG是不等待參數,它隻運作一遍 ,當他運作時候,子程序沒死,等子程序死後,他沒運作,就沒有收到停止狀态,是以成了僵屍程序。

八、孤兒程序

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;
}      
Linux 程式程式設計入門

父程序運作結束前,子程序的父程序pid還是13098。

父程序運作結束後,子程序的父程序變成了init程序( pid=1)。

Linux 程式程式設計入門

九、exec族函數

1. exec族函數的作用:

我們用fork函數建立新程序後,經常會在新程序中調用exec函數去執行另外一個程式。當程序調用exec函數時,該程序被完全替換為新程式因為調用exec函數并不建立新程序,是以前後程序的ID并沒有改變。

2. 為什麼要用exec族函數,有什麼作用?

  1. 一個父程序希望複制自己,使父、子程序同時執行不同的代碼段。這在網絡服務程序中是常見的——父程序等待用戶端的服務請求。當這種請求到達時,父程序調用fork,使子程序處理此請求。父程序則繼續等待下一個服務請求到達。
  2. 一個程序要執行一個不同的程式。這對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族函數參數極難記憶和分辨,函數名中的字元會給我們一些幫助:

字元 說明
l 使用參數清單
p 使用檔案名,并從PATH環境尋找可執行檔案
v 應該先構造一個指向各參數的指針數組,然後将該數組的位址作為這些函數的參數。
e 多了envp[]數組,使用新的環境變量代替調用程序的環境變量

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,然後從原程式的調用點接着往下執行。

Linux 程式程式設計入門
perror(“why”); //用來在執行錯誤時候,查詢錯誤原因

若要調用ech 執行一般執行ls ,同理。隻需要改動

if(execl(“/bin/ls”,“ls”,NULL,NULL)==-1)
Linux 程式程式設計入門

若要調用ech 執行一般執行ls-l ,同理。

if(execl(“/bin/ls”,“ls”,“-l”,NULL)==-1)
Linux 程式程式設計入門

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;
}      

在路徑中不用寫具體路徑,就可以自動找到檔案

Linux 程式程式設計入門

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 檢視環境變量

export PATH=$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;
}      
Linux 程式程式設計入門

實作了當父程序檢查到輸入為1的時候,建立子程序把配置檔案的字段值修改掉。

Linux 程式程式設計入門

2. 用exec的方法: 實作功能,當父程序檢查到輸入為1的時候,建立子程序把配置檔案的字段值修改掉。

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;
}      
Linux 程式程式設計入門
Linux 程式程式設計入門
使用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;
}      
Linux 程式程式設計入門

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 在應用中的好處:

可以擷取運作的輸出結果

Linux 程式程式設計入門

popen函數執行完,執行結果到管道内,資料流出的時候,在管道尾部fread就可以讀出執行資料,就能實作把資料讀到或寫到想要的緩沖區裡。

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可以擷取運作的輸出結果 ,可以讀取也可以寫入檔案中。

繼續閱讀