1. 程式人生 > >Linux C 程序控制筆記(一)

Linux C 程序控制筆記(一)

一、程序

  • 程序是一個動態實體,是程式的一次執行過程,是作業系統資源分配的基本單位。程序是執行中的程式,程式是一些儲存在硬碟上的可執行程式碼。

二、程序結構

Linux中程序由三部分組成:程式碼段,資料段,堆疊段

程式碼段(存放可執行程式碼) 資料段(存放程式全域性變數,常量,靜態變數)

堆疊段(堆存放動態分配的記憶體變數,棧用於函式呼叫,存放函式的引數,函式內部定義的區域性變數)

三、建立程序

  • 程序建立的兩種方式

  • 由作業系統建立:

                 由作業系統建立的程序之間的關係平等,一般不存在資源繼承關係。

  • 由父程序(建立該程序的程序稱父程序)建立:

                 對於父程序建立的程序(子程序),子程序和父程序存在隸屬關係。父子程序共享程式碼段,子程序還獲得父程序資料段、堆、棧的複製。

  • fork函式

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void)

呼叫fork函式後,程序實際上已經分裂為父子兩個程序,父子程序在呼叫fork函式處分開,fork函式有兩個返回值,一個是父程序呼叫fork後的返回值,返回建立的子程序的ID;另一個是子程序中fork函式的返回值,0。返回兩次的前提是程序建立成功,若失敗則只返回-1。

  • 下面是一個父子程序交替執行的例子:

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    pid_t pid;
    char* msg;
    int k;

    printf("Processs Creation Study\n");
    pid = fork();
    switch(pid) {
        case 0:
            msg = "Child process is running";
            k = 3;
            break;
        case -1:
            perror("Process creation failed\n");
            break;
        default:
            msg = "Parent process is running";
            k = 5;
            break;
    }

    while(k > 0){
        puts(msg);
        sleep(1);
        k--;
    }

    exit(0);

}

執行結果:

Processs Creation Study
Parent process is running
Child process is running
Parent process is running
Child process is running
Parent process is running
Child process is running
Parent process is running
Parent process is running
  •  如果一個子程序的父程序先結束,子程序就被稱為孤兒程序,由init程序收養,成為init程序的子程序

下面是一個孤兒程序:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
    pid_t pid;
    pid = fork();
    switch(pid) {
        case 0:
            while(1) {
                printf("A background proccess,PID:%d\n, ParentID: %d\n",getpid(),getppid());
                sleep(3);
            }
        case -1:
            perror("Proccess creation failed\n");
            exit(-1);
        default:
            printf("I am parent proccess,my pid is %d\n",getpid());
            exit(0);
    }
    return 0;
}

以上是fork成功建立程序的過程,在失敗時返回-1,失敗的原因通常是父程序擁有的子程序的個數超過了規定的限制,此時errno值為EAGAIN,記憶體不足也會導致程序建立失敗,此時errno值為ENOMEM。

子程序會繼承父程序的使用者ID,組ID,當前工作目錄,根目錄,開啟的檔案,建立檔案時用的遮蔽字,上下文環境,共享的儲存段,資源限制等。也有一些與父程序不一樣的屬性:

  • 子程序有自己唯一的程序ID
  • fork返回值不同
  • 不同的父程序ID。子程序的父程序ID為建立他的父程序ID
  • 子程序共享父程序開啟的檔案描述符,但父程序對檔案描述符的改變不會影響子程序中的檔案描述符
  • 子程序不繼承父程序設定的檔案鎖
  • 子程序不繼承父程序設定的警告
  • 子程序的未決訊號集被清空
  • vfork函式

  1. vfork和fork一樣都是呼叫一次,返回兩次
  2. fork建立的子程序只是完全複製父程序的資源,這樣得到的子程序獨立於父程序,有良好的併發性。而使用vfork建立一個子程序時,作業系統並不將父程序的地址空間完全複製到子程序,用vfork建立的子程序共享父程序的地址空間,也就是說子程序完全執行在父程序的地址空間上,子程序對該地址空間中任何資料的修改同樣為父程序所見。
  3. fork建立程序,父子那個先執行取決於系統的排程演算法。而vfork保證子程序先執行,當他呼叫了exec或exit後,父程序才可能被排程。如果在呼叫exec或exit之前子程序要依靠父程序的某個行為,就會導致死鎖。

下面這個例子對比fork和vfork的區別:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int globVar = 5;
int main()
{
    pid_t pid;
    int var = 1, i;
    printf("fork is differentwith vfork \n");
    pid = fork();
    //pid = vfork();
    switch(pid) {
        case 0: 
            i = 3;
            while(i-- > 0){
                printf("Child process is running\n");
                globVar++;
                var++;
                sleep(1);
            }
            printf("Child's globVar = %d,var = %d\n",globVar,var);
            exit(0);//break;
        case -1:
            perror("Process creation failed\n");
            exit(0);
        default:
            i = 5;
            while(i-- > 0){
                printf("Parent process is running\n");
                globVar++;
                var++;
                sleep(1);
            }
            printf("Parent's globVar = %d ,var = %d\n",globVar ,var);
            exit(0);
    }
}

當用fork執行的結果:

fork is differentwith vfork 
Parent process is running
Child process is running
Parent process is running
Child process is running
Parent process is running
Child process is running
Parent process is running
Child's globVar = 8,var = 4
Parent process is running
Parent's globVar = 10 ,var = 6

