20145215《資訊保安系統設計基礎》第十一週學習總結
教材學習內容總結
異常
- 異常是異常控制流的一種形式,它一部分是由硬體實現的,一部分是有作業系統實現的。
- 異常:控制流中的突變,用來響應處理器狀態中的某些變化。
- 在處理器中,狀態被編碼為不同的位和訊號。狀態變化成為事件。
- 異常表:當處理器監測到有時間發生時,通過一張叫做異常表的跳轉表,進行一個間接過程呼叫,到一個專門設計用來處理這類事件的作業系統子程式(異常處理程式)。
- 當異常處理程式完成處理後,根據引起異常的事件的型別,會發生以下三種情況的一種:
- 處理程式將控制返回給當前指令Icurr,即當事件發生時正在執行的指令。
- 處理程式將控制返回給Inext,即如果沒有發生異常將會執行的下一條指令。
- 處理程式終止被中斷的程式。
- 異常號:到異常表中的索引
- 異常表基址暫存器:異常表的起始地址存放的位置。
- 異常與過程呼叫的異同:
- 過程呼叫時,在跳轉到處理器之前,處理器將返回地址壓入棧中。然而,根據異常的型別,返回地址要麼是當前指令,要麼是下一條指令。
- 處理器把一些額外的處理器狀態壓入棧裡,在處理程式返回時,重新開始被中斷的程式會需要這些狀態。
- 如果控制從一個使用者程式轉移到核心,那麼所有這些專案都被壓到核心棧中,而不是壓到使用者棧中。
- 異常處理程式執行在核心模式下,意味著它們對所有的系統資源都有完全的訪問許可權。
異常的類別
- 異常的分類:中斷、陷阱、故障和終止。
- 中斷:非同步發生,是來自處理器外部的I/O裝置的訊號的結果。 硬體異常中斷處理程式通常稱為中斷處理程式。
- 陷阱和系統呼叫:
- 陷阱最重要的用途是在使用者程式和核心之間提供一個像過程一樣的介面,叫做系統呼叫。
- 普通的函式執行在使用者模式中,使用者模式限制了函式可以執行的指令的型別,而且它們只能訪問與呼叫函式相同的棧。系統呼叫執行在核心模式中,核心模式允許系統呼叫執行指令,並訪問定義在核心中的棧。
- 故障:是由錯誤情況引起的。
- 終止:是不可恢復的致命錯誤造成的結果,通常是一些硬體錯誤。終止處理程式從不將控制返回給應用程式。
- 0~31號:由intel架構師定義的異常;32~255號:作業系統定義的中斷和陷阱。
- 每一個系統呼叫都有一個唯一的整數號,對應於一個到核心中跳轉表的偏移量。
程式
- 異常是允許作業系統提供程式的概念所需要的基本構造塊。
- 程式:一個執行中的程式的例項。
- 上下文是由程式正確執行所需要的狀態組成的,這個狀態包括存放在儲存器中的程式的程式碼和資料,它的棧、通用目的暫存器的內容、程式計數器、環境變數以及開啟檔案描述符的集合。
- 程式提供給應用程式的關鍵抽象:
- 一個獨立的邏輯控制流,獨佔地使用處理器;
- 一個私有的地址空間,獨佔地使用儲存器系統。
- 併發流:一個邏輯流的執行在時間上與另一個流重疊。
- 併發:多個流併發地執行的一般現象。
- 多工:一個程式和其他程式輪流執行的概念。
- 時間片:一個程式執行它的控制流的一部分的每一時間段。
- 並行流:如果兩個流併發的執行在不同的處理器核或者計算機上
使用者模式和核心模式
- 模式位:用某個控制暫存器中的一個位模式,限制一個應用可以執行的指令以及它可以訪問的地址空間範圍。
- 當設定了位模式,程式就執行在核心模式中,一個執行在核心模式中的程式可以中興指令集中的任何指令,而且可以訪問系統中任何儲存器位置。
- 沒有設定位模式時,程式就執行在使用者模式中,不允許執行特權指令,例如停止處理器、改變位模式,或者發起一個I/O操作。
- 使用者程式必須通過系統呼叫介面間接的當問核心程式碼和資料。
- 程式從使用者模式變為核心模式的唯一方法是通過諸如中斷、故障、或者陷入系統呼叫這樣的異常。
上下文切換
- 上下文就是核心重新啟動一個被搶佔的程式所需的狀態。
- 排程:核心可以決定搶佔當前程式,並重新開始一個先前被搶佔的程式。有核心中稱為排程器的程式碼處理的。
- 上下文切換機制:
- 儲存當前程式的上下文
- 恢復某個先前被搶佔的程式被儲存的上下文
- 將控制傳遞給這個新恢復的程式
- 引起上下文切換的情況
- 當核心代表使用者執行系統呼叫時
- 中斷時
系統呼叫錯誤處理
- 錯誤處理包裝函式:包裝函式呼叫基本函式,檢查錯誤,如果有任何問題就終止。
程式控制
獲取程式ID
- 每個程式都有一個唯一的正數的程式ID。
- getpid函式返回撥用程式的PID,getppid函式返回它的父程式的PID。上面兩個函式返回一個同型別為pid_t的整數值,在linux系統中,它在types.h中被定義為int。
建立和終止程式
- 程式總處於三種狀態
- 執行:程式要麼在CPU上執行,要麼在等待被執行且最終會被核心排程。
- 停止:程式的執行被掛起,,且不會被排程。
- 終止:程式用永遠停止了。終止原因:(1)收到一個訊號,預設行為是終止程式;(2)從主程式返回(3)呼叫exit函式。
- 父程式通過呼叫fork函式建立一個新的執行的子程式。
- 子程式和父程式的異同:
- 異:有不同的PID
- 同:使用者級虛擬地址空間,包括:文字、資料和bss段、堆以及使用者棧。任何開啟檔案描述符,子程式可以讀寫父程式中開啟的任何檔案。
- fork函式: 因為父程式的PID總是非零的,返回值就提供一個明確的方法來分辨程式是在父程式還是在子程式中執行。
fork函式的特點:- 呼叫一次,返回兩次
- 併發執行
- 相同的但是獨立的地址空間
- 共享檔案
回收子程式
- 當父程式回收已終止的子程式時,核心將子程式的退出狀態傳遞給父程式,然後拋棄已終止的程式。
一個終止了但還未被回收的程式稱為僵死程式。 - 一個程式可以通過呼叫waitpid函式來等待它的子程式終止或者停止。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
//返回:若成功,返回子程式的PID;若WNOHANG,返回0;若其他錯誤,返回-1
- 修改預設行為,通過options設定:
- WNOHANG:預設行為是掛起呼叫程式。
- WUNTRACED:預設行為是隻返回已終止的子程式。
- WNOHANG|WUNTRACED:立即返回,如果等待集合中沒有任何子程式被停止或者已終止,那麼返回值為0,或者返回值等於那個被停止或者已經終止的子程式的PID。
- 錯誤條件:
- 若呼叫程式沒有子程式,那麼waitpid返回-1,並且設定errno為ECHILD;
- 若waitpid函式被一個訊號中斷,那麼返回-1,並設定errno為EINTR
訊號
訊號術語
- 傳送訊號的兩個不同步驟:
- 傳送訊號:核心通過更新目的程式上下文中的某個狀態,傳送(遞送)一個訊號給目的程式。
- 接收訊號:訊號處理程式捕獲訊號的基本思想。
- 傳送訊號的兩個原因:
- 核心監測到一個系統事件,比如被零除錯誤或者子程式終止。
- 一個程式呼叫了kill函式,顯式地要求核心傳送一個訊號給目的程式。一個程式可以傳送訊號給它自己。
- 待處理訊號:一個只發出而沒有被接收的訊號
- 一個程式可以有選擇性地阻塞接收某種訊號。
待處理訊號不會被接收,直到程式取消對這種訊號的阻塞。 - 一個待處理訊號最多隻能被接受一次,pending位向量:維護著待處理訊號集合,blocked向量:維護著被阻塞的訊號集合。
傳送訊號
- 程式組:每個程式都只屬於一個程式組,程式組是由一個正整數程式組ID來標識的。getpgrp函式返回當前程式的程式組ID:預設地,一個子程式和它的父程式同屬於一個程式組。
- 用/bin/kill/程式傳送訊號:一個為負的PID會導致訊號被髮送到程式組PID中的每個程式。
- 從鍵盤傳送訊號:作業:表示對一個命令列求值而建立的程式。外殼為每個作業建立一個獨立的程式組。
- 用kill函式傳送訊號: 程式通過呼叫kill函式傳送訊號給其他的程式。父程式用kill函式傳送SIGKILL訊號給它的子程式。
- 用alarm函式傳送訊號:在任何情況下,對alarm的呼叫都將取消任何待處理的鬧鐘,並且返回任何待處理的鬧鐘在被髮送前還剩下的秒數。
接收訊號
- 當核心從一個異常處理程式返回,準備將控制傳遞該程式p時,它會檢查程式p的未被阻塞的待處理訊號的集合。如果這個集合是非空的,那麼核心選擇集合中的某個訊號k,並且強制p接收訊號k。
- 程式可以通過使用signal函式修改和訊號相關聯的預設行為。 唯一例外是SIGSTOP和SIGKILL,它們的預設行為是不能被修改的。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
//返回:若成功,返回指向前次處理程式的指標;若出錯,為SIG_ERR
- signal函式改變和訊號signum相關聯的行為的三種方法:
- handler是SIG_ IGN,忽略型別為signum的訊號;
- handler是SIG_ DFL,型別為signum的訊號行為恢復為預設行為。
- 否則,handler就是使用者定義的函式地址。這個函式稱為訊號處理程式。
- 設定訊號處理程式:通過把處理程式的地址傳遞到signal函式從而改變預設行為。
- 捕獲訊號:呼叫訊號處理程式。
- 處理訊號:執行訊號處理程式。
- 因為訊號處理程式的邏輯控制流與主函式的邏輯控制流重疊,訊號處理程式和主函式併發執行。
訊號處理問題
- 待處理訊號被阻塞:
- 待處理訊號不會排隊等待;
- 系統呼叫可以被中斷:像read、wait、accept這樣的系統呼叫潛在地會阻塞程式一段較長的時間,稱為慢速系統呼叫。
注意:不可以用訊號來對其他程式中發生的事件計數。
實踐部分
exec1.c
程式程式碼
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;//NULL
printf("* * * About to exec ls -l\n");
execvp( "ls" , arglist );//第一個引數傳遞的是檔名
printf("* * * ls is done. bye");
return 0;
}
執行結果
分析過程
- execvp()會從PATH 環境變數所指的目錄中查詢符合引數file 的檔名,找到後便執行該檔案,然後將第二個引數argv傳給該欲執行的檔案。
- 如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno中。
- 在執行時exevp函式呼叫成功沒有返回,所以沒有列印“* * * ls is done. bye”
exec2.c
程式程式碼
#include <stdio.h>
#include <unistd.h>
int main(){
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
execvp( arglist[0] , arglist );
printf("* * * ls is done. bye\n");
}
分析過程
- exec2把“ls”替換成了“arglist[0]”,所以並不會影響結果。
exec3.c
程式程式碼
#include <stdio.h>
#include <unistd.h>
int main(){
char *arglist[3];
char*myenv[3];
myenv[0] = "PATH=:/bin:";
myenv[1] = NULL;
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye\n");
}
分析過程
檢視幫助文件:
- int execlp(const char * file,const char * arg,....);
- execlp()會從PATH 環境變數所指的目錄中查詢符合引數file的檔名,找到後便執行該檔案,然後將第二個以後的引數當做該檔案的argv[0]、argv[1]……,最後一個引數必須用空指標(NULL)作結束。
指定了環境變數,然後依然執行了ls -l指令,成功後沒有返回,所以最後一句話不會輸出。執行結果同exec1。
exec函式族
- fork()函式通過系統呼叫建立一個與原來程式(父程式)幾乎完全相同的程式,在fork後的子程式中使用exec函式族,可以裝入和執行其它程式(子程式替換原有程式,和父程式做不同的事),fork建立一個新的程式就產生了一個新的PID,exec啟動一個新程式,替換原有的程式,因此這個新的被exec執行的程式的PID不會改變。
- exec函式族裝入並執行程式path/file,並將引數arg0(arg1, arg2, argv[], envp[])傳遞給子程式,出錯返回-1。
- 在exec函式族中,字尾l、v、p、e指定函式將具有某種操作能力:
字尾 | 操作能力 |
---|---|
l | 希望接收以逗號分隔的引數列表,列表以NULL指標作為結束標誌 |
v | 希望接收到一個以NULL結尾的字串陣列的指標 |
p | 是一個以NULL結尾的字串陣列指標,函式可以DOS的PATH變數查詢子程式檔案 |
e | 函式傳遞指定引數envp,允許改變子程式的環境,無字尾e時,子程式使用當前程式的環境 |
forkdemo1.c
程式程式碼
#include <stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
int ret_from_fork, mypid;
mypid = getpid();
printf("Before: my pid is %d\n", mypid);
ret_from_fork = fork();
sleep(1);
printf("After: my pid is %d, fork() said %d\n",
getpid(), ret_from_fork);
return 0;
}
執行結果
分析過程
- 這個程式碼先是列印程式pid,然後呼叫fork函式生成子程式,休眠一秒後再次列印程式id,這時父程式列印子程式pid,子程式返回0。
- 父程式通過呼叫fork函式建立一個新的執行子程式。
- 呼叫一次,返回兩次。一次返回到父程式,一次返回到新建立的子程式。
forkdemo2.c
程式程式碼
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before:my pid is %d\n", getpid() );
fork();
fork();
printf("aftre:my pid is %d\n", getpid() );
return 0;
}
執行結果
分析過程
- 這個程式碼呼叫兩次fork,一共產生四個子程式,所以會列印四個aftre輸出。
forkdemo3.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int fork_rv;
int main()
{
printf("Before: my pid is %d\n", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the parent. my child is %d\n", getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d\n", fork_rv);
exit(0);
}
return 0;
}
執行結果
分析過程
- fork函式會將一個程式分成兩個程式,並且會返回兩次,所以如上圖所示,我們可以看到,出現了一次“I am the parent. my child is 4954”,又出現了一次“I am the parent. my child is 4954”。
- 這個程式碼進行了錯誤處理,提高了程式碼的健壯性。
forkdemo4.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int fork_rv;
printf("Before: my pid is %d\n", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the child. my pid=%d\n", getpid());
printf("parent pid= %d, my pid=%d\n", getppid(), getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d\n", fork_rv);
sleep(10);
exit(0);
}
return 0;
}
執行結果
分析過程
- 先列印程式pid,然後fork建立子程式,父程式返回子程式pid,所以輸出parent一句,執行sleep(10)語句,休眠十秒。
- 子程式返回0,所以輸出child與之後一句。
forkgdb.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int gi=0;
int main()
{
int li=0;
static int si=0;
int i=0;
pid_t pid = fork();
if(pid == -1){
exit(-1);
}
else if(pid == 0){
for(i=0; i<5; i++){
printf("child li:%d\n", li++);
sleep(1);
printf("child gi:%d\n", gi++);
printf("child si:%d\n", si++);
}
exit(0);
}
else{
for(i=0; i<5; i++){
printf("parent li:%d\n", li++);
printf("parent gi:%d\n", gi++);
sleep(1);
printf("parent si:%d\n", si++);
}
exit(0);
}
return 0;
}
執行結果
分析過程
- 父程式列印是先列印兩句,然後休眠一秒,然後列印一句,子程式先列印一句,然後休眠一秒,然後列印兩句。並且這兩個執行緒是併發的,所以可以看到在一個執行緒休眠的那一秒,另一個執行緒在執行,並且執行緒之間相互獨立互不干擾。
psh1.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 20
#define ARGLEN 100
int execute( char *arglist[] )
{
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
}
char * makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
int main()
{
char *arglist[MAXARGS+1];
int numargs;
char argbuf[ARGLEN];
numargs = 0;
while ( numargs < MAXARGS )
{
printf("Arg[%d]? ", numargs);
if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
執行結果
分析過程
- 依次你輸入要執行的指令與引數,回車表示輸入結束,然後輸入的每個引數對應到函式中,再呼叫對應的指令。
- 第一個是程式名,然後依次是程式引數。
- 一個字串,一個字串構造引數列表argist,最後在陣列末尾加上NULL
- 將arglist[0]和arglist陣列傳給execvp。
- 程式正常執行,execvp命令指定的程式程式碼覆蓋了shell程式程式碼,並在命令結束之後退出,shell就不能再接受新的命令。
psh2.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#define MAXARGS 20
#define ARGLEN 100
char *makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
void execute( char *arglist[] )
{
int pid,exitstatus;
pid = fork();
switch( pid ){
case -1:
perror("fork failed");
exit(1);
case 0:
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
default:
while( wait(&exitstatus) != pid )
;
printf("child exited with status %d,%d\n",
exitstatus>>8, exitstatus&0377);
}
}
int main()
{
char *arglist[MAXARGS+1];
int numargs;
char argbuf[ARGLEN];
numargs = 0;
while ( numargs < MAXARGS )
{
printf("Arg[%d]? ", numargs);
if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
執行結果
分析過程
- 功能:在子程式中執行使用者輸入的指令,利用wait函式,通過父程式,實現迴圈輸入指令。
- 這個程式碼與psh1.c程式碼最大的區別就在於execute函式。 呼叫wait(&status)等價於呼叫waitpid(-1.&status,0),當option=0時,waitpid掛起呼叫程式的執行,直到它的等待集合中的一個子程式終止。只要有一個子程式沒有結束,父程式就被掛起。所以當wait返回pid時沒說明,子程式都已經結束,即使用者輸入的指令都已經執行完畢。因為execute函式在大的迴圈中呼叫,所以會迴圈執行下去,除非使用者強制退出。
- 另外,當子程式正常執行完使用者指令後,子程式的狀態為0,若執行指令出錯,子程式的狀態為1。
testbuf1.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello");
fflush(stdout);
while(1);
}
執行結果
分析過程
- 效果是先輸出hello,然後保持在迴圈中不結束程式。
testbuf2.c
程式程式碼
#include <stdio.h>
int main()
{
printf("hello\n");
while(1);
}
執行結果
和testbuf1.c執行結果一樣
分析過程
- 從testbuf1.c和testbuf2.c的執行結果上看,我們可以猜出fflush(stdout);的功能就是列印換行符。
testbuf3.c
程式程式碼
#include <stdio.h>
int main()
{
fprintf(stdout, "1234", 5);
fprintf(stderr, "abcd", 4);
}
執行結果
分析過程
- 將內容格式化輸出到標準錯誤、輸出流中。
- 在預設情況下,stdout是行緩衝的,他的輸出會放在一個buffer裡面,只有到換行的時候,才會輸出到螢幕。而stderr是無緩衝的,會直接輸出,因此stderr中的值會先顯示出來。
testpid.c
程式程式碼
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("my pid: %d \n", getpid());
printf("my parent's pid: %d \n", getppid());
return 0;
}
執行結果
分析過程
- 功能:輸出當前程式pid和當前程式的父程式的pid。
testpp.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
int main()
{
char **pp;
pp[0] = malloc(20);
return 0;
}
執行結果
testsystem.c
程式程式碼
#include <stdlib.h>
int main ( int argc, char *argv[] )
{
system(argv[1]);
system(argv[2]);
return EXIT_SUCCESS;
}
執行結果
分析過程
- system函式:發出一個DOS命令
- 用法: int system(char *command);
- system函式需加標頭檔案<stdlib.h>後方可呼叫,最終就是執行使用者輸入的指令。
waitdemo1.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define DELAY 4
void child_code(int delay)
{
printf("child %d here. will sleep for %d seconds\n", getpid(), delay);
sleep(delay);
printf("child done. about to exit\n");
exit(17);
}
void parent_code(int childpid)
{
int wait_rv=0; /* return value from wait() */
wait_rv = wait(NULL);
printf("done waiting for %d. Wait returned: %d\n",
childpid, wait_rv);
}
int main()
{
int newpid;
printf("before: mypid is %d\n", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
return 0;
}
執行結果
分析過程
- 如果有子程式,則終止子程式,成功返回子程式pid。
waitdemo2.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define DELAY 10
void child_code(int delay)
{
printf("child %d here. will sleep for %d seconds\n", getpid(), delay);
sleep(delay);
printf("child done. about to exit\n");
exit(27);
}
void parent_code(int childpid)
{
int wait_rv;
int child_status;
int high_8, low_7, bit_7;
wait_rv = wait(&child_status);
printf("done waiting for %d. Wait returned: %d\n", childpid, wait_rv);
high_8 = child_status >> 8; /* 1111 1111 0000 0000 */
low_7 = child_status & 0x7F; /* 0000 0000 0111 1111 */
bit_7 = child_status & 0x80; /* 0000 0000 1000 0000 */
printf("status: exit=%d, sig=%d, core=%d\n", high_8, low_7, bit_7);
}
int main()
{
int newpid;
printf("before: mypid is %d\n", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
}
執行結果
分析過程
- 多了一個子程式的狀態區分,把狀態拆分成三塊,exit,sig和core。
environ.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("PATH=%s\n", getenv("PATH"));
setenv("PATH", "hello", 1);
printf("PATH=%s\n", getenv("PATH"));
#if 0
printf("PATH=%s\n", getenv("PATH"));
setenv("PATH", "hellohello", 0);
printf("PATH=%s\n", getenv("PATH"));
printf("MY_VER=%s\n", getenv("MY_VER"));
setenv("MY_VER", "1.1", 0);
printf("MY_VER=%s\n", getenv("MY_VER"));
#endif
return 0;
}
執行結果
分析過程
- 功能:列印設定環境變數的值。
- 如圖所示:先列印了一開始的初始環境變數,接著重新設定環境變數,並列印輸出。
environvar.c
程式程式碼
#include <stdio.h>
int main(void)
{
extern char **environ;
int i;
for(i = 0; environ[i] != NULL; i++)
printf("%s\n", environ[i]);
return 0;
}
執行結果
分析過程
- 功能:將外部變數environ的內容列印出來,也就是把系統相關巨集值,列印出來。
consumer.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
int main()
{
int pipe_fd;
int res;
int open_mode = O_RDONLY;
char buffer[BUFFER_SIZE + 1];
int bytes = 0;
memset(buffer, 0, sizeof(buffer));
printf("Process %d opeining FIFO O_RDONLY \n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);//判斷開啟檔案是否成功
printf("Process %d result %d\n", getpid(), pipe_fd);
if (pipe_fd != -1) {
do {
res = read(pipe_fd, buffer, BUFFER_SIZE);
bytes += res;
} while (res > 0);
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d bytes read\n", getpid(), bytes);
exit(EXIT_SUCCESS);
}
執行結果
分析過程
- 功能:判斷是否開啟檔案流,並判斷是否正常開啟檔案。
- 輸出開啟檔案流的程式號,以及開啟檔案程式號,並返回開啟檔案的結果。並且可以輸入訊息。
producer.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)
int main()
{
int pipe_fd;
int res;
int open_mode = O_WRONLY;
int bytes = 0;
char buffer[BUFFER_SIZE + 1];
if (access(FIFO_NAME, F_OK) == -1) {
res = mkfifo(FIFO_NAME, 0777);
if (res != 0) {
fprintf(stderr, "Could not create fifo %s \n",
FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), pipe_fd);//該句無法被列印
if (pipe_fd != -1) {
while (bytes < TEN_MEG) {
res = write(pipe_fd, buffer, BUFFER_SIZE);
if (res == -1) {
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
bytes += res;
}
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finish\n", getpid());
exit(EXIT_SUCCESS);
}
執行結果
listargs.c
程式程式碼
#include <stdio.h>
main( int ac, char *av[] )
{
int i;
printf("Number of args: %d, Args are:\n", ac);
for(i=0;i<ac;i++)
printf("args[%d] %s\n", i, av[i]);
fprintf(stderr,"This message is sent to stderr.\n");
}
執行結果
分析過程
- 列印使用者輸入的指令,輸出相關資訊。
pipe.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define oops(m,x) { perror(m); exit(x); }
int main(int ac, char **av)
{
int thepipe[2],
newfd,
pid;
if ( ac != 3 ){
fprintf(stderr, "usage: pipe cmd1 cmd2\n");
exit(1);
}
if ( pipe( thepipe ) == -1 )
oops("Cannot get a pipe", 1);
if ( (pid = fork()) == -1 )
oops("Cannot fork", 2);
if ( pid > 0 ){
close(thepipe[1]);
if ( dup2(thepipe[0], 0) == -1 )
oops("could not redirect stdin",3);
close(thepipe[0]);
execlp( av[2], av[2], NULL);
oops(av[2], 4);
}
close(thepipe[0]);
if ( dup2(thepipe[1], 1) == -1 )
oops("could not redirect stdout", 4);
close(thepipe[1]);
execlp( av[1], av[1], NULL);
oops(av[1], 5);
}
執行結果
分析過程
- 功能:相當於管道命令
stdinredir1.c
程式程式碼
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd ;
char line[100];
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
close(0);
fd = open("/etc/passwd", O_RDONLY);
if ( fd != 0 ){
fprintf(stderr,"Could not open data as fd 0\n");
exit(1);
}
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
執行結果
分析過程
- 先從標準輸入輸入3行資訊,接著分別列印這三行資訊,執行開啟檔案語句,若開啟正常,則從檔案中讀取前三行資訊。
stdinredir2.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
//#define CLOSE_DUP
//#define USE_DUP2
main()
{
int fd ;
int newfd;
char line[100];
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fd = open("data", O_RDONLY);
#ifdef CLOSE_DUP
close(0);
newfd = dup(fd);
#else
newfd = dup2(fd,0);
#endif
if ( newfd != 0 ){
fprintf(stderr,"Could not duplicate fd to 0\n");
exit(1);
}
close(fd);
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
執行結果
分析過程
- 上圖結果表示開啟檔案失敗的情況。
testtty.c
程式程式碼
#include <unistd.h>
int main()
{
char *buf = "abcde\n";
write(0, buf, 6);
}
執行結果
分析過程
- 將緩衝區中的內容列印出來。
whotofile.c
程式程式碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int pid ;
int fd;
printf("About to run who into a file\n");
if( (pid = fork() ) == -1 ){
perror("fork"); exit(1);
}
if ( pid == 0 ){
close(1); /* close, */
fd = creat( "userlist", 0644 ); /* then open */
execlp( "who", "who", NULL ); /* and run */
perror("execlp");
exit(1);
}
if ( pid != 0 ){
wait(NULL);
printf("Done running who. results in userlist\n");
}
return 0;
}
執行結果
分析過程
- 定義函式:int close(int fd);
- 函式說明:當使用完檔案後若已不再需要則可使用 close()關閉該檔案, 二close()會讓資料寫回磁碟, 並釋放該檔案所佔用的資源. 引數fd 為先前由open()或creat()所返回的檔案描述詞.
- 返回值:若檔案順利關閉則返回0, 發生錯誤時返回-1.
- 從結果中我們可以看出,沒有子程式的執行,我們知道fork函式會產生2個返回,所以子程式是一定有的,close(1)關閉了子程式的標準輸出,所以之後的執行都無法列印出來。
sigactdemo.c
程式程式碼
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#define INPUTLEN 100
void inthandler();
int main()
{
struct sigaction newhandler;
sigset_t blocked;
char x[INPUTLEN];
newhandler.sa_handler = inthandler;
newhandler.sa_flags = SA_RESTART|SA_NODEFER
|SA_RESETHAND;
sigemptyset(&blocked);
sigaddset(&blocked, SIGQUIT);
newhandler.sa_mask = blocked;
if (sigaction(SIGINT, &newhandler, NULL) == -1)
perror("sigaction");
else
while (1) {
fgets(x, INPUTLEN, stdin);
printf("input: %s", x);
}
return 0;
}
void inthandler(int s)
{
printf("Called with signal %d\n", s);
sleep(s * 4);
printf("done handling signal %d\n", s);
}
執行結果
分析過程
- 引數結構sigaction定義如下
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
- flag
- SA_RESETHAND:當呼叫訊號處理函式時,將訊號的處理函式重置為預設值SIG_DFL
- SA_RESTART:如果訊號中斷了程式的某個系統呼叫,則系統自動啟動該系統呼叫
- SA_NODEFER :一般情況下, 當訊號處理函式執行時,核心將阻塞該給定訊號。但是如果設定SA_NODEFER標記, 那麼在該訊號處理函式執行時,核心將不會阻塞該訊號
- 函式sigaction
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
- sigaction()會依引數signum指定的訊號編號來設定該訊號的處理函式。引數signum可以指定SIGKILL和SIGSTOP以外的所有訊號。
sigactdemo2.c
程式程式碼
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void sig_alrm( int signo )
{
/*do nothing*/
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset( &newact.sa_mask );
newact.sa_flags = 0;
sigaction( SIGALRM, &newact, &oldact );
alarm( nsecs );
pause();
unslept = alarm ( 0 );
sigaction( SIGALRM, &oldact, NULL );
return unslept;
}
int main( void )
{
while( 1 )
{
mysleep( 2 );
printf( "Two seconds passed\n" );
}
return 0;
}
執行結果
分析過程
- 每兩秒輸出一次
sigdemo1.c
程式程式碼
#include <stdio.h>
#include <signal.h>
void f(int);
int main()
{
int i;
signal( SIGINT, f );
for(i=0; i<5; i++ ){
printf("hello\n");
sleep(2);
}
return 0;
}
void f(int signum)
{
printf("OUCH!\n");
}
執行結果
分析過程
- 連續輸出五個hello,每兩個間隔是兩秒
- 在這期間,每次輸入的Ctrl+C都被處理成列印OUCH
sigdemo2.c
程式程式碼
#include <stdio.h>
#include <signal.h>
main()
{
signal( SIGINT, SIG_IGN );
printf("you can't stop me!\n");
while( 1 )
{
sleep(1);
printf("haha\n");
}
}
執行結果
分析過程
- 一直輸出haha,按Ctrl+C不能停止,必須按Ctrl+Z。
- SIG_DFL,SIG_IGN 分別表示無返回值的函式指標,指標值分別是0和1,這兩個指標值邏輯上講是實際程式中不可能出現的函式地址值。
- SIG_DFL:預設訊號處理程式
- SIG_IGN:忽略訊號的處理程式
sigdemo3.c
程式程式碼
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#define INPUTLEN 100
int main(int argc, char *argv[])
{
void inthandler(int);
void quithandler(int);
char input[INPUTLEN];
int nchars;
signal(SIGINT, inthandler);//^C
signal(SIGQUIT, quithandler);//^\
do {
printf("\nType a message\n");
nchars = read(0, input, (INPUTLEN - 1));
if (nchars == -1)
perror("read returned an error");
else {
input[nchars] = '\0';
printf("You typed: %s", input);
}
}
while (strncmp(input, "quit", 4) != 0);
return 0;
}
void inthandler(int s)
{
printf(" Received signal %d .. waiting\n", s);
sleep(2);
printf(" Leaving inthandler \n");
}
void quithandler(int s)
{
printf(" Received signal %d .. waiting\n", s);
sleep(3);
printf(" Leaving quithandler \n");
}
執行結果
分析過程
- 從上圖看出,我們從標準輸入輸入訊息,在標準輸出上列印出來。
遇到的問題及解決過程
問題
- 在編譯執行testpp.c程式碼時出現錯誤:
解決
- 我覺得應該是pp變數中儲存的是一個無效隨機不可用的地址,誰也不知道它指向哪裡,因此用
char **pp
時,要給它分配一個記憶體地址。 - 對程式碼進行如下修改:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char **pp;
pp = (char **) malloc(sizeof(char**));
pp[0] = malloc(20);
return 0;
}
- 這時再編譯執行結果就沒有問題了。
本週程式碼託管截圖
- 程式碼託管連結:click here
- 程式碼行數統計:
心得體會
- 本週在學習了異常控制流的基礎上,繼續學習了系統呼叫,由此可見系統呼叫的重要性。
- 此外,本週學習的內容有點多,想要深入理解,還需要更多的時間去消化。
學習進度條
程式碼行數(新增/累積) | 部落格量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 | |
第一週 | 0/0 | 1/2 | 25/45 | 學習了幾個Linux核心命令 |
第二週 | 55/55 | 2/4 | 27/72 | 學會了vim,gcc以及gdb的基本操作 |
第三週 | 148/203 | 1/5 | 23/95 | 對資訊的表示和處理有更深入的理解 |
第五週 | 72/275 | 1/6 | 25/120 | 對組合語言有了更深的理解 |
第六週 | 56/331 | 2/8 | 30/150 | 安裝了Y86模擬器 |
第七週 | 61/392 | 1/9 | 22/172 | 理解了區域性性原理和快取思想在儲存層次結構中的應用 |
第八週 | 0/392 | 1/10 | 20/192 | 複習前幾章內容 |
第九周 | 132/524 | 2/12 | 24/216 | 瞭解了Linux作業系統提供的基本I/O服務 |
第十週 | 420/524 | 2/14 | 20/236 | 對常用指令的程式碼進行了分析除錯,加深了理解 |
第十一週 | 1017/1541 | 2/16 | 26/262 | 對系統呼叫有了更深的認識 |