天天看點

Unix程序與信号一、程序二、信号

程序是Unix系統程式設計的重要内容,多程序可以讓程式的功能更加強大。本文會先介紹一些關于程序的常識,然後介紹兩種建立程序的方法,以及兩種方法的差別,最後再講一下信号。

目錄

一、程序

1、程序和程式的關系

2、一些常識

3、關于父程序和子程序

4、建立子程序的兩種辦法

4.1 fork()

4.2 vfork() + execl()

4.3 waitpid()和wait()

5、程序的退出

5.1 正常退出

5.2 非正常退出

二、信号

1、什麼是信号?

2、常見的信号

3 信号處理

3.1 signal()/sigaction()

3.2 父子程序之間的信号處理

4 信号的發送方式

5、sleep()和usleep()

6、信号集和信号屏蔽

6.1 信号集

6.2 信号屏蔽

一、程序

1、程序和程式的關系

程式是硬碟的檔案,是代碼的編譯連結産物,運作起來的程式就是程序,程序是記憶體中運作的程式。

2、一些常識

·作業系統支援多程序的啟動,每個程序内部支援多線程。程序之間是完全獨立的。

·程序用程序ID做唯一辨別,叫PID,函數getpid()可以擷取目前程序的pid。程序用PID表示,PID是一個非負整數。PID可以延遲重用,是以,PID在同一

時刻保證唯一。幾個常用的函數:

getpid()  -取目前程序的PID

getppid() -取目前程序的父程序的PID

getuid()  -取目前使用者的ID

·檢視程序的方式:windows裡面用任務管理器檢視。Linux/Unix 用ps指令檢視程序。

ps 隻能顯示目前終端啟動的程序

ps -aux :Linux 專用選項,Unix不直接支援

ps -ef  :Unix/Linux通用選項

ps 可以檢視程序的如下資訊:

程序PID、程序的啟動者(屬主)、CPU和記憶體使用率、狀态、父程序PID、啟動的程式是哪個。其中,程序的狀态主要包括:

S -休眠,程序大多數處于休眠狀态

s -說明程序有子程序(父程序)

R -正在運作的程序

Z -僵屍程序(已經結束但是資源沒有回收的程序)

·whereis 可以檢視指令在哪個目錄下。

3、關于父程序和子程序

作業系統中的多程序是有啟動順序的,Unix系統先啟動0程序,0程序再啟動程序1和程序2(有些系統隻啟動程序1),然後0程序就休眠,程序1和程序2啟動其他的程序,其他的程序再啟動其他的程序,直到所有的程序都啟動為止。如果程序a啟動了程序b,a叫b的父程序,b叫a的子程序。

4、建立子程序的兩種辦法

4.1 fork()

fork()建立子程序,通過複制父程序建立子程序。是以父子程序對應相同的代碼區。

fork()是一個非常複雜的簡單函數。

pid_t fork(void);

傳回,如果成功,對于父程序傳回子程序的pid,對于子程序傳回0,失敗則傳回-1。沒有參數。

fork是通過複制父程序的記憶體空間建立子程序,複制除了代碼區之外的所有區域(包括緩沖區),代碼區父子程序共享(隻讀)。

fork()會建立一個子程序,子程序從fork的目前位置執行代碼,fork()之前的代碼隻執行一次,fork()之後的代碼父子程序分别執行一次(共2次)。

fork()函數自身會傳回兩次,父程序傳回子程序的pid,子程序會傳回0.

fork()建立子程序時,如果父程序有檔案描述符,子程序會複制檔案描述符

,不複制檔案表(父子程序共用一個檔案表)。

fork()建立子程序後,父子程序誰先運作不确定,誰先結束也不确定。如果子程序先結束,子程序會給父程序發信号,父程序回收子程序的資源。如果父程序先結束,子程序變成孤兒程序,子程序會認程序1(init程序)為新的父程序。

fork()建立子程序後,父子程序同時運作,如果子程序結束時給父程序發信号,父程序沒有受到信号貨沒有及時處理,子程序将變成僵屍程序。

fork()建立的子程序會複制父程序的虛拟記憶體位址,但映射到不同的實體記憶體上,同時把值拷貝過來。複制完成以後父子記憶體上獨立。

4.2 vfork() + execl()

