天天看點

作業系統原理 實驗指導書-實驗0 Linux基礎實驗0 Linux基礎include <unistd.h>

實驗0 Linux基礎

【實驗目的】

1.掌握Linux基本指令接口的使用方法與常用指令;

2.掌握在Linux環境下如何編輯、編譯和運作一個C語言程式。

3.學會利用gcc、gdb編譯、調試C程式。

【預習内容】

1.預習Linux虛拟機的安裝,并安裝一個Linux虛拟機(redhat,ubuntu或fedora,建議使用ubuntu)。

2.預習常用的SHELL指令。

4.預習Linux下C程式編輯、編譯和運作過程。

【實驗内容】

一、登陸Linux

輸入使用者名: root ,輸入密碼: 123456 ,進入redhat5圖形桌面環境。

作業系統原理 實驗指導書-實驗0 Linux基礎實驗0 Linux基礎include <unistd.h>
作業系統原理 實驗指導書-實驗0 Linux基礎實驗0 Linux基礎include <unistd.h>

二、熟悉Linux圖形使用者界面(GUI)

目前,Linux上有兩種GUI:KDE和GNOME,GNOME使用者量比較大,主要的原因是KDE4經常崩潰,據了解,目前KDE5還可以。

圖形使用者界面,由于比較簡單,請各位同學自學。

啟動終端模拟器

GNOME終端模拟器用一個視窗來模拟字元終端的行為。終端常常被稱為指令行或者 shell,Linux 中絕大部分工作都可以用指令行完成。要啟動一個終端,可以選擇 應用程式 → 附件 → 終端。

作業系統原理 實驗指導書-實驗0 Linux基礎實驗0 Linux基礎include <unistd.h>

三、練習常用的Shell指令。(重點)

當使用者登入到字元界面系統或使用終端模拟視窗時,就是在和稱為shell的指令解釋程式進行通信。當使用者在鍵盤上輸入一條指令時,shell程式将對指令進行解釋并完成相應的動作。這種動作可能是執行使用者的應用程式,或者是調用一個編輯器、GNU/Linux實用程式或其他标準程式,或者是一條錯誤資訊,告訴使用者輸入了錯誤的指令。

1.目錄操作

mkdir abc 建立一個目錄abc

cd abc 将工作目錄改變到abc

cd 改變目前目錄到主目錄

ls 列出目前目錄的内容

ls -l 輸出目前目錄内容的長清單,每個目錄或檔案占一行

pwd 顯示目前目錄的全路徑

2.檔案顯示實用程式

cat mx.c 顯示mx.c檔案内容

more mx.c 分屏顯示mx.c内容

tail mx.c 顯示檔案後幾行

cat file1 file2 連接配接file1 和file2

head filename 顯示檔案filename的開始10行

wc filename 統計檔案filename中的行數、單詞數和字元數

od 檔案 檢視非文本檔案

3.檔案管理實用程式

cp file1 file2 将檔案1複制到檔案2

mv file1 file2 将檔案重命名為file2

rm filename 删除檔案filename

rm -i filename 請求使用者确認删除

4.資料操作實用程式

tty                  顯示目前終端的路徑和檔案名
who                  顯示目前登入使用者的清單
sort  filename       顯示檔案filename中的行的排序結果
           

spell filename 檢查檔案filename中的拼寫錯誤

5. 改變檔案或目錄的存取權

我們知道,每個檔案或目錄對系統的3種人,即檔案的主人、同組人和其他人各有3種存取權。這3種存取權是讀權、寫權和執行權。在檔案初建時,檔案的主人對其隻有讀、寫權,同組人隻有讀權,其他人無任何存取權。這種存取權在系統種要根據需要進行變化,用chmod指令來實作。Chmod指令有兩種格式,一種是符号方式,一種是數字方式。現在先介紹第一種:
 chmod 誰  操作符 許可權  檔案名(目錄名)……
           

