20145201《資訊保安系統設計基礎》第11周學習總結

20145201李子璇發表於2016-11-27

20145201 《資訊保安系統設計基礎》第11周學習總結

教材學習內容總結

第8章異常控制流

控制流:控制轉移序列。

控制轉移:從一條指令到下一條指令。

異常控制流:現代作業系統通過使控制流發生突變來對系統狀態做出反應,這些突變稱為異常控制流。

硬體的一個系統狀態的改變,會觸發控制轉移到異常處理程式。(程式狀態變化時,會觸發控制轉移到該去的地方)核心狀態的變化,會觸發控制從一個使用者程式轉移到另一個使用者程式。應用層狀態的變化,會讓一個使用者程式將控制轉移到其訊號處理程式中。

所以,異常控制流,講的是控制各種狀態;狀態變化了,肯定有一定的方法可以檢測,檢測之後,就對應的轉移控制。其實就是設定了一些條件,如果條件觸發了,就做一些事情。核心就是這樣的。

現在有兩個問題:系統狀態是我設定的條件,我如何檢測條件是否觸發了;對各種系統狀態的觸發,結果是什麼?

ECF是作業系統用來實現I/O、程式和虛擬儲存器的基本機制。

系統呼叫是一種ECF,我在程式中呼叫系統函式,就是一個觸發,然後系統給出反應,作業系統為應用程式提供了強大的ECF機制,用來建立新程式等。

ECF是系統的基礎,系統中能稱為觸發的很多,或者說絕大多數的操作都可以稱為觸發,也就是變化系統狀態,也就是ECF的一種表現,ECF無處不在。作業系統也就是一個各種觸發和反應的集合體。

8.1 異常

異常是ECF的一種,一部分由硬體實現,一部分由作業系統實現。就是位於硬體和作業系統之間的ECF。

硬體上,系統狀態實際是處理器的狀態,處理器的狀態通常就是不同的位和訊號(暫存器的位),處理器狀態的變化(比如說某個bit置一)稱為事件。

事件可能和當前指令的執行相關。比如當前指令執行中產生了溢位。
事件也可能和當前指令沒有關係,比如系統定時器產生訊號,或者,一個I/O請求完成。

1、異常處理
異常表:當處理器檢測到有事件發生時,它會通過跳轉表,進行一個間接過程呼叫(異常),到異常處理程式。
異常號:系統中可能的某種型別的異常都分配了一個唯一的非負整數的異常號。異常號是到異常表中的索引。

關係:
異常號是到異常表中的索引,異常表的起始地址放在異常表基址暫存器。

異常類似於過程呼叫,但有一些重要的不同之處。
1.處理器壓入棧的返回地址,是當前指令地址或者下一條指令地址。
2.處理器也把一些額外的處理器狀態壓到棧裡
3.如果控制一個使用者程式到核心,所有專案都壓到核心棧裡。
4.異常處理程式執行在核心模式下,對所有的系統資源都有完全的訪問許可權。
一旦硬體觸發了異常,異常處理程式則由軟體完成。

2、異常的類別——中斷、陷阱、故障和終止
20145201《資訊保安系統設計基礎》第11周學習總結
a)中斷處理:非同步是指硬體中斷不是由任何一條指令造成的,而是由外部I/O裝置的事件造成的。
b)陷阱和系統呼叫:系統呼叫是一些封裝好的函式,內部通過指令int n實現。
陷阱最重要的用途是提供系統呼叫。系統呼叫執行在核心模式中,並且可以訪問核心中的棧。
系統呼叫的引數是通過通用暫存器而不是棧來傳遞的,如,%eax儲存系統呼叫號,%ebx,%ecx,%edx,%esi,%edi,%ebp最多儲存六個引數,%esp不能用,因為進入核心模式後,會覆蓋掉它。
c)故障
d)終止