vfork()+execl()建立子程序,父程序和子程序的代碼區完全不同,父子程序執行是完全不同的代碼。

       vfork()和fork()在文法上沒有差別,唯一差別在vfork()不複制父程序的任何資源,而是直接占用父程序的資源運作代碼,父程序處于阻塞狀态,直到子程序結束或者調用了exec系列函數(比如:execl())。

vfork()和execl()的合作方式:

vfork()可以建立新的程序,但是沒有代碼和資料,execl()建立不了新程序,但可以為程序提供代碼和資料。和fork()不同,vfork()建立子程序後確定子程序先運作,vfork()如果占用父程序的資源,必須要用exit()顯式退出。execl()是exec系統函數中的第一個,功能是啟動一個全新的程序,替換目前的程序。新的程序會全面覆寫舊程序,但不會建立程序。(會替換各種記憶體區域,但pid不變)。

execl("程式的路徑","執行指令","選項","參數",NULL)

隻有第一個參數是必須的,第二個參數必須存在但可以不正确,第三個第四個參數可以沒有,NULL代表參數結束了。

例如:execl("./b.out","b.out",NULL)

4.3 waitpid()和wait()

wait()和waitpid()可以讓父程序等待子程序的結束,并取得子程序的退出狀态和退出碼(return 後面的值或exit()中的值)。

wait()和waitpid()的差別在于wait()很固定,而waitpid()更靈活。wait()是等待任意一個子程序的結束後傳回,而waitpid()可以選擇等待的子程序,也可以不等待。wait()等待的子程序包括僵屍子程序,是以wait()也叫驗屍工。

pid_t wait(int* status)

參數是一個傳出參數,用來帶出結束子程序的退出碼和退出狀态;

傳回 有結束子程序就傳回它的PID,沒有結束就等待,父程序自己阻塞,如

果出了錯,就傳回-1.

宏函數WIFEXITED(status)可以判斷是否正常退出,WEXITSTATUS可以取退出

碼。

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

參數status和wait()一樣,pid可以設定等待哪個子程序,option可以設定是否等待。pid的值可能是:

==-1 等待任意子程序,與wait()同效

>0   等待指定的子程序(指定pid)

==0  等待本程序組任一子程序

<-1  等待程序組ID等于pid絕對值的任一子程序。

option的值:

0        -阻塞

WNOHANG 不阻塞,直接傳回0

傳回值,有子程序結束時傳回子程序的pid,出錯傳回-1,如果阻塞方式,沒有子程序結束繼續等待。如果是WNOHANG , 沒有子程序傳回0.

5、程序的退出

5.1 正常退出

1、在main函數中執行return語句

2、執行exit()函數

3、執行_Exit()或_exit()函數

4、最後一個線程退出

5、主線程退出

5.2 非正常退出

1、被信号打斷導緻退出

2、最後一個線程被取消

exit()、_Exit()和_exit()都是用來退出程序的,差別在于,_exit()和_Exit()是一樣的,差別在于頭檔案不同(UC/标C)

exit()和_Exit()差別在于退出方式不同,_Exit()是立即退出,exit()不是立即退出,還可以調用一些其他函數再退出。

可以使用atexit()函數注冊一些函數,這個函數再exit()之前被自動調用,return也會調用。_Exit()不調用。

二、信号

1、什麼是信号?

信号是Unix/Linux系統下最常見的一種軟體中斷方式。中斷就是讓程式停止目前正在運作的代碼,轉而執行其他代碼的過程。中斷分為軟體中斷和硬體中斷,軟體中斷就是用軟體的方式中斷代碼。

2、常見的信号

ctrl + C ->信号2

kill -9  ->信号9

段錯誤   ->信号11

總線錯誤 ->信号7(不确定,不同系統不一定)

kill指令用于發信号,格式:kill -信号 程序pid

信号0沒有實際的意義,用于測試是否有發信号的權限

Unix系統信号從1-48,Linux系統從1-64,但是不確定連續,而且規範中沒有規定信号的數量。信号都有一個宏名稱,以SIG開頭,比如:信号2叫SIGINT,本質是一個非負整數,檢視信号的指令:

kill -l

注:程式設計時,信号用宏名稱,因為有些系統信号數字不同,但宏名稱是一樣的。

常見宏名稱:

SIGINT -信号2  ctrl+C

SIGQUIT -信号3 ctrl+\

SIGKILL -信号9

在Linux系統中,1-31是不可靠信号,是早期的信号,不支援排隊,因為有可能丢失;34-64是可靠信号,支援排隊,不可能丢失。信号分為可靠信号和不可靠信号。

3 信号處理

信号隻是一個整數,實作中斷的功能靠信号處理,信号處理的方式有三種:

1、預設處理,系統提供,多半是退出程序

2、忽略信号,信号來了不做額外的處理,直接忽略

3、自定義處理函數,信号按程式員的代碼處理

注:

有些信号是不能被自定義和忽略的,比如信号9

程序可以給其他程序發信号,但隻能給本使用者的程序發信号,root使用者可以給所有使用者的程序發信号。

3.1 signal()/sigaction()

函數signal()/sigaction() 可以設定信号的處理方式。

signal()使用了一個函數指針,原型:

void (*f)(int)

函數指針 signal(int 信号值,函數指針)

其中,第二個參數函數指針可以是以下三個值:

1、SIG_IGN  -忽略該信号

2、SIG_DFL  -恢複預設處理

3、傳入一個程式員自定義函數名,自定義處理函數

傳回:成功傳回之前的處理方式,失敗傳回SIG_ERR

注:signal()函數隻是設定了信号處理方式,自身并沒有發信号,是以信号

處理函數在有信号到來時才執行。

3.2 父子程序之間的信号處理

如果父程序用fork()建立的子程序,子程序與父程序的信号處理方式完全一緻(照抄)。如果父程序用vfork()+execl()方式建立的子程序,父程序自定義處理函數的子程序改為預設(是以子程序已經沒了處理函數),其他照抄。

4 信号的發送方式

1、鍵盤發送(部分信号)

ctrl+C ->信号2

ctrl+\ ->信号3

2、硬體錯誤(部分信号)

段錯誤、總線錯誤、整數被0除

3、kill指令(所有指令都可以發送)

kill -信号 程序pid

4、系統函數發送(所有信号)

raise()、kill()、alarm()、sigqueue()。。。

重點是kill()函數,了解alarm()

int kill(pid_t pid,int signo)

參數pid指定了給哪個或哪些程序發信号,和waitpid()中的pid一樣。

signo就是發送信号幾

pid的值:

正數:就是給指定的一個程序發信号(pid)

-1  :就是給所有程序發信号(有權限的)

0   :本組所有的程序發信号

<-1 :指定程序組(組ID=-pid)的所有程序

最常用的是正數,其次-1。

指令killall a.out 可以殺死所有的加a.out的程序。

5、sleep()和usleep()

Sleep()是秒級的延時,usleep()是us級的延時。sleep()函數會讓程式休眠,直到休眠時間到,或者有未忽略的信号到來。會傳回剩餘的秒數。

6、信号集和信号屏蔽

6.1 信号集

系統定義了存放多個信号的資料結構,就是信号集,是一個超大的整數,每個二進制位代表一個信号。

信号集的運算結構(提供函數):

增加元素(信号) 

删除元素(信号)

查詢元素(信号)

sigaddset()  -增加一個信号(某個二進制位置一)

sigfillset() -所有信号全部增加(全部置一)

sigdelset()  -删除一個信号

sigemptyset()-删除所有信号

sigismember()-查詢一個信号是否存在

6.2 信号屏蔽

信号什麼時候來是無法确定和控制的,但可以讓信号處理的時間延後。信号屏蔽主要用于關鍵代碼的執行。不能阻止信号的到來,而是将信号的處理延後。關鍵代碼執行完畢後,要解除信号的屏蔽,信号得到處理。

函數sigprocmask() 可以屏蔽/解除屏蔽

int sigprocmask(int how,sigset_t* new,sigset_t* old)

參數how是信号屏蔽的方式

new是新的權限屏蔽字(屏蔽哪些權限)

old是傳出參數,用于傳出以前的信号屏蔽字,便于後面恢複

how的三種方式:

1、SIG_BLOCK:之前的屏蔽 +new的屏蔽

2、SIG_UNBLOCK:之前的屏蔽-new的屏蔽

3、SIG_SETMASK:直接使用新的屏蔽,與之前的無關,這個最常用

uc