上面格式中各項的具體内容如下:

誰 操作符 許可權

u(user: 檔案主人) + r(read: 讀權)

g(group: 同組人) — w(write: 寫權)

o(other: 其他人) = x(excute: 執行權)

a(all: 所有人)

要給系統中某種人增加某種存取權可用“+”操作,要取消某種存取權可用“-“操作;而”=“操作則表示給表達式中指定的人以指定的存取權而取消其以前的存取權,下面簡單舉例進行說明:

$ shmod u+x file

表示給檔案的主人增加對檔案FILE的執行權。

$ chmod g=x file

表示給同組人對flie以執行權,同時取消其原有權利。

$ chmod u+x,g=x file

6.其他實用程式

date                 輸出系統日期和時間
cal                  顯示本月的月曆。cal 2002 顯示2002年的月曆
clear                清除終端螢幕
history              顯示你以前執行過的指令的清單
man                  顯示實用程式的有用資訊,并提供該實用程式的基本用法
echo                 讀取參數并把它寫到輸出
           

四、目錄和檔案系統

Linux 和 Unix 檔案系統被組織成一個有層次的樹形結構。檔案系統的最上層是 /,或稱為 根目錄。在 Unix 和 Linux 的設計理念中,一切皆為檔案——包括硬碟、分區和可插拔媒體。這就意味着所有其它檔案和目錄(包括其它硬碟和分區)都位于根目錄中。 例如:/home/jebediah/cheeses.odt 給出了正确的完整路徑,它指向 cheeses.odt 檔案,而該檔案位于 jebediah 目錄下,該目錄又位于 home 目錄,最後,home 目錄又位于根(/) 目錄下。 在根 (/) 目錄下,有一組重要的系統目錄,在大部分 Linux 發行版裡都通用。直接位于根 (/) 目錄下的常見目錄清單如下:

• /bin - 重要的二進制 (binary) 應用程式

• /boot - 啟動 (boot) 配置檔案

• /dev - 裝置 (device) 檔案

• /etc - 配置檔案、啟動腳本等 (etc)

• /home - 本地使用者主 (home) 目錄

• /lib - 系統庫 (libraries) 檔案

• /lost+found - 在根 (/) 目錄下提供一個遺失+查找(lost+found) 系統

• /media - 挂載可移動媒體 (media),諸如 CD、數位相機等

• /mnt - 挂載 (mounted) 檔案系統

• /opt - 提供一個供可選的 (optional) 應用程式安裝目錄

• /proc - 特殊的動态目錄,用以維護系統資訊和狀态,包括目前運作中程序 (processes) 資訊。

• /root - root (root) 使用者主檔案夾,讀作“slash-root”

• /sbin - 重要的系統二進制 (system binaries) 檔案

• /sys - 系統 (system) 檔案

• /tmp - 臨時(temporary)檔案

• /usr - 包含絕大部分所有使用者(users)都能通路的應用程式和檔案

• /var - 經常變化的(variable)檔案,諸如日志或資料庫等

五.打開PROC目錄了解系統配置

把/proc作為目前目錄,就可使用ls指令列出它的内容。

/proc 檔案系統是一種核心和核心子產品用來向程序 (process) 發送資訊的機制 。這個僞檔案系統讓你可以和核心内部資料結構進行互動,擷取有關程序的有用資訊,在運作中改變設定 (通過改變核心參數)。 與其他檔案系統不同,/proc 存在于記憶體之中而不是硬碟上。

1.察看 /proc 的檔案

/proc 的檔案可以用于通路有關核心的狀态、計算機的屬性、正在運作的程序的狀态等資訊。大部分 /proc 中的檔案和目錄提供系統實體環境最新的資訊。盡管 /proc 中的檔案是虛拟的,但它們仍可以使用任何檔案編輯器或像’more’, 'less’或 'cat’這樣的程式來檢視。

2.得到有用的系統/核心資訊