二、程式
(作業系統層):邏輯控制流,私有地址空間,多工,併發,並行,上下文,上下文切換,排程。
程式就是一個執行中的程式例項。系統中的每個程式都是執行在某個程式的上下文中的。
程式提供給應用程式的關鍵抽象:

  • 一個獨立的邏輯控制流 類似獨佔的使用處理器
  • 一個私有的地址空間 類似獨佔的使用儲存器系統
    1、邏輯控制流
    程式計數器(PC)值的序列叫做邏輯控制流,簡稱邏輯流。如下圖所示,處理器的一個物理控制流分成了三個邏輯流,每個程式一個。
    20145201《資訊保安系統設計基礎》第11周學習總結
併發流:併發流一個邏輯流的執行在時間上與另一個流重疊。           
併發:多個流併發執行的一般現象稱為併發。
多工:多個程式併發叫做多工。
並行:併發流在不同的cpu或計算機上。

2、私有地址空間
一個程式為每個程式提供它自己的私有地址空間。
執行應用程式程式碼的程式初始時是在使用者模式中的。程式從使用者模式變為核心模式的唯一方法是通過異常。
linux提供了/proc檔案系統,它允許使用者模式程式訪問核心資料結構的內容。

3、私有地址空間
每個私有地址空間都關聯著一段真實的儲存器空間,一般來說,一般的理解,這些對應著的真實的儲存器空間應該是相互都錯開的。但每個這樣的空間都有相同的通用結構。
20145201《資訊保安系統設計基礎》第11周學習總結

  • 執行應用程式的程式初始時是在使用者模式中的。
  • 程式從使用者模式變為核心模式的唯一方法是通過諸如中斷、故障、陷阱這樣的異常。
  • 當異常發生時,控制轉移到異常處理程式,處理器將模式從使用者模式變為核心模式。當它返回到應用程式程式碼時,處理器就把模式從核心模式改回到使用者模式。

4、上下文切換,排程

上下文切換:作業系統核心使用叫上下文切換的異常控制流來實現多工。
上下文切換:

- 儲存當前程式的上下文;
- 恢復某個先前被搶佔的程式被儲存的上下文;
- 將控制傳遞給這個新恢復的程式

排程:核心中的排程器實現排程。
當核心代表使用者執行上下文切換時,可能會發生上下文切換。如果系統呼叫發生阻塞,那麼核心可以讓當前程式休眠,切換到另一個程式,如read系統呼叫,或者sleep會顯示地請求讓呼叫程式休眠。一般,即使系統呼叫沒有阻塞,核心亦可以決定上下文切換,而不是將控制返回給呼叫程式。
中斷也可能引起上下文切換。如,定時器中斷。

三、系統呼叫錯誤
當UNIX系統級函式遇到錯誤時,它們典型地會返回-1,並設定全域性整數變數errno來表示什麼出錯了。

四、程式控制
1、獲取程式ID
每個程式都有一個唯一的正數程式ID(PID)。

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回撥用程式的PID
pid_t getppid(void);    返回父程式的PID(建立呼叫程式的程式)

2、建立和終止程式
從程式設計師的角度,我們可以認為程式總處於下面三種狀態之一:

執行——在cpu上執行,或者,等待執行且最終會執行(會被核心排程)
停止——程式被掛起(也就是被其他的程式搶佔了),且不會被排程,但可以被訊號喚醒
終止——程式被永遠的停止了,受到終止訊號,或者從主程式返回,或者呼叫exit函式。

父程式通過呼叫fork建立一個新的執行子程式:父程式與子程式有相同(但是獨立的)地址空間

fork函式定義如下:

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

20145201《資訊保安系統設計基礎》第11周學習總結

  • 呼叫一次,返回兩次。父程式返回子程式的PID,子程式返回0.如果失敗返回-1.
  • 父程式和子程式是獨立的程式,併發執行的。一般而言,作為程式設計師,我們決不能對不同的程式中指令的交替執行做任何假設。
  • 相同的但是獨立的地址空間。
  • 共享檔案。

呼叫fork函式n次,產生2^n次方個程式