子程序繼承了父程序的全域性變數和區域性變數,由最後父子程序globVar和var的結果可以證明fork的子程序有自己獨立的地址空間。

再用vfork執行一次:

fork is differentwith vfork 
Child process is running
Child process is running
Child process is running
Child's globVar = 8,var = 4
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent's globVar = 13 ,var = 9

同理可以推出vfork的子程序共享父程序的地址空間。

  • 守護程序

  • 什麼是守護程序?

守護程序是指在後臺執行的,沒有控制終端與之相連的程序。他獨立於控制終端,通常週期性的執行某種任務。

  • 守護程序有什麼用?

Linux的大多數伺服器就是用守護程序的方式實現的,如Internet伺服器程序inetd,Web伺服器程序http等。守護程序在後臺執行,類似於Windows中的系統服務.

  • 守護程序的啟動方式

守護程序可以在Linux系統啟動時從啟動指令碼/etc/rc.d中啟動;可以由作業規劃程序crond啟動;還可以由使用者終端(Shell)執行。

  • 編寫建立守護程序的要點

  1. 讓程序在後臺執行。呼叫fork產生一個子程序,然後使父程序退出
  2. 呼叫setsid建立一個新對話期。控制終端,登陸會話和程序組通常是從父程序繼承下來的,守護程序要擺脫他們,不受他們影響,方法是呼叫setsid使程序成為一個會話組長
  3. 禁止程序重新開啟終端。再次通過fork建立新的子程序,使呼叫fork的程序退出
  4. 關閉不需要的檔案描述符。子程序從父程序繼承開啟的檔案描述符。為了不浪費系統資源,造成程序所在的檔案系統無法卸下以及引起無法預料的錯誤。先得到最高檔案描述符值,然後用一個迴圈程式,關閉0到最高檔案描述符值的所有檔案描述符
  5. 將當前目錄更改為根目錄。當守護程序當前工作目錄在一個裝配檔案系統中時,該檔案系統不能被解除安裝,一般需要將工作目錄改為根目錄
  6. 將檔案建立時使用的遮蔽字設定為0。程序從建立他的父程序那裡繼承的檔案建立遮蔽字可能會拒絕某些許可權。為防止這一點,使用umask(0)將遮蔽字清0
  7. 處理SIGCHLD訊號

下面是建立守護程序的程式碼:

#include <stdio.h>
#include <sys/param.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <time.h>
#include <syslog.h>
int init_daemon()
{
	int pid;
	int i;

	//忽略終端I/O訊號,STOP訊號
	signal(SIGTTOU,SIG_IGN);
	signal(SIGTTIN,SIG_IGN);
	signal(SIGTSTP,SIG_IGN);
	signal(SIGHUP,SIG_IGN);
	
	pid = fork();
	if(pid > 0) {
		exit(0); //結束父程序,使子程序成為後臺程序
	}
	else if(pid < 0){
		return -1;
	}
	//建立一個新的程序組,在這個新的程序組中,子程序成為這個程序組的首程序,以使該程序脫離所有終端
	setsid();
	
	//再次新建一個程序,退出父程序,保證該程序不是程序組長,同時讓該程序無法再開啟一個新的終端
	pid = fork();
	if(pid > 0){
		exit(0);
	}
	else if(pid < 0){
		return -1;
	}
	
	//關閉所有從父程序繼承的不再需要的檔案描述符
	for(i = 0; i <NOFILE; close(i++));
	//改變工作目錄,使程序不與任何檔案系統聯絡
	chdir("/");
	
	//將檔案遮蔽字設定為0
	umask(0);
	
	//忽略SIGCHLD訊號
	signal(SIGCHLD,SIG_IGN);
	
	return 0;
}
int main()
{
	time_t now;
	init_daemon();
	syslog(LOG_USER|LOG_INFO,"測試守護程序!\n");
	while(1){
		sleep(8);
		time(&now);
		syslog(LOG_USER|LOG_INFO,"系統時間:\t%s\t\t\n",ctime(&now));
	}
}

編譯執行,然後用ps -ef檢視程序狀態:

UID        PID  PPID  C STIME TTY          TIME CMD
crdbyb   24682  2329  0 15:26 ?        00:00:00 ./a.out

四、程序退出

  • 兩種退出方式

  • 正常退出

  • 在main函式中執行return
  • 呼叫exit函式
  • 呼叫_exit函式
  • 異常退出

  • 呼叫about函式
  • 程序收到某個訊號,而該訊號使程式終止

兩種退出方式都會執行核心中的同一段程式碼。這段程式碼用來關閉程序所有已開啟的檔案描述符,釋放他所佔用的記憶體和其他資源

  • 退出方式的比較

  • exit和return,exit是一個函式,有引數,而return是函式執行完後的返回。exit把控制權交給系統,而return將控制權交給呼叫函式
  • exit和about,exit是正常終止程序,而about是異常終止
  • exit(int exit_code):exit中的引數exit-code為0代表程序正常終止,若為其他值表示程式執行過程中有錯誤發生
  • exit()和_exit()的區別:exit在標頭檔案stdlib.h中宣告,而_exit()宣告在標頭檔案unistd.h中。兩個函式均能正常終止程序,但是_exit()會執行後立即返回給核心,而exit()要先執行一些清除操作,然後將控制權交給核心