/proc 檔案系統可以被用于收集有用的關于系統和運作中的核心的資訊。下面是一些重要的檔案:

• /proc/cpuinfo - CPU 的資訊 (型号, 家族, 緩存大小等)

• /proc/meminfo - 實體記憶體、交換空間等的資訊

• /proc/mounts - 已加載的檔案系統的清單

• /proc/devices - 可用裝置的清單

• /proc/filesystems - 被支援的檔案系統

• /proc/modules - 已加載的子產品

• /proc/version - 核心版本

• /proc/cmdline - 系統啟動時輸入的核心指令行參數

proc 中的檔案遠不止上面列出的這麼多。想要進一步了解的讀者可以對 /proc 的每一個檔案都’more’一下 。

3.有關運作中的程序的資訊

/proc 檔案系統可以用于擷取運作中的程序的資訊。在 /proc 中有一些編号的子目錄。每個編号的目錄對應一個程序 id (PID)。這樣,每一個運作中的程序 /proc 中都有一個用它的 PID 命名的目錄。這些子目錄中包含可以提供有關程序的狀态和環境的重要細節資訊的檔案。

/proc 檔案系統提供了一個基于檔案的 Linux 内部接口。它可以用于确定系統的各種不同裝置和程序的狀态。對他們進行配置。因而,了解和應用有關這個檔案系統的知識是了解你的 Linux 系統的關鍵。

六、熟悉vim編輯器

在編寫文本或計算機程式時,需要建立檔案、插入新行、重新排列行、修改内容等,計算機文本編輯器就是用來完成這些工作的。

Vim編輯器的兩種操作模式是指令模式和輸入模式(如圖2所示)。當vim處于指令模式時,可以輸入vim指令。例如,可以删除文本并從vim中退出。在輸入模式下,vim将把使用者所輸入的任何内容都當作文本資訊,并将它們顯示在螢幕上。

vi的工作模式見圖2所示。

作業系統原理 實驗指導書-實驗0 Linux基礎實驗0 Linux基礎include <unistd.h>

⑴指令模式

在輸入模式下,按ESC可切換到指令模式。指令模式下,可選用下列指令離開vi:

指令 作 用

:q! 離開vi,并放棄剛在緩沖區内編輯的内容

:wq 将緩沖區内的資料寫入目前檔案中,并離開vi

:ZZ 同wq

:x 同wq

:w 将緩沖區内的資料寫入目前檔案中,但并不離開vi

:q 離開vi,若檔案被修改過,則要被要求确認是否放棄修改的内容,此指令可與:w配合使用

指令模式下光标的移動 :

命 令 作 用

h或左箭頭 左移一個字元

J 下移一個字元

k 上移一個字元

l 右移一個字元

0 移至該行的首

$ 移至該行的末

^ 移至該行的第一個字元處

H 移至視窗的第一列

M 移至視窗中間那一列

L 移至視窗的最後一列

G 移至該檔案的最後一列

W, W 下一個單詞 (W 忽略标點)

b, B 上一個單詞 (B 忽略标點)

  • 移至下一列的第一個字元處
  • 移至上一列的第一個字元處

    ( 移至該句首

    ) 移至該句末

    { 移至該段首

    } 移至該段末

    nG 移至該檔案的第n列

⑵輸入模式

輸入以下指令即可進入vi輸入模式:

命 令 作 用

a(append) 在光标之後加入資料

A 在該行之末加入資料

i(insert) 在光标之前加入資料

I 在該行之首加入資料

o(open) 新增一行于該行之下,供輸入資料用

O 新增一行于該行之上,供輸入資料用

Dd 删除目前光标所在行

X 删除目前光标字元

X 删除目前光标之前字元

U 撤消

• 重做

F 查找

s 替換,例如:将檔案中的所有"FOX"換成"duck",用":%s/FOX/duck/g"

ESC 離開輸入模式

啟動vim指令:

指令 作用