3、回收子程式

  • 當一個程式由於某種原因終止時,核心並不是立即把它從系統中清除。相反,程式被保持在一種已終止的狀態中。直到被它的父程式回收。

  • 當父程式回收已終止的子程式時,核心將子程式的退出狀態傳遞給父程式。然後拋棄已終止的程式。

  • 一個終止了但是還未被回收的程式稱為僵死程式。

  • 如果父程式沒有回收它的僵死子程式就終止了,那麼核心就會安排另外一個程式來回收它們,這個程式是init。其pid為1。其在系統初始化時由核心建立。
  • 一個程式可以通過呼叫waitpid函式來等待它的子程式終止或者停止。
    waitpid函式定義如下:
    20145201《資訊保安系統設計基礎》第11周學習總結

這個函式有三個引數:

pid
status
options

當options=0時,呼叫這個函式的程式掛起,也就是這個程式處於函式中什麼也不做,等待著,等待什麼呢,等待其子程式終止,如果終止了一個,那麼函式就返回了,返回的,就是終止的子程式的pid,並且將這個子程式從系統中除去。

  • 等待的子程式有哪些?
    這點由pid決定:pid=-1,那麼就是所有的子程式
    pid>0,那麼就是一個子程式,當前pid表示的那個子程式。
  • 修改預設行為
    options=WNOHANG時,如果沒有終止的子程式,那麼函式立即返回,返回0。
    options=WUNTRACED時,和options=0類似,但這裡還檢測停止的子程式。
    options=0只檢測終止的子程式。且,本options不會將子程式從系統中除去。
    options=WNOHANG|WNUNTRACED時,立即返回,返回值要麼是停止或者終止的pid,要麼是0。

  • 條件錯誤
    如果呼叫程式沒有子程式,那麼waitpid返回-1,並設定errno為ECHLILD;如果waitpid函式被一個訊號中斷,那麼它返回-1,並設定errno為EINTR。

  • wait函式:
    wait函式是waitpid函式的簡單版本
    20145201《資訊保安系統設計基礎》第11周學習總結
    wait(&status)函式,等價於呼叫wait(-1,&status,0)

4、讓程式休眠:
sleep函式將一個程式掛起一段指定的時間。

  • sleep函式定義:
#include <unistd.h>
unsigned int sleep(unsigned int secs);

返回值是剩下還要休眠的秒數,如果請求時間量到了返回0。

  • pause函式讓呼叫函式休眠,知道該程式收到一個訊號。
    20145201《資訊保安系統設計基礎》第11周學習總結

5、載入並執行程式
execve函式在當前程式的上下文中載入並執行一個新程式。

#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);

成功不返回,錯誤返回-1。
execve函式呼叫一次,從不返回。
20145201《資訊保安系統設計基礎》第11周學習總結
解釋:
filename:可執行目標檔案
argv:引數列表
envp:環境列表

新程式開始時:
20145201《資訊保安系統設計基礎》第11周學習總結

fork函式和execve函式的區別:
fork函式在新的子程式中執行相同的程式,新的子程式是父程式的一個複製品。

execve函式在當前程式的上下文中載入並執行一個新的程式,它會覆蓋當前程式的地址空間,但並沒有建立一個新程式。
新的程式仍然有相同的pid,並且繼承了呼叫execve函式時已開啟的所有檔案描述符。

五、訊號
Unix訊號是一種更高層次的軟體形式的異常。
20145201《資訊保安系統設計基礎》第11周學習總結

linux支援30種不同型別的異常。

每種訊號型別都對應於某種系統事件。底層的硬體異常是由核心異常處理程式處理的,正常情況下,對使用者程式而言是不可見的。訊號提供了一種機制,通知使用者程式發生了這些異常。
1、訊號術語
傳遞一個訊號到目的程式的兩個步驟

傳送訊號——核心通過更新目的程式上下文中的某個狀態,告訴目的程式,有一個訊號來了。
接收訊號——當目的程式被核心強迫以某種方式對訊號的傳送做出反應時,目的程式就接收了訊號。
程式可以忽略、終止、捕獲。

2、傳送訊號的方式

/bin/kill
鍵盤傳送訊號
kill函式
alarm函式

