20145302張薇 《資訊保安系統設計基礎》第11周學習總結
學習內容總結
- 本週主要學習《深入理解計算機系統》第八章 異常控制流實踐的內容
8.1異常
1、異常是異常控制流的一種形式,它一部分是由硬體實現的,一部分是有作業系統實現的。
2、異常:控制流中的突變,用來響應處理器狀態中的某些變化。
3、在處理器中,狀態被編碼為不同的位和訊號。狀態變化成為事件。
4、異常表:當處理器監測到有時間發生時,通過一張叫做異常表的跳轉表,進行一個間接過程呼叫,到一個專門設計用來處理這類事件的作業系統子程式(異常處理程式)。
5、當異常處理程式完成處理後,根據引起異常的事件的型別,會發生以下三種情況的一種:
(1)處理程式將控制返回給當前指令Icurr,即當事件發生時正在執行的指令。
(2)處理程式將控制返回給Inext,即如果沒有發生異常將會執行的下一條指令。
(3)處理程式終止被中斷的程式。
8.1.1 異常處理
1、系統中可能的每種型別的異常都分配了一個唯一的非負整數的異常號。
異常號的分配: (1)處理器的設計者:被除零、缺頁、儲存器訪問違例、斷點以及算數溢位。
(2)作業系統核心的設計者分配的:系統呼叫和來自意外不I/O裝置的訊號。
2、異常號:到異常表中的索引
異常表基址暫存器:異常表的起始地址存放的位置。
3、異常與過程呼叫的異同:
(1)過程呼叫時,在跳轉到處理器之前,處理器將返回地址壓入棧中。然而,根據異常的型別,返回地址要麼是當前指令,要麼是下一條指令。
(2)處理器把一些額外的處理器狀態壓入棧裡,在處理程式返回時,重新開始被中斷的程式會需要這些狀態。
(3)如果控制從一個使用者程式轉移到核心,那麼所有這些專案都被壓到核心棧中,而不是壓到使用者棧中。
(4)異常處理程式執行在核心模式下,意味著它們對所有的系統資源都有完全的訪問許可權。
8.1.2 異常的類別
1、異常的分類:中斷、陷阱、故障和終止。
2、中斷:非同步發生,是來自處理器外部的I/O裝置的訊號的結果。
(1)硬體異常中斷處理程式通常稱為中斷處理程式。
(2)非同步異常是有處理器外部的I/O裝置中的時間產生的,同步異常是執行一條指令的直接產物。
(3)陷阱、故障、終止時同步發生的,是執行當前指令的結果,我們把這類指令叫做故障指令。
3、陷阱和系統呼叫
(1)陷阱最重要的用途是在使用者程式和核心之間提供一個像過程一樣的介面,叫做系統呼叫。
(2)普通的函式執行在使用者模式中,使用者模式限制了函式可以執行的指令的型別,而且它們只能訪問與呼叫函式相同的棧。系統呼叫執行在核心模式中,核心模式允許系統呼叫執行指令,並訪問定義在核心中的棧。
4、故障:是由錯誤情況引起的。
例如:abort例程會終止引起故障的應用程式。 根據故障是否能夠被修復,故障處理程式要麼重新執行引起故障的指令,要麼終止。例如:缺頁故障。
5、終止:是不可恢復的致命錯誤造成的結果,通常是一些硬體錯誤。終止處理程式從不將控制返回給應用程式。
8.1.3 linux/IA32系統中的異常
1、0~31號:由intel架構師定義的異常。32~255號:作業系統定義的中斷和陷阱。
2、linux/IA32故障和終止:
(1)除法錯誤(linux中稱為浮點異常)
(2)一般保護故障(linux中稱為段故障)
(3)缺頁
(4)機器檢查
3、linux/IA32系統呼叫
- 每一個系統呼叫都有一個唯一的整數號,對應於一個到核心中跳轉表的偏移量。
- C程式用syscall函式可以直接呼叫任何系統呼叫。
- 系統級函式:系統呼叫和它們相關聯的包裝函式。
- linux系統呼叫的引數都是通過吉春器而不是棧傳遞的,暫存器%eax包含系統呼叫號,棧指標%esp不能使用,因為當進入核心呼叫時,核心會覆蓋它。
8.2 程式
1、異常是允許作業系統提供程式的概念所需要的基本構造塊。
- 程式:一個執行中的程式的例項。
- 上下文是由程式正確執行所需要的狀態組成的,這個狀態包括存放在儲存器中的程式的程式碼和資料,它的棧、通用目的暫存器的內容、程式計數器、環境變數以及開啟檔案描述符的集合。
2、程式提供給應用程式的關鍵抽象: - 一個獨立的邏輯控制流,獨佔地使用處理器;
- 一個私有的地址空間,獨佔地使用儲存器系統。
8.2.1 邏輯控制流
- 程式計數器:唯一的對應於包含在程式的可執行目標檔案中的指令,或者是包含在執行時動態連結到程式的共享物件中的指令。
- 這個PC值的序列叫做邏輯控制流,簡稱邏輯流。
8.2.2 並非流
1、- 併發流:一個邏輯流的執行在時間上與另一個流重疊。
- 併發:多個流併發地執行的一般現象。
- 多工:一個程式和其他程式輪流執行的概念。
- 時間片:一個程式執行它的控制流的一部分的每一時間段。
多工也叫時間分片。
2、並行流:如果兩個流併發的執行在不同的處理器核或者計算機上
8.2.3 私有地址空間
- 和這個空間中某個地址相關聯的那個儲存器位元組是不能被其他程式讀或者寫的。所以這個空間地址是私有的。
8.2.4 使用者模式和核心模式
1、模式位:用某個控制暫存器中的一個位模式,限制一個應用可以執行的指令以及它可以訪問的地址空間範圍。
2、當設定了位模式,程式就執行在核心模式中,一個執行在核心模式中的程式可以中興指令集中的任何指令,而且可以訪問系統中任何儲存器位置。
3、沒有設定位模式時,程式就執行在使用者模式中,不允許執行特權指令,例如停止處理器、改變位模式,或者發起一個I/O操作。
4、使用者程式必須通過系統呼叫介面間接的當問核心程式碼和資料。
5、程式從使用者模式變為核心模式的唯一方法是通過諸如中斷、故障、或者陷入系統呼叫這樣的異常。
上下文切換
1、上下文就是核心重新啟動一個被搶佔的程式所需的狀態。
2、排程:核心可以決定搶佔當前程式,並重新開始一個先前被搶佔的程式。有核心中稱為排程器的程式碼處理的。
3、上下文切換機制:
(1)儲存當前程式的上下文
(2)恢復某個先前被搶佔的程式被儲存的上下文
(3)將控制傳遞給這個新恢復的程式
4、引起上下文切換的情況
(1)當核心代表使用者執行系統呼叫時
(2)中斷時
8.3 系統呼叫錯誤處理
- 錯誤處理包裝函式:包裝函式呼叫基本函式,檢查錯誤,如果有任何問題就終止。
8.4.1 獲取程式ID
1、每個程式都有一個唯一的正數的程式ID。
2、getpid函式返回撥用程式的PID,getppid函式返回它的父程式的PID。上面兩個函式返回一個同型別為pid_t的整數值,在linux系統中,它在types.h中被定義為int。
8.4.2 建立和終止程式
1、程式總處於三種狀態
(1)執行:程式要麼在CPU上執行,要麼在等待被執行且最終會被核心排程。
(2)停止:程式的執行被掛起,,且不會被排程。
(3)終止:程式用永遠停止了。終止原因:
- 收到一個訊號,預設行為是終止程式;
- 從主程式返回
- 呼叫exit函式。
2、父程式通過呼叫fork函式建立一個新的執行的子程式。
3、子程式和父程式的異同:
異:有不同的PID
同:使用者級虛擬地址空間,包括:文字、資料和bss段、堆以及使用者棧。任何開啟檔案描述符,子程式可以讀寫父程式中開啟的任何檔案。
4、fork函式: 因為父程式的PID總是非零的,返回值就提供一個明確的方法來分辨程式是在父程式還是在子程式中執行。
fork函式的特點:
(1)呼叫一次,返回兩次
(2)併發執行
(3)相同的但是獨立的地址空間
(4)共享檔案
8.4.3 回收子程式
1、當父程式回收已終止的子程式時,核心將子程式的退出狀態傳遞給父程式,然後拋棄已終止的程式。
一個終止了但還未被回收的程式稱為僵死程式。
2、一個程式可以通過呼叫waitpid函式來等待它的子程式終止或者停止。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
//返回:若成功,返回子程式的PID;若WNOHANG,返回0;若其他錯誤,返回-1。
- 預設地,當option=0時,waitpid掛起呼叫程式的執行,直到它的等待集合中的一個子程式終止。
3、判定等待集合的成員
有引數pid來確定的:
(1)pid>0:等待集合是一個單獨的子程式,程式ID等於pid。
(2)pid=-1:等待結合就是由父程式所有的子程式組成的。
4、修改預設行為 - 通過options設定:
(1)WNOHANG:預設行為是掛起呼叫程式。
(2)WUNTRACED:預設行為是隻返回已終止的子程式。
(3)WNOHANG|WUNTRACED:立即返回,如果等待集合中沒有任何子程式被停止或者已終止,那麼返回值為0,或者返回值等於那個被停止或者已經終止的子程式的PID。
5、檢查已回收子程式的退出狀態
wait.h標頭檔案定義瞭解釋status引數的幾個巨集:
(1)WIFEXITED:如果子程式通過呼叫exit或者一個返回正常終止,就返回真;
(2)WEXITSTATUS:返回一個正常終止的子程式的退出狀態。只有在WIFEXITED返回真時,才會定義這個狀態。
6、錯誤條件
(1)若呼叫程式沒有子程式,那麼waitpid返回-1,並且設定errno為ECHILD;
(2)若waitpid函式被一個訊號中斷,那麼返回-1,並設定errno為EINTR
7、wait函式
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
//返回:若成功,返回子程式的PID;若錯誤,返回-1。
呼叫wait(&status)等價於呼叫waitpid(-1.&status,0)
8.4.4 讓程式休眠
1、sleep函式:將程式掛起一段指定的時間
#include <unistd.h>
unsigned int sleep(unsigned int secs);
//返回:還要休眠的秒數
- 如果請求的時間量已經到了,返回0,否則返回還剩下的要休眠的秒數。
2、pause函式:讓呼叫函式休眠,直到該程式收到一個訊號。
#include <unistd.h>
int pause(void);
//返回:總是-1
8.4.5 載入並執行程式
1、execve函式:在當前程式的上下文中載入並執行一個新程式。
#include <unistd.h>
int execve(const char *filename,const char *argv[],const char *envp[]);
//返回:若成功,則不返回,若錯誤,返回-1
- filename:可執行目標檔案
- argv:帶引數列表
- envp:環境變數列表
- 特點:execve呼叫一次從不返回
2、getenv函式:在環境陣列中搜素字串“name =VALUE”,若找到了,就返回一個指向value的指標,否則它就返回NULL。
#include <stdlib.h>
char *getenv(const char *name);
//返回:存在,返回指向name的指標,若無匹配的,為NULL
3、注意:
- execve函式在當前程式的上下文中載入並執行一個新的程式。它會覆蓋當前程式的地址空間,並沒有建立一個新的程式,新的程式仍然有相同的PID,並且繼承了呼叫execve函式時已開啟的所有檔案描述符。
8.4.6 利用fork和execve執行程式
1、外殼是一個互動型的應用級程式,它代表使用者執行其他程式。
2、外殼執行一系統的讀/求值步驟,然後終止。讀步驟讀取來自使用者的一個命令列,求值步驟解釋命令列,並代表使用者執行程式。
3、eval函式:對外殼命令列求值
4、parseline函式:解析外殼的一個輸入
8.5 訊號
- 底層的硬體異常是由核心異常處理程式處理的,正常情況下,對使用者程式而言是不可見的。
- 其他訊號對應於核心或者其他使用者程式中較高層的軟體事件。
8.5.1 訊號術語
1、傳送訊號的兩個不同步驟:
(1)傳送訊號:核心通過更新目的程式上下文中的某個狀態,傳送(遞送)一個訊號給目的程式。
傳送訊號的兩個原因:
(1)核心監測到一個系統事件,比如被零除錯誤或者子程式終止。
(2)一個程式呼叫了kill函式,顯式地要求核心傳送一個訊號給目的程式。一個程式可以傳送訊號給它自己。
(2)接收訊號:訊號處理程式捕獲訊號的基本思想。
2、待處理訊號:一個只發出而沒有被接收的訊號
- 一個程式可以有選擇性地阻塞接收某種訊號。
- 待處理訊號不會被接收,直到程式取消對這種訊號的阻塞。
3、一個待處理訊號最多隻能被接受一次,pending位向量:維護著待處理訊號集合,blocked向量:維護著被阻塞的訊號集合。
8.5.2 傳送訊號
1、程式組
每個程式都只屬於一個程式組,程式組是由一個正整數程式組ID來標識的。getpgrp函式返回當前程式的程式組ID:預設地,一個子程式和它的父程式同屬於一個程式組。
2、用/bin/kill/程式傳送訊號 一個為負的PID會導致訊號被髮送到程式組PID中的每個程式。
3、從鍵盤傳送訊號
作業:表示對一個命令列求值而建立的程式。外殼為每個作業建立一個獨立的程式組。
4、用kill函式傳送訊號
程式通過呼叫kill函式傳送訊號給其他的程式。父程式用kill函式傳送SIGKILL訊號給它的子程式。
5、用alarm函式傳送訊號
在任何情況下,對alarm的呼叫都將取消任何待處理的鬧鐘,並且返回任何待處理的鬧鐘在被髮送前還剩下的秒數。
8.5.3 接收訊號
1、當核心從一個異常處理程式返回,準備將控制傳遞該程式p時,它會檢查程式p的未被阻塞的待處理訊號的集合。如果這個集合是非空的,那麼核心選擇集合中的某個訊號k,並且強制p接收訊號k。
2、程式可以通過使用signal函式修改和訊號相關聯的預設行為。 唯一例外是SIGSTOP和SIGKILL,它們的預設行為是不能被修改的。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
//返回:若成功,返回指向前次處理程式的指標;若出錯,為SIG_ERR
3、signal函式改變和訊號signum相關聯的行為的三種方法:
(1)handler是SIG_ IGN,忽略型別為signum的訊號;
(2)handler是SIG_ DFL,型別為signum的訊號行為恢復為預設行為。 (3)否則,handler就是使用者定義的函式地址。這個函式稱為訊號處理程式。
4、設定訊號處理程式:通過把處理程式的地址傳遞到signal函式從而改變預設行為。
- 捕獲訊號:呼叫訊號處理程式。
- 處理訊號:執行訊號處理程式。
5、因為訊號處理程式的邏輯控制流與主函式的邏輯控制流重疊,訊號處理程式和主函式併發執行。
8.5.4 訊號處理問題
1、待處理訊號被阻塞:
2、待處理訊號不會排隊等待;
3、系統呼叫可以被中斷:像read、wait、accept這樣的系統呼叫潛在地會阻塞程式一段較長的時間,稱為慢速系統呼叫。
- 注意:不可以用訊號來對其他程式中發生的事件計數。
8.5.5 可移植的訊號處理
- 訊號處理語義的差異,是UNIX訊號處理的一個缺陷。
8.5.6 顯式地阻塞和取消阻塞訊號
- sigprocmask函式改變當前已阻塞訊號的訊號。
- how的值:
(1)SIG_ BLOCK :新增set中的訊號到blocked中
(2)SIG_ UNBLOCK:從blocked中刪除set中的訊號
(3)SIG_ SETMASK:blocked = set
實踐部分
1.sigactdemo
#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以外的所有訊號。
2.sigactdemo2
#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;
}
- 每兩秒輸出一次
3.sigdemo1
#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
4.sigdemo2
#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
不能停止。 - SIG_DFL,SIG_IGN 分別表示無返回值的函式指標,指標值分別是0和1,這兩個指標值邏輯上講是實際程式中不可能出現的函式地址值。
- SIG_DFL:預設訊號處理程式
- SIG_IGN:忽略訊號的處理程式
5.sigdemo3
#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");
}
- 多訊號處理SIGX打斷SIGX的情況
- 遞迴,呼叫同一個處理函式
- 忽略第二給訊號
- 阻塞第二個訊號直到第一個處理完畢
6.exec1
#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;
}
ok
int execvp(const char file ,char const argv []);
- execvp()會從PATH 環境變數所指的目錄中查詢符合引數file 的檔名,找到後便執行該檔案,然後將第二個引數argv傳給該欲執行的檔案。
- 如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno中。
- 在執行時exevp函式呼叫成功沒有返回,所以沒有列印“* * * ls is done. bye”
7.exec2
#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");
}
- exec1傳的是ls,exec2傳送的是arglist[0],但執行結果是相同的。
8.exer3
#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。
9.forkdemo1
#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函式建立一個新的執行子程式。
- 呼叫一次,返回兩次。一次返回到父程式,一次返回到新建立的子程式。
10.forkdemo2
#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輸出。
11.forkdemo4
#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一句,休眠十秒;子程式返回0,所以輸出child與之後一句。
12.forkgdb
#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;
}
- 父程式列印是先列印兩句,然後休眠一秒,然後列印一句,子程式先列印一句,然後休眠一秒,然後列印兩句。並且這兩個執行緒是併發的,所以可以看到在一個執行緒休眠的那一秒,另一個執行緒在執行,並且執行緒之間相互獨立互不干擾。
13.psh1
#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就不能再接受新的命令。
14.psh2
#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;
}
- 比起psh1多了迴圈判斷,不退出的話就可以一直保持在輸入指令,並且對於子程式存在的狀態條件。
- 為了解決這個問題,程式通過呼叫fork來複制自己。
- 呼叫fork函式之後核心的工作過程:
分配新的記憶體塊和核心資料結構
複製原來的程式到新的程式
向執行程式集新增新的程式
將控制返回給兩個程式
15.testbuf1
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello");
fflush(stdout);
while(1);
}
- 效果是先輸出hello,然後保持在迴圈中不結束程式。
16.testbuf2
#include <stdio.h>
int main()
{
printf("hello\n");
while(1);
}
- fflush(stdout)的效果和換行符\n是一樣的。
17.testbuf3
#include <stdio.h>
int main()
{
fprintf(stdout, "1234", 5);
fprintf(stderr, "abcd", 4);
}
- 將內容格式化輸出到標準錯誤、輸出流中。
18.testpid
#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。
19.testpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
char **pp;
pp[0] = malloc(20);
return 0;
}
- 出現段錯誤,核心已轉儲
- 我覺得問題在於沒給pp分配空間就呼叫了pp[0],畢竟宣告的時候只是一個指標,而指標必須要初始化。
- 我認為應該改成:
include <stdio.h>
include <stdlib.h>
int main()
{
char pp;
pp = (char)malloc(20);
pp[0] = (char*)malloc(20);
return 0;
}
20.testsystem
#include <stdlib.h>
int main ( int argc, char *argv[] )
{
system(argv[1]);
system(argv[2]);
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
- system()——執行shell命令,也就是向dos傳送一條指令。這裡是後面可以跟兩個引數,然後向dos傳送這兩個命令,分別執行。
21.waitdemo1
#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。
22.waitdemo2
#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。
其他
- 本週按照程式碼驅動學習將自己的git目錄重新更新了。