vim filename 從第一行開始編輯filename檔案

vim +filename 從最後一行開始編輯filename檔案

vim -r filename 在系統崩潰之後恢複filename檔案

vim -R filename 以隻讀方式編輯filename檔案

更多用法見 info vi。

vim 下程式錄入過程:

①$ vim aaa.c ↙ 進入vim指令模式

② i ↙ 進入輸入模式輸入 C源程式(或文本)

③ ESC ↙ 回到指令模式

④ ZZ ↙ 儲存檔案并推出vim

⑤ CAT aaa.c ↙ 顯示aaa.c 檔案内容

七、熟悉gcc編譯器

GNU/Linux中通常使用的C編譯器是GNU gcc。編譯器把源程式編譯生成目标代碼的任務分為以下4步:

a. 預處理,把預處理指令掃描處理完畢;

b. 編譯,把預處理後的結果編譯成彙編或者目标子產品;

c. 彙編,把編譯出來的結果彙編成具體CPU上的目标代碼子產品;

d. 連接配接,把多個目标代碼子產品連接配接生成一個大的目标子產品;

1.使用文法:

  gcc [ option | filename ]…

  其中 option 為 gcc 使用時的選項,而 filename 為 gcc要處理的檔案。

2.GCC選項

GCC的選項有很多類,這類選項控制着GCC程式的運作,以達到特定的編譯目的。

⑴全局選項(OVERALL OPTIONS)

全局開關用來控制在“GCC功能介紹”中的GCC的4個步驟的運作,在預設的情況下,這4個步驟都是要執行的,但是當給定一些全局開關後,這些步驟就會在 某一步停止執行,這産生中間結果,例如可能你隻是需要中間生成的預處理的結果或者是彙編檔案(比如你的目的是為了看某個CPU上的彙編語言怎麼寫)。

① –x language

對于源檔案是用什麼語言編寫的,可以通過檔案名的字尾來标示,也可以用這開關。指定輸入檔案是什麼語言編寫的,language 可以是如下的内容

a. c

b. objective-c

c. c-header

d. c++

e.cpp-output

f.assembler

g.assembler-with-cpp

②–x none

把-x開關都給關掉了。

③ –c

編譯成把源檔案目标代碼,不做連接配接的動作。

④–S

把源檔案編譯成彙編代碼,不做彙編和連接配接的動作。

⑤–E

隻把源檔案進行預處理之後的結果輸出來。不做編譯,彙編,連接配接的動作。

⑥ –o file (常用)

指明輸出檔案名是file。

⑦–v

把整個編譯過程的輸出資訊都給列印出來。

⑧–pipe

由于gcc的工作分為好幾步才完成,是以需要在過程中生成臨時檔案,使用-pipe就是用管道替換臨時檔案。

⑵ 語言相關選項(Language Options)

用來處理和語言相關的選項。

①–ansi

這個開關讓GCC編譯器把所有的GNU的編譯器特性都給關掉,讓你的程式可以和ansi标準相容。

②–include file

在編譯之前,把file包含進去,相當于在所有編譯的源檔案最前面加入了一個#include 語句,

③–C

同-E參數配合使用。讓預處理後的結果,把注釋保留,讓人能夠比較好讀它。

⑶連接配接開關(Linker Options)

用來控制連接配接過程的開關選項。

① –llibrary

連接配接庫檔案開關。例如-lugl,則是把程式同libugl.a檔案進行連接配接。

② –lobjc

這個開關用在面向對象的C語言檔案的庫檔案進行中。

③ –nostartfiles

在連接配接的時候不把系統相關的啟動代碼連接配接進來。

④ –nostdlib

在連接配接的時候不把系統相關的啟動檔案和系統相關的庫連接配接進來。

⑤–static

在一些系統上支援動态連接配接,這個開關則不允許動态連接配接。

⑥shared

生成可共享的被其他程式連接配接的目标子產品。