3、接收訊號的預設行為

  • 程式終止
  • 程式終止並轉儲儲存器
  • 程式停止直到被SIGCONT訊號重啟
  • 程式忽略該訊號。

但接受訊號的行為可以不是預設行為,通過設定訊號處理程式。
4、訊號處理問題
當一個程式要捕獲多個訊號時,一些細微的問題就產生了。

- 待處理訊號被阻塞——Unix訊號處理程式通常會阻塞當前處理程式正在處理的型別的待處理訊號。
- 待處理訊號不會排隊等待——任意型別至多隻有一個待處理訊號。
- 系統呼叫可以被中斷——像read、wait、accept這樣的系統呼叫潛在的會阻塞程式一段較長的時間,稱為慢速系統呼叫。
  • 父程式有一套自己的訊號處理程式,放在那裡,然後父程式用了一個read函式,這個函式會執行一段時間,在執行的這段時間裡,來了一個訊號,所以控制轉移到了訊號處理程式,也就是說控制從read函式裡跳到了訊號處理程式。當處理程式處理完畢了,其返回,在有的系統中,返回了,但read函式的呼叫不再繼續了,而是直接返回給使用者一個錯誤。當然在其他的系統中,比如linux,還是會返回到read函式繼續執行。

  • 父子程式的,對於任意一個程式,單獨的程式,其只有一份控制,這份控制可以交給陷阱(系統呼叫),也可以交給訊號處理程式,但貌似,訊號處理程式有更高的優先順序,當訊號來的時候,如果不被阻塞,那麼立刻的就跳到了訊號處理程式中處理,而不管當前的控制是否在main中還是是否在系統呼叫也就是核心中。更有一點,就是訊號處理程式處理完了返回時,系統呼叫是否恢復是因系統而異的。
    同時,父子程式之間存在這一種競爭關係,一般是:父子之間相互發訊號。

6 非本地跳轉

c語言提供一種使用者級異常控制流形式——非本地跳轉。

  • setjump函式在env緩衝區中儲存當前呼叫環境,以供後面longjmp使用,並返回零。

呼叫環境:程式計數器,棧指標,通用目的暫存器

  • longjmp函式從env緩衝區中恢復呼叫環境,然後觸發一個從最近一次初始化env的setjmp呼叫的返回。然後setjmp返回,並帶有非零的返回值retval。
setjump函式與longjmp函式區別:
setjmp函式只被呼叫一次,但返回多次
longjmp函式被呼叫一次,但從不返回。

7、操作程式的工具

STRACE:列印一個正在執行的程式和他的子程式呼叫的每個系統呼叫的痕跡
PS:列出當前系統中的程式,包括僵死程式
TOP:列印出關於當前程式資源使用的資訊
PMAP:顯示程式的儲存器對映
/proc

8、小結

  • 硬體層——四種不同型別的異常:中斷、故障、終止、陷阱。
  • 作業系統層——核心用ECF提供程式的基本概念。
  • 在作業系統和應用程式的介面處——訊號
  • 應用層——非本地跳轉

process實踐部分

1、argtest.c

#include <stdio.h>
#include <stdlib.h>
#include "argv.h"//該函式庫中包括freemakeargv.c及makeargv.c函式的呼叫

int main(int argc, char *argv[]) 
{
   char delim[] = " \t";//製表符
   int i;
   char **myargv;//見下方解釋
   int numtokens;

   if (argc != 2)//如果輸入的命令字元個數不等於2,就輸出標準錯誤 
   {
        fprintf(stderr, "Usage: %s string\n", argv[0]);
        return 1;
   }   
  if ((numtokens = makeargv(argv[1], delim, &myargv)) == -1) 
  {
        fprintf(stderr, "Failed to construct an argument array for %s\n", argv[1]);//意思是無法構造一個引數陣列
        return 1;
   } 
   printf("The argument array contains:\n");
   for (i = 0; i < numtokens; i++)
        printf("%d:%s\n", i, myargv[i]);
   execvp(myargv[0], myargv);

   return 0;
}