⑷目錄相關開關(Directory Options)

用于定義與目錄操作相關的開關。

–Ldir

搜尋庫檔案(*.a)的路徑。

⑸調試開關(Debugging Options)

–g

把調試開關打開,讓編譯的目标檔案有調試資訊。

–V version

用來告訴編譯器使用它的多少版本的功能,version參數用來表示版本。

八、掌握Linux下C程式編輯運作過程(重點)

Linux下編寫C程式要經過以下幾個步驟:

⑴啟動常用的編輯器,鍵入C源程式代碼。

例如,點選應用程式/附件/文本編輯器,進入編輯環境,輸入C源程式,儲存并命名為hello.c

include <stdio.h>

void main(void)

{

Printf(“Hello world!\n”);

}

⑵編譯源程式

點選應用程式/附件/終端,進入指令行。用gcc編譯器對C源程式進行編譯,以生成一個可執行檔案。方法:

gcc -o hello.out hello.c ↙

⑶運作可執行檔案

•/hello.out ↙

注:指令行中 -o選項表示要求編譯器輸出可執行檔案名為hello.out檔案,hello.c是源程式檔案。

關于Makefile檔案

Make是Linux下一個常用的自動化編譯管理工具,可以簡化和自動化多子產品程式的編譯過程

  1. 文法規則

    目标:依賴檔案集合

      指令1

      指令2

  2. 指令清單中的每條指令必須以TAB鍵開始,不能使用空格

    第一條規則的目标成為預設目标 例如main

main: main.o input.o calcu.o

gcc -o main main.o input.o calcu.o

main.o:main.c

gcc -c main.c

input.o:input.c

gcc -c input.c

calcu.o:calcu.c

gcc -c calcu.c

clean:

rm *.o

rm main

九、UNIX系統調用及應用(重點)

  1. UNIX系統調用概述

    在進階語言程式設計中,UNIX系統調用是系統為使用者程式提供的一組通路系統核心(kernel)的函數調用;如使用者在C語言程式設計中使用系統調用,則可在C語言程式設計中可直接應用系統調用的函數名;

    例: 檔案的随機存取。利用系統調用Lseek來定位檔案的讀寫位置,可實作對檔案的随機存取。

    Lseek的格式是

    long lseek (fd,offset,whence)

    long offset;int fd,whence;

    其中,fd是被打開的檔案号,offset和whence結合起來定位檔案的讀寫位置,若whence=0,則讀寫位置offset為(如offset=512,則下一個讀寫就從第512個位元組開始);若whence=1,則讀寫位置被指定為目前位置加offset(可正可負);若whence=2,則讀寫位置被指定為檔案末尾加offset。

    下面的C函數可從一個檔案的任意位置開始讀出指定位元組數的資訊。

    Get (fd,position,buf,n) /read n bytes from position to buf/

    Int fd,n,position;

    Char *buf;

    Int fd,n;

    Long position;

    {

    lseek (fd,position,0);

    rerurn (read(fd,buf,n));

    }

使用者在應用程式中通過系統調用可充分的利用作業系統提供的系統功能和服務,但是其程式執行的效率降低,原因如下:

•在啟動調用系統的時候會有一段不可避免的延遲,這是因為Linux必須把作為使用者級的程序請求化為核心級的程序,然後再把結果轉會使用者級的程序,這其中的花費比一般的函數調用要多得多。

•硬體裝置往往對一次輸入、輸出資料的尺寸有一定的限制;比如錄音帶裝置常常有一個最小塊限制,假設是10KB ,如果你想向錄音帶機寫入8KB 的資料,錄音帶機仍然會寫入10KB的資料,是以在錄音帶機上産生了一段缺口。

  1. 程序與線程

    ⑴概念

    程序: 程序是在一位址空間上執行的單一的指令序列。

    線程: 在程序中可以有多個指令序列并發執行,把每一個執行序列稱為線程。

    ⑵查閱程序

    我們使用ps指令查閱目前系統的程序。下面是個典型的輸出:

    $ps

    PID TTY STAT TIME COMMAND

    71 1 S 0:00 -bash

    72 2 S 0:00 -bash

    73 3 S 0:00 /sbin/agetty 38400 tty3 linux

    74 4 S 0:00 /sbin/agetty 38400 tty4 linux

    75 5 S 0:00 /sbin/agetty 38400 tty5 linux

    76 6 S 0:00 /sbin/agetty 38400 tty6 linux

    87 p0 S 0:00 -bash

    102 p0 S 0:00 ./mysqld – Sg – – port =1989

    103 p0 S 0:00 ./mysqld – Sg – – port =1989

    104 p0 S 0:00 ./mysqld – Sg – – port =1989

    108 p0 S 0:00 ./mysqld – Sg – – port =1989

    162 p1 S 0:00 -bash

    332 p1 S 0:00 ps

    這一輸出說明mysql 資料庫正在運作。其中:

    PID ----程序号

    TTY----啟動程序的終端

    STAT----程序目前的狀态

    TIME----程序消耗CPU的時間

    COMMAND----啟動程序的指令(包括指令參數)

⑶建立新的程序
   我們将介紹三種建立程序的方法:
           

• system函數

在一個程式中建立一個程序的最簡單的方法就是使用sysytem函數。

#include <stdlib.h>

int system(const char *string);

system函數執行由string參數指定的指令(可以包含參數),并将程式設定為等待狀态直到此指令結束,再繼續執行下面的代碼。

下面就利用system函數編寫一個程式,在程式中調用外部指令ps。

#include <stdlib.h>

#include <stdio.h>

int main()

{

printf(“Running ps with system\n”);

system(“ps -ax”);

print(“Done.\n”);

exit(0);

}

上機實習步驟:

輸入編輯源程式:

使用全螢幕編輯程式vi來輸入和編輯源程式(vi的使用方法見後面内容)

② 編譯源程式:

$ cc -o system system.c

運作源程式

$./system

螢幕顯示:

Running ps with system

PID TTY STAT TIME COMMAND

1 ? S 0:03 init

2 ? SW 0:00 (kflushd)

…….

162 p1 S -bash

710 p1 S ./system

711 p1 R ps -ax

Done.

可以看到在系統程序表中的710和711是我們執行程式時産生的,并且ps處于運作

狀态,而./system處于睡眠狀态。

練習:将上面源程式的第六行改為:

system(“ps –ax &”);

時,執行結構是怎樣的?為什麼?

• exec函數家族

exec函數家族的函數都以exec開頭,且功能類似;它比system函數低級得多。它是通過覆寫自身執行代碼的方法來執行外部指令的。

如:

include <unistd.h>

extern char * *environ;
  
  int execl(const char *path,const char * arg , …, (char *) 0);
  int execlp(const char *file ,const char * arg , …, (char *) 0);
  int execv(const char *path,  char * const  argv[ ]);
           

下面就利用execlp函數編寫一個程式,在程式中調用外部指令ps。

#include <unistd.h>

#include <stdio.h>

int main()

{

printf(“Running ps with execlp \n”);

execlp(“ps”, “ps”, “-ax”,0);

printf(“Done.\n”);

exit(0);

}

以檔案名pexec.c 存盤,并編譯運作:

$./pexec

螢幕顯示:

Running ps with system

PID TTY STAT TIME COMMAND

1 ? S 0:03 init

2 ? SW 0:00 (kflushd)

…….

161 p1 S -bash

811 p1 R ps -ax

Done.

•系統調用 fork

fork是系統提供的建立程序的系統調用,其工作原理是:fork建立一個新的程序,新程序擁有系統配置設定的具有唯一特性的程序号,然後,父程序繼續執行下去(父程序就是調用fork的程序)。新的程序複制了父程序的資料空間(變量),檔案描述符和檔案流;