20145201《資訊保安系統設計基礎》第11周學習總結

2、environ.c

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("PATH=%s\n", getenv("PATH"));//getenv函式用來取得引數PATH環境變數的值,執行成功則返回該內容的指標
    setenv("PATH", "hello", 1);/*setenv用來在本次函式執行的過程中增加或者修改環境變數。當最後一個引數不為0的時候,原來的內容會被修改為第二個引數所指的內容。*/
    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;
}

20145201《資訊保安系統設計基礎》第11周學習總結

3、environvar.c

#include <stdio.h>
int main(void)
{
    extern char **environ;
    int i;
    for(i = 0; environ[i] != NULL; i++)/*該變數指向一個叫“environment”的字串陣列。包括USER(登入使用者的名字),LOGNAME(與user類似),HOME(使用者登入目錄),LANG(地域名),PATH等*/
        printf("%s\n", environ[i]);

    return 0;
}

20145201《資訊保安系統設計基礎》第11周學習總結

4、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);
}

5、producer

#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);//依據FIFO_NAME建立fifo檔案,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);
}

20145201《資訊保安系統設計基礎》第11周學習總結

6、testmf.c

#include  <stdio.h>
#include  <stdlib.h>
#include  <sys/types.h>
#include  <sys/stat.h>

int main()//就是建立fifo檔案
{
    int res = mkfifo("/tmp/myfifo", 0777);
    if (res == 0) {
        printf("FIFO created \n");
    }
    exit(EXIT_SUCCESS);
}

20145201《資訊保安系統設計基礎》第11周學習總結

7、pipe.c

#include    <stdio.h>
#include<stdlib.h>
#include    <unistd.h>

#define oops(m,x)   //當linux系統執行程式碼遇到問題時,就會報告錯誤
{ perror(m); exit(x); }

int main(int ac, char **av)
{
    int thepipe[2], newfd,pid;              

    if ( ac != 3 ){//輸入的命令長度不等於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);
}

20145201《資訊保安系統設計基礎》第11周學習總結

8、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 );
}

20145201《資訊保安系統設計基礎》第11周學習總結
結果輸入什麼就列印出什麼

9、testtty.c

#include <unistd.h>
int main()
{
    char *buf = "abcde\n";
    write(0, buf, 6);/*write(int handle,void *buf,int nbyte); 第一個引數是檔案描述符,第二個引數是指向一端記憶體單元的指標,第三個引數是要寫入指定檔案的位元組個數;成功時返回位元組個數,否則返回-1。*/
}

20145201《資訊保安系統設計基礎》第11周學習總結

10、sigactdemo1.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;  //sa_flags是一個位掩碼。這裡,第一個引數使得被訊號打斷的一些原語“正常返回”
    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);
}

輸入什麼就列印什麼,如下列印學號:
20145201《資訊保安系統設計基礎》第11周學習總結

11、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");
}

signal函式:
原型 signal(引數1,引數2);
其中引數1是進行處理的訊號,引數2是處理的方式。

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

20145201《資訊保安系統設計基礎》第11周學習總結

休息two seconds秒後返回;或者被訊號中斷且訊號處理函式返回後sleep()返回0。
如果不計較返回值的話,pause()的功能相當於無限期的sleep()。

13、exec1

#include <stdio.h>
#include <unistd.h>//表標頭檔案

int main()
{
    char    *arglist[3];//arglist屬於命令列引數,表示不定引數列表,表示後跟不定個引數。

    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函式:

int execvp(const char file ,char const argv []);

execvp()會從PATH 環境變數所指的目錄中查詢符合引數file 的檔名,找到後便執行該檔案,然後將第二個引數argv傳給該欲執行的檔案。

如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno中。

20145201《資訊保安系統設計基礎》第11周學習總結

14、exec2
exevp函式的第一個引數,exec2的引數是arglist[0],與exec1等價,執行結果相同

15、exec3

#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");
}

這裡使用了execlp()函式,
標頭檔案:

include<unistd.h>