fork調用将傳回程序号給父程序,若傳回為0,則表明建立程序成功,進入子程序執行;若傳回為-1,則表示出錯,一般是由于程序數超過了系統允許建立的子程序最大限制。

其調用架構為:

#include <sys/types.h>

#include <unistd.h>

main ()

{

pid_t new_pid;

new_pid=fork();

switch (new_pid)

{

case –1 😕/Error

break;

case 0: //come in child

break;

default; //in parent

break;

}

例:

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

int main ()

{

pid_t pid;

char *message;

int n;

printf(“fork program starting \n”);

pid=fork();

switch(pid)

{

case -1 : exit(1);

case 0 : message=“This is the child”;

n=5;

break;

default : message=“This is the parent”;

n=3;

break;

}

for (; n>0; n--)
	   {
	     puts(message);
		 sleep(1);
	    }
exit(0);
           

}

在上面的程式中,我們建立了一個子程序,此時有兩個子程序并發執行,其中子程序輸出一個字元串五次,父程序輸出另一個字元串三次。

将上例以檔案名fork編輯、編譯和運作。結果如下:

$./fork

fork program starting

This is then parent

This is then child

This is then parent

This is then child

This is then parent

This is then child

$ This is then child {注意此時父程序以執行完畢,但子程序為完成}

This is then child

如果對上例感到不了解,可以把列印語句分别放到case 中給變量n 賦數值之後、break之前,并把原來的switch之後的列印語句删除,這樣可能較清晰一些。

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

int main ()

{

pid_t pid;

char *message;

int n;

printf("fork program starting \n");

pid=fork();

switch(pid)
   {
     case -1 : exit(1);
     case 0  : message="This is the child";
	       n=5;
	       for (; n>0; n--)
	        {
                      puts(message);
	          sleep(1);
                      }	 
                    break;
    default  : message="This is the parent";
	       n=3;
               break;
                   for (; n>0; n--)
                      {
                        puts(message);
                    sleep(1);
	           break;
                       }
       }
exit(0);
           

}

注意:子程序一旦被建立,它就脫離父程序獨立運作;在上例中我們看到父程序先于子程序結束,這在實際應用中不利,如主要程式隻有在所有子程序完成後才能中止。為了解決此問題,可使用系統調用wait,來讓父程序處于等待狀态,直到子程序結束才完成。有關細節請同學們參考其他書籍完成。

⑷建立新的線程

程序可以提高程式的并發執行的程度,但仍然存在如下兩個問題:

•fork是昂貴的,記憶體映像要從父程序複制到子程序。即使現在實作的寫時複制技術(copy – on – write)也隻能略微減輕這種負擔。

•fork 建立子程序後,需要用程序通訊的方法在父、子程序間傳遞資訊。Fork之前的資訊時容易傳遞的,因為子程序開始的時候複制了父程序的資料空間;但是從子程序傳回資訊給父程序是很麻煩的一件事。

線程機制可以解決上面的兩個問題。

線程由稱為輕程序,線程的開銷比程序的小得多,是以建立線程也快得多,通常要比建立程序快10-100倍。一個程序中的所有線程共享全局的記憶體(變量),這使得線程的資訊共享較容易。但是其簡易性也帶來了另一個棘手的問題,即同步。

線程不僅共享全局變量而且也共享:

•程序指令

•大多數資料

•打開的檔案

•信号處理程式和信号處置

•目前目錄

•使用者ID群組ID

但每個線程都有屬于自己的:

•線程ID

•寄存器集合(包括程式計數器和棧指針)

•棧(用于存放局部變量和傳回位址)

•erron(錯誤代碼)

•信号掩碼

•優先級

linux系統提供了pthread函數庫來編寫線程的程式,下面我們簡單的加以介紹:
           

函數庫pthread中的函數都以pthead_開頭。

#include <pthread.h>

int pthread_creat(pthread_t *tid,const pthread_attr_t *attr,void *(*func(void 8),void *arg);   
           

int pthread_join(pthread_t tid,void * *status);

int pthread_exit(void *status);

分别解釋如下:

• 函數pthread_create用來建立一個線程。一個程序中的每個線程都由一個線程ID來辨別,其資料類型是pthread_t(通常是unsigned int)。如果函數成功執行将傳回一個線程的ID ,而pthread_create的第一個參數就是用來存儲該值的。

每個線程都有很多屬性:優先級、起始棧大小、是否作為守護線程等。而pthread_create函數的第二個參數就是用來設定這些屬性的,如果為空,則使用預設值。

最後,當建立一個線程的時候,我們要給它指定一個将要執行的函數。這個函數由第三個參數給出,而此函數的參數由第四個參數指定。

線程建立成功傳回0,出錯傳回非零值賦給errno。

• 函數pthread_join用來等待一個線程的終止。如果把線程和程序加以對比,那麼pthread_create類似于forkj,而pthread_join相當于waitpid。

• 函數pthread_exit用于終止線程,如果線程未脫離,其線程ID和狀态資訊将一直保留到調用程序中的某個其他線程調用pthread_join。有兩種方法終止線程:

啟動線程的函數(pthread_create 的第三個參數)傳回。

如果程序的main函數傳回或者任何一個線程調用了exit,程序将終止,自然所有的線程也随之終止。

下面的例子是一個簡單的線程建立的程式,更進一步的内容可參考其他書籍。

#include <stddef.h>

#include <stdio.h>

#include <unistd.h>

#include <pthread.h>

void *process(void *arg)

{

int i;

fprintf(stderr,“Starting process %s \n”,(char )agr);

for (i=0;i<10000;i++)

{

write(1,(char) arg,1);

}

return NULL;

}

int main()

{

int retcode;

pthread_t th_a,th_b;

void * retval;

retcode=pthread_create(&th_a,NULL,process,“a”);

if (retcode!=0) fprintf(stderr,“creat a failed %d \n”,retcode);

retcode=pthread_create(&th_a,NULL,process,“b”);

if (retcode!=0) fprintf(stderr,“creat b failed %d \n”,retcode);

retcode=pthread_join(th_a,&retval);

if (retcode!=0) fprintf(stderr,“join a failed %d \n”,retcode);

retcode=pthread_join(th_b,&retval);

if (retcode!=0) fprintf(stderr,“join a failed %d \n”,retcode);

return 0;

}

上面的程式很簡單,它先輸出10000個a,然後輸出10000個b。

注意:有些發行版的linux不帶有pthread庫,需要自己安裝,方法為:把頭檔案複制到/usr/include 目錄下,而将libpthread.a複制到/usr/lib目錄下,共享庫libpthread.so.x.xx複制到/usr/lib目錄下。再來編譯運作。

$ cc –o pthread pthread.c –lpthread

$./pthread

螢幕顯示:

aaaa…

….bbb

bbb…

….

bb…

$

二、實習題

在LINUX環境下建立、修改、連接配接和執行上述例序;列印源程式和執行結果;最後撤消所有檔案。

程式設計執行下列指令序列。

cp /etc/fstab

sort fstab –o myfstab

cat myfstab

參考代碼如下:

main()

{

if (! fork) )

{

execlp(“cp”,”cp”,”/ect/fstab”,”.”,0);

printf(“error executing cp\n”);

exit(1);

}

wait(0);

if (! fork) )

{

execlp(“sort”,”sort”,”fstab”,”-o”,”myfstab”,0);

printf(“error executing sort\n”);

exit(1);

}

wait(0);
 execlp(“cat”,”cat”,”myfstab”,0);
 printf(“error executing cat\n”);
 exit(1);
           

}

注意:此出沒有檢查fork的傳回值,請同學們完成。

【實驗報告】

本實驗不寫實驗報告

繼續閱讀