定義函式:
int execlp(const char file, const char argv ...)

execlp()會從PATH 環境變數所指的目錄中查詢符合引數file的檔名,找到後便執行該檔案,然後將第二個以後的引數當做該檔案的argv[0]、argv[1]……,最後一個引數必須用空指標(NULL)作結束。
如果用常數0來表示一個空指標,則必須將它強制轉換為一個字元指標,否則將它解釋為整形引數,如果一個整形數的長度與char * 的長度不同,那麼exec函式的實際引數就將出錯。
如果函式呼叫成功,程式自己的執行程式碼就會變成載入程式的程式碼,execlp()後邊的程式碼也就不會執行了.
返回值:
如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。

exec3程式碼指定了環境變數,然後依然執行了ls -l指令,成功後沒有返回,所以最後一句話不會輸出。執行結果同exec1。

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

20145201《資訊保安系統設計基礎》第11周學習總結

該程式碼首先列印程式pid,然後呼叫fork函式生成子程式,休眠一秒後再次列印程式id,這時父程式列印子程式pid,子程式返回0。

17、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輸出。
20145201《資訊保安系統設計基礎》第11周學習總結

18、forkdemo3

#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());
    
        exit(0);
    }
    else{
        printf("I am the parent. my child is %d\n", fork_rv);
        exit(0);
    }

    return 0;
}

程式碼呼叫一次fork產生子程式,父程式返回子程式pid,不為0,所以輸出父程式,子程式返回0,所以輸出子程式。
20145201《資訊保安系統設計基礎》第11周學習總結

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

20145201《資訊保安系統設計基礎》第11周學習總結

呼叫一次fork,父程式先列印兩句,然後休眠一秒,然後列印一句,子程式先列印一句,然後休眠一秒,然後列印兩句。這兩個執行緒是併發的,所以可以看到在一個執行緒休眠的那一秒,另一個執行緒在執行,並且執行緒之間相互獨立互不干擾。

20、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);
}

20145201《資訊保安系統設計基礎》第11周學習總結

waitdemo1:如果有子程式,則終止子程式,成功返回子程式pid。
waitdemo2:多了一個子程式的狀態區分,把狀態拆分成三塊,exit,sig和core。

程式碼除錯問題

testmf.c程式碼執行完畢後無顯示,為了方便知道它已經執行完畢,自己嘗試修改了原始碼,如下:
20145201《資訊保安系統設計基礎》第11周學習總結

再次執行結束後便可以清楚的知道它finish了:
20145201《資訊保安系統設計基礎》第11周學習總結

sigactdemo2.c開始執行後,每兩秒才會顯示,並無限迴圈。
可以使用ctrl+c使它停止
20145201《資訊保安系統設計基礎》第11周學習總結

其他

本章閱讀內容比較多,主要掌握程式及其控制函式的使用,訊號傳送函式,接收函式和處理函式。
這周老師給了很多程式碼包,動手實踐的過程很多,感覺學到了不少的內容。比如fork個函式的特點,雖然在書本上有講過,但動手實踐後感受到了它更深的原理。

本週程式碼託管截圖

程式碼連結

20145201《資訊保安系統設計基礎》第11周學習總結

學習進度條

程式碼行數(新增/累積) 部落格量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 100/100 2/2 25/25 安裝了虛擬機器並學習掌握核心的linux命令
第二週 100/200 1/3 30/55 虛擬機器上的C語言程式設計
第三週 150/350 1/4 10/65 計算機中資訊的表示和運算
第四周 0/350 0/4 3/68 複習前幾周內容
第五週 75/420 1/5 20/88 程式的機器級表示
第六週 125/545 1/6 20/108 Y86指令 硬體語言控制HCL
第七週 72/617 1/7 20/128 磁碟 儲存器相關結構
第八週 0/617 2/9 20/148 期中總結
第九周 185/802 2/11 25/173 系統級的輸入輸出
第十週 669/1472 2/13 20/193 重點程式碼的學習
第十一週 669/1472 2/15 35/228 process程式碼的學習

參考資料

相關文章