1. 程式人生 > >linux下C程式設計詳解

linux下C程式設計詳解

linux作業系統下

c語言程式設計


整理編寫:007xiong
原文:Hoyt等


(一)目錄介紹

1)Linux程式設計入門--基礎知識2)Linux程式設計入門--程序介紹3)Linux程式設計入門--檔案操作4)Linux程式設計入門--時間概念5)Linux程式設計入門--訊號處理6)Linux程式設計入門--訊息管理7)Linux程式設計入門--執行緒操作8)Linux程式設計入門--網路程式設計9)LinuxC開發工具介紹

(二)具體內容

1)Linux程式設計入門--基礎知識

Linux下C語言程式設計基礎知識
前言:
這篇文章介紹在LINUX下進行C語言程式設計所需要的基礎知識.在這篇文章當中,我們將
會學到以下內容:
源程式編譯
Makefile的編寫
程式庫的連結
程式的除錯
標頭檔案和系統求助
----------------------------------------------------------------------------
----
1.源程式的編譯
在Linux下面,如果要編譯一個C語言源程式,我們要使用GNU的gcc編譯器. 下面我們
以一個例項來說明如何使用gcc編譯器.
假設我們有下面一個非常簡單的源程式(hello.c):
int main(int argc,char **argv)
{
printf("Hello Linux/n");
}
要編譯這個程式,我們只要在命令列下執行:
gcc -o hello hello.c
gcc 編譯器就會為我們生成一個hello的可執行檔案.執行./hello就可以看到程式的輸出
結果了.命令列中 gcc表示我們是用gcc來編譯我們的源程式,-o 選項表示我們要求編譯
器給我們輸出的可執行檔名為hello 而hello.c是我們的源程式檔案.
gcc編譯器有許多選項,一般來說我們只要知道其中的幾個就夠了. -o選項我們已經知道
了,表示我們要求輸出的可執行檔名. -c選項表示我們只要求編譯器輸出目的碼,而
不必要輸出可執行檔案. -g選項表示我們要求編譯器在編譯的時候提供我們以後對程式
進行除錯的資訊.
知道了這三個選項,我們就可以編譯我們自己所寫的簡單的源程式了,如果你想要知道更
多的選項,可以檢視gcc的幫助文件,那裡有著許多對其它選項的詳細說明.
2.Makefile的編寫
假設我們有下面這樣的一個程式,原始碼如下:
/* main.c */
#include "mytool1.h"
#include "mytool2.h"
int main(int argc,char **argv)
{
mytool1_print("hello");
mytool2_print("hello");
}
/* mytool1.h */
#ifndef _MYTOOL_1_H
#define _MYTOOL_1_H
void mytool1_print(char *print_str);
#endif
/* mytool1.c */
#include "mytool1.h"
void mytool1_print(char *print_str)
{
printf("This is mytool1 print %s/n",print_str);
}
/* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H
void mytool2_print(char *print_str);
#endif
/* mytool2.c */
#include "mytool2.h"
void mytool2_print(char *print_str)
{
printf("This is mytool2 print %s/n",print_str);
}
當然由於這個程式是很短的我們可以這樣來編譯
gcc -c main.c
gcc -c mytool1.c
gcc -c mytool2.c
gcc -o main main.o mytool1.o mytool2.o
這樣的話我們也可以產生main程式,而且也不時很麻煩.但是如果我們考慮一下如果有一
天我們修改了其中的一個檔案(比如說mytool1.c)那麼我們難道還要重新輸入上面的命令
?也許你會說,這個很容易解決啊,我寫一個SHELL指令碼,讓她幫我去完成不就可以了.是的
對於這個程式來說,是可以起到作用的.但是當我們把事情想的更復雜一點,如果我們的程
序有幾百個源程式的時候,難道也要編譯器重新一個一個的去編譯?
為此,聰明的程式設計師們想出了一個很好的工具來做這件事情,這就是make.我們只要執行以
下make,就可以把上面的問題解決掉.在我們執行make之前,我們要先編寫一個非常重要的
檔案.--Makefile.對於上面的那個程式來說,可能的一個Makefile的檔案是:
# 這是上面那個程式的Makefile檔案
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
有了這個Makefile檔案,不過我們什麼時候修改了源程式當中的什麼檔案,我們只要執行
make命令,我們的編譯器都只會去編譯和我們修改的檔案有關的檔案,其它的檔案她連理
都不想去理的.
下面我們學習Makefile是如何編寫的.
在Makefile中也#開始的行都是註釋行.Makefile中最重要的是描述檔案的依賴關係的說
明.一般的格式是:
target: components
TAB rule
第一行表示的是依賴關係.第二行是規則.
比如說我們上面的那個Makefile檔案的第二行
main:main.o mytool1.o mytool2.o
表示我們的目標(target)main的依賴物件(components)是main.o mytool1.o mytool2.o
當倚賴的物件在目標修改後修改的話,就要去執行規則一行所指定的命令.就象我們的上
面那個Makefile第三行所說的一樣要執行 gcc -o main main.o mytool1.o mytool2.o
注意規則一行中的TAB表示那裡是一個TAB鍵
Makefile有三個非常有用的變數.分別是

[email protected],$^,$<代表的意義分別是:
[email protected]目標檔案,$^--所有的依賴檔案,$<--第一個依賴檔案.
如果我們使用上面三個變數,那麼我們可以簡化我們的Makefile檔案為:
# 這是簡化後的Makefile
main:main.o mytool1.o mytool2.o
gcc -o [email protected] $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
mytool1.o:mytool1.c mytool1.h
gcc -c $<
mytool2.o:mytool2.c mytool2.h
gcc -c $<
經過簡化後我們的Makefile是簡單了一點,不過人們有時候還想簡單一點.這裡我們學習
一個Makefile的預設規則
..c.o:
gcc -c $<
這個規則表示所有的 .o檔案都是依賴與相應的.c檔案的.例如mytool.o依賴於mytool.c
這樣Makefile還可以變為:
# 這是再一次簡化後的Makefile
main:main.o mytool1.o mytool2.o
gcc -o
[email protected]
$^
..c.o:
gcc -c $<
好了,我們的Makefile 也差不多了,如果想知道更多的關於Makefile規則可以檢視相應的
文件.
3.程式庫的連結
試著編譯下面這個程式
/* temp.c */
#include <math.h>;
int main(int argc,char **argv)
{
double value;
printf("Value:%f/n",value);
}
這個程式相當簡單,但是當我們用 gcc -o temp temp.c 編譯時會出現下面所示的錯誤.

/tmp/cc33Kydu.o: In function `main':
/tmp/cc33Kydu.o(.text+0xe): undefined reference to `log'
collect2: ld returned 1 exit status
出現這個錯誤是因為編譯器找不到log的具體實現.雖然我們包括了正確的標頭檔案,但是我
們在編譯的時候還是要連線確定的庫.在Linux下,為了使用數學函式,我們必須和數學庫
連線,為此我們要加入 -lm 選項. gcc -o temp temp.c -lm這樣才能夠正確的編譯.也許
有人要問,前面我們用printf函式的時候怎麼沒有連線庫呢?是這樣的,對於一些常用的函
數的實現,gcc編譯器會自動去連線一些常用庫,這樣我們就沒有必要自己去指定了. 有時
候我們在編譯程式的時候還要指定庫的路徑,這個時候我們要用到編譯器的 -L選項指定
路徑.比如說我們有一個庫在 /home/hoyt/mylib下,這樣我們編譯的時候還要加上 -L/h
ome/hoyt/mylib.對於一些標準庫來說,我們沒有必要指出路徑.只要它們在起預設庫的路
徑下就可以了.系統的預設庫的路徑/lib /usr/lib /usr/local/lib 在這三個路徑下面
的庫,我們可以不指定路徑.
還有一個問題,有時候我們使用了某個函式,但是我們不知道庫的名字,這個時候怎麼辦呢
?很抱歉,對於這個問題我也不知道答案,我只有一個傻辦法.首先,我到標準庫路徑下面去
找看看有沒有和我用的函式相關的庫,我就這樣找到了執行緒(thread)函式的庫檔案(libp
thread.a). 當然,如果找不到,只有一個笨方法.比如我要找sin這個函式所在的庫. 就只
好用 nm -o /lib/*.so|grep sin>;~/sin 命令,然後看~/sin檔案,到那裡面去找了. 在s
in檔案當中,我會找到這樣的一行libm-2.1.2.so:00009fa0 W sin 這樣我就知道了sin在
libm-2.1.2.so庫裡面,我用 -lm選項就可以了(去掉前面的lib和後面的版本標誌,就剩
下m了所以是 -lm). 如果你知道怎麼找,請趕快告訴我,我回非常感激的.謝謝!
4.程式的除錯
我們編寫的程式不太可能一次性就會成功的,在我們的程式當中,會出現許許多多我
們想不到的錯誤,這個時候我們就要對我們的程式進行除錯了.
最常用的除錯軟體是gdb.如果你想在圖形介面下除錯程式,那麼你現在可以選擇xxgdb.記
得要在編譯的時候加入 -g選項.關於gdb的使用可以看gdb的幫助檔案.由於我沒有用過這
個軟體,所以我也不能夠說出如何使用. 不過我不喜歡用gdb.跟蹤一個程式是很煩的事情
,我一般用在程式當中輸出中間變數的值來除錯程式的.當然你可以選擇自己的辦法,沒有
必要去學別人的.現在有了許多IDE環境,裡面已經自己帶了偵錯程式了.你可以選擇幾個試
一試找出自己喜歡的一個用.
5.標頭檔案和系統求助
有時候我們只知道一個函式的大概形式,不記得確切的表示式,或者是不記得著函式
在那個標頭檔案進行了說明.這個時候我們可以求助系統.
比如說我們想知道fread這個函式的確切形式,我們只要執行 man fread 系統就會輸出著
函式的詳細解釋的.和這個函式所在的標頭檔案<stdio.h>;說明了. 如果我們要write這個函
數的說明,當我們執行man write時,輸出的結果卻不是我們所需要的. 因為我們要的是w
rite這個函式的說明,可是出來的卻是write這個命令的說明.為了得到write的函式說明
我們要用 man 2 write. 2表示我們用的write這個函式是系統呼叫函式,還有一個我們常
用的是3表示函式是C的庫函式.
記住不管什麼時候,man都是我們的最好助手.
------------------------------------------------------------------------
好了,這一章就講這麼多了,有了這些知識我們就可以進入激動人心的Linux下的C程式探
險活動.

2)Linux
程式設計入門--程序介紹

Linux下程序的建立
前言:
這篇文章是用來介紹在Linux下和程序相關的各個概念.我們將會學到:
程序的概念
程序的身份
程序的建立
守護程序的建立
----------------------------------------------------------------------------
----
1。程序的概念
Linux作業系統是面向多使用者的.在同一時間可以有許多使用者向作業系統發出各種命
令.那麼作業系統是怎麼實現多使用者的環境呢? 在現代的作業系統裡面,都有程式和程序
的概念.那麼什麼是程式,什麼是程序呢? 通俗的講程式是一個包含可以執行程式碼的檔案
,是一個靜態的檔案.而程序是一個開始執行但是還沒有結束的程式的例項.就是可執行文
件的具體實現. 一個程式可能有許多程序,而每一個程序又可以有許多子程序.依次迴圈
下去,而產生子孫程序. 當程式被系統呼叫到記憶體以後,系統會給程式分配一定的資源(內
存,裝置等等)然後進行一系列的複雜操作,使程式變成程序以供系統呼叫.在系統裡面只
有程序沒有程式,為了區分各個不同的程序,系統給每一個程序分配了一個ID(就象我們的
身份證)以便識別. 為了充分的利用資源,系統還對程序區分了不同的狀態.將程序分為新
建,執行,阻塞,就緒和完成五個狀態. 新建表示程序正在被建立,執行是程序正在執行,阻
塞是程序正在等待某一個事件發生,就緒是表示系統正在等待CPU來執行命令,而完成表示
程序已經結束了系統正在回收資源. 關於程序五個狀態的詳細解說我們可以看《操作系
統》上面有詳細的解說。
2。程序的標誌
上面我們知道了程序都有一個ID,那麼我們怎麼得到程序的ID呢?系統呼叫getpid可
以得到程序的ID,而getppid可以得到父程序(建立呼叫該函式程序的程序)的ID.
#include <unistd>;
pid_t getpid(void);
pid_t getppid(void);
程序是為程式服務的,而程式是為了使用者服務的.系統為了找到程序的使用者名稱,還為程序和
使用者建立聯絡.這個使用者稱為程序的所有者.相應的每一個使用者也有一個使用者ID.通過系統
呼叫getuid可以得到程序的所有者的ID.由於程序要用到一些資源,而Linux對系統資源是
進行保護的,為了獲取一定資源程序還有一個有效使用者ID.這個ID和系統的資源使用有關
,涉及到程序的許可權. 通過系統呼叫geteuid我們可以得到程序的有效使用者ID. 和使用者ID
相對應程序還有一個組ID和有效組ID系統呼叫getgid和getegid可以分別得到組ID和有效
組ID
#include <unistd>;
#include <sys/types.h>;

uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
git_t getegid(void);
有時候我們還會對使用者的其他資訊感興趣(登入名等等),這個時候我們可以呼叫getpwui
d來得到.
struct passwd {
char *pw_name; /* 登入名稱 */
char *pw_passwd; /* 登入口令 */
uid_t pw_uid; /* 使用者ID */
gid_t pw_gid; /* 使用者組ID */
char *pw_gecos; /* 使用者的真名 */
char *pw_dir; /* 使用者的目錄 */
char *pw_shell; /* 使用者的SHELL */
};
#include <pwd.h>;
#include <sys/types.h>;

struct passwd *getpwuid(uid_t uid);
下面我們學習一個例項來實踐一下上面我們所學習的幾個函式:
#include <unistd.h>;
#include <pwd.h>;
#include <sys/types.h>;
#include <stdio.h>;
int main(int argc,char **argv)
{
pid_t my_pid,parent_pid;
uid_t my_uid,my_euid;
gid_t my_gid,my_egid;
struct passwd *my_info;
my_pid=getpid();
parent_pid=getppid();
my_uid=getuid();
my_euid=geteuid();
my_gid=getgid();
my_egid=getegid();
my_info=getpwuid(my_uid);
printf("Process ID:%ld/n",my_pid);
printf("Parent ID:%ld/n",parent_pid);
printf("User ID:%ld/n",my_uid);
printf("Effective User ID:%ld/n",my_euid);
printf("Group ID:%ld/n",my_gid);
printf("Effective Group ID:%ld/n",my_egid):
if(my_info)
{
printf("My Login Name:%s/n" ,my_info->;pw_name);
printf("My Password :%s/n" ,my_info->;pw_passwd);
printf("My User ID :%ld/n",my_info->;pw_uid);
printf("My Group ID :%ld/n",my_info->;pw_gid);
printf("My Real Name:%s/n" ,my_info->;pw_gecos);
printf("My Home Dir :%s/n", my_info->;pw_dir);
printf("My Work Shell:%s/n", my_info->;pw_shell);
}
}
3。程序的建立
建立一個程序的系統呼叫很簡單.我們只要呼叫fork函式就可以了.
#include <unistd.h>;

pid_t fork();
當一個程序呼叫了fork以後,系統會建立一個子程序.這個子程序和父程序不同的地方只
有他的程序ID和父程序ID,其他的都是一樣.就象符程序克隆(clone)自己一樣.當然建立
兩個一模一樣的程序是沒有意義的.為了區分父程序和子程序,我們必須跟蹤fork的返回
值. 當fork掉用失敗的時候(記憶體不足或者是使用者的最大程序數已到)fork返回-1,否則f
ork的返回值有重要的作用.對於父程序fork返回子程序的ID,而對於fork子程序返回0.我
們就是根據這個返回值來區分父子程序的. 父程序為什麼要建立子程序呢?前面我們已經
說過了Linux是一個多使用者作業系統,在同一時間會有許多的使用者在爭奪系統的資源.有時
程序為了早一點完成任務就建立子程序來爭奪資源. 一旦子程序被建立,父子程序一起從
fork處繼續執行,相互競爭系統的資源.有時候我們希望子程序繼續執行,而父程序阻塞直
到子程序完成任務.這個時候我們可以呼叫wait或者waitpid系統呼叫.
#include <sys/types.h>;
#include <sys/wait.h>;

pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid,int *stat_loc,int options);
wait系統呼叫會使父程序阻塞直到一個子程序結束或者是父程序接受到了一個訊號.如果
沒有父程序沒有子程序或者他的子程序已經結束了wait回立即返回.成功時(因一個子進
程結束)wait將返回子程序的ID,否則返回-1,並設定全域性變數errno.stat_loc是子程序的
退出狀態.子程序呼叫exit,_exit 或者是return來設定這個值. 為了得到這個值Linux定
義了幾個巨集來測試這個返回值.
WIFEXITED:判斷子程序退出值是非0
WEXITSTATUS:判斷子程序的退出值(當子程序退出時非0).
WIFSIGNALED:子程序由於有沒有獲得的訊號而退出.
WTERMSIG:子程序沒有獲得的訊號號(在WIFSIGNALED為真時才有意義).
waitpid等待指定的子程序直到子程序返回.如果pid為正值則等待指定的程序(pid).如果
為0則等待任何一個組ID和呼叫者的組ID相同的程序.為-1時等同於wait呼叫.小於-1時等
待任何一個組ID等於pid絕對值的程序. stat_loc和wait的意義一樣. options可以決定
父程序的狀態.可以取兩個值 WNOHANG:父程序立即返回當沒有子程序存在時. WUNTACHE
D:當子程序結束時waitpid返回,但是子程序的退出狀態不可得到.
父程序建立子程序後,子程序一般要執行不同的程式.為了呼叫系統程式,我們可以使用系
統呼叫exec族呼叫.exec族呼叫有著5個函式.
#include <unistd.h>;
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const char *arg,...);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]):
exec族呼叫可以執行給定程式.關於exec族呼叫的詳細解說可以參考系統手冊(man exec
l). 下面我們來學習一個例項.注意編譯的時候要加 -lm以便連線數學函式庫.
#include <unistd.h>;
#include <sys/types.h>;
#include <sys/wait.h>;
#include <stdio.h>;
#include <errno.h>;
#include <math.h>;
void main(void)
{
pid_t child;
int status;
printf("This will demostrate how to get child status/n");
if((child=fork())==-1)
{
printf("Fork Error :%s/n",strerror(errno));
exit(1);
}
else if(child==0)
{
int i;
printf("I am the child:%ld/n",getpid());
for(i=0;i<1000000;i++) sin(i);
i=5;
printf("I exit with %d/n",i);
exit(i);
}
while(((child=wait(&status))==-1)&(errno==EINTR));
if(child==-1)
printf("Wait Error:%s/n",strerror(errno));
else if(!status)
printf("Child %ld terminated normally return status is zero/n",
child);
else if(WIFEXITED(status))
printf("Child %ld terminated normally return status is %d/n",
child,WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("Child %ld terminated due to signal %d znot caught/n",
child,WTERMSIG(status));
}
strerror函式會返回一個指定的錯誤號的錯誤資訊的字串.
4。守護程序的建立
如果你在DOS時代編寫過程式,那麼你也許知道在DOS下為了編寫一個常駐記憶體的程式
我們要編寫多少程式碼了.相反如果在Linux下編寫一個"常駐記憶體"的程式卻是很容易的.我
們只要幾行程式碼就可以做到. 實際上由於Linux是多工作業系統,我們就是不編寫程式碼
也可以把一個程式放到後臺去執行的.我們只要在命令後面加上&符號SHELL就會把我們的
程式放到後臺去執行的. 這裡我們"開發"一個後臺檢查郵件的程式.這個程式每個一個指
定的時間回去檢查我們的郵箱,如果發現我們有郵件了,會不斷的報警(通過機箱上的小喇
叭來發出聲音). 後面有這個函式的加強版本加強版本
後臺程序的建立思想: 首先父程序建立一個子程序.然後子程序殺死父程序(是不是很無
情?). 訊號處理所有的工作由子程序來處理.
#include <unistd.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <stdio.h>;
#include <errno.h>;
#include <fcntl.h>;
#include <signal.h>;
/* Linux 的默任個人的郵箱地址是 /var/spool/mail/使用者的登入名 */
#define MAIL "/var/spool/mail/hoyt"
/* 睡眠10秒鐘 */

#define SLEEP_TIME 10
main(void)
{
pid_t child;
if((child=fork())==-1)
{
printf("Fork Error:%s/n",strerror(errno));
exit(1);
}
else if(child>;0)
while(1);
if(kill(getppid(),SIGTERM)==-1)
{
printf("Kill Parent Error:%s/n",strerror(errno));
exit(1);
}
{
int mailfd;
while(1)
{
if((mailfd=open(MAIL,O_RDONLY))!=-1)
{
fprintf(stderr,"%s","/007");
close(mailfd);
}
sleep(SLEEP_TIME);
}
}
}
你可以在預設的路徑下建立你的郵箱檔案,然後測試一下這個程式.當然這個程式還有很
多地方要改善的.我們後面會對這個小程式改善的,再看我的改善之前你可以嘗試自己改
善一下.比如讓使用者指定郵相的路徑和睡眠時間等等.相信自己可以做到的.動手吧,勇敢
的探險者.
好了程序一節的內容我們就先學到這裡了.程序是一個非常重要的概念,許多的程式都會
用子程序.建立一個子程序是每一個程式設計師的基本要求!

3)Linux程式設計入門--檔案操作
Linux下檔案的操作
前言:
我們在這一節將要討論linux下檔案操作的各個函式.
檔案的建立和讀寫
檔案的各個屬性
目錄檔案的操作
管道檔案
----------------------------------------------------------------------------
----
1。檔案的建立和讀寫
我假設你已經知道了標準級的檔案操作的各個函式(fopen,fread,fwrite等等).當然
如果你不清楚的話也不要著急.我們討論的系統級的檔案操作實際上是為標準級檔案操作
服務的.
當我們需要開啟一個檔案進行讀寫操作的時候,我們可以使用系統呼叫函式open.使用完
成以後我們呼叫另外一個close函式進行關閉操作.
#include <fcntl.h>;
#include <unistd.h>;
#include <sys/types.h>;
#include <sys/stat.h>;

int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);
int close(int fd);
open函式有兩個形式.其中pathname是我們要開啟的檔名(包含路徑名稱,預設是認為在
當前路徑下面).flags可以去下面的一個值或者是幾個值的組合.
O_RDONLY:以只讀的方式開啟檔案.
O_WRONLY:以只寫的方式開啟檔案.
O_RDWR:以讀寫的方式開啟檔案.
O_APPEND:以追加的方式開啟檔案.
O_CREAT:建立一個檔案.
O_EXEC:如果使用了O_CREAT而且檔案已經存在,就會發生一個錯誤.
O_NOBLOCK:以非阻塞的方式開啟一個檔案.
O_TRUNC:如果檔案已經存在,則刪除檔案的內容.
前面三個標誌只能使用任意的一個.如果使用了O_CREATE標誌,那麼我們要使用open的第
二種形式.還要指定mode標誌,用來表示檔案的訪問許可權.mode可以是以下情況的組合.
-----------------------------------------------------------------
S_IRUSR 使用者可以讀 S_IWUSR 使用者可以寫
S_IXUSR 使用者可以執行 S_IRWXU 使用者可以讀寫執行
-----------------------------------------------------------------
S_IRGRP 組可以讀 S_IWGRP 組可以寫
S_IXGRP 組可以執行 S_IRWXG 組可以讀寫執行
-----------------------------------------------------------------
S_IROTH 其他人可以讀 S_IWOTH 其他人可以寫
S_IXOTH 其他人可以執行 S_IRWXO 其他人可以讀寫執行
-----------------------------------------------------------------
S_ISUID 設定使用者執行ID S_ISGID 設定組的執行ID
-----------------------------------------------------------------
我們也可以用數字來代表各個位的標誌.Linux總共用5個數字來表示檔案的各種許可權.
00000.第一位表示設定使用者ID.第二位表示設定組ID,第三位表示使用者自己的許可權位,第四
位表示組的許可權,最後一位表示其他人的許可權.
每個數字可以取1(執行許可權),2(寫許可權),4(讀許可權),0(什麼也沒有)或者是這幾個值的和
..
比如我們要建立一個使用者讀寫執行,組沒有許可權,其他人讀執行的檔案.設定使用者ID位那麼
我們可以使用的模式是--1(設定使用者ID)0(組沒有設定)7(1+2+4)0(沒有許可權,使用預設)
5(1+4)即10705:
open("temp",O_CREAT,10705);
如果我們開啟檔案成功,open會返回一個檔案描述符.我們以後對檔案的所有操作就可以
對這個檔案描述符進行操作了.
當我們操作完成以後,我們要關閉檔案了,只要呼叫close就可以了,其中fd是我們要關閉
的檔案描述符.
檔案打開了以後,我們就要對檔案進行讀寫了.我們可以呼叫函式read和write進行檔案的
讀寫.
#include <unistd.h>;
ssize_t read(int fd, void *buffer,size_t count);
ssize_t write(int fd, const void *buffer,size_t count);
fd是我們要進行讀寫操作的檔案描述符,buffer是我們要寫入檔案內容或讀出檔案內容的
記憶體地址.count是我們要讀寫的位元組數.
對於普通的檔案read從指定的檔案(fd)中讀取count位元組到buffer緩衝區中(記住我們必
須提供一個足夠大的緩衝區),同時返回count.
如果read讀到了檔案的結尾或者被一個訊號所中斷,返回值會小於count.如果是由訊號中
斷引起返回,而且沒有返回資料,read會返回-1,且設定errno為EINTR.當程式讀到了檔案
結尾的時候,read會返回0.
write從buffer中寫count位元組到檔案fd中,成功時返回實際所寫的位元組數.
下面我們學習一個例項,這個例項用來拷貝檔案.
#include <unistd.h>;
#include <fcntl.h>;
#include <stdio.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <errno.h>;
#include <string.h>;
#define BUFFER_SIZE 1024
int main(int argc,char **argv)
{
int from_fd,to_fd;
int bytes_read,bytes_write;
char buffer[BUFFER_SIZE];
char *ptr;
if(argc!=3)
{
fprintf(stderr,"Usage:%s fromfile tofile/n/a",argv[0]);
exit(1);
}
/* 開啟原始檔 */
if((from_fd=open(argv[1],O_RDONLY))==-1)
{
fprintf(stderr,"Open %s Error:%s/n",argv[1],strerror(errno));
exit(1);
}
/* 建立目的檔案 */
if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
{
fprintf(stderr,"Open %s Error:%s/n",argv[2],strerror(errno));
exit(1);
}
/* 以下程式碼是一個經典的拷貝檔案的程式碼 */
while(bytes_read=read(from_fd,buffer,BUFFER_SIZE))
{
/* 一個致命的錯誤發生了 */
if((bytes_read==-1)&&(errno!=EINTR)) break;
else if(bytes_read>;0)
{
ptr=buffer;
while(bytes_write=write(to_fd,ptr,bytes_read))
{
/* 一個致命錯誤發生了 */
if((bytes_write==-1)&&(errno!=EINTR))break;
/* 寫完了所有讀的位元組 */
else if(bytes_write==bytes_read) break;
/* 只寫了一部分,繼續寫 */
else if(bytes_write>;0)
{
ptr+=bytes_write;
bytes_read-=bytes_write;
}
}
/* 寫的時候發生的致命錯誤 */
if(bytes_write==-1)break;
}
}
close(from_fd);
close(to_fd);
exit(0);
}
2。檔案的各個屬性
檔案具有各種各樣的屬性,除了我們上面所知道的檔案許可權以外,檔案還有建立時間
,大小等等屬性.
有時侯我們要判斷檔案是否可以進行某種操作(讀,寫等等).這個時候我們可以使用acce
ss函式.
#include <unistd.h>;

int access(const char *pathname,int mode);
pathname:是檔名稱,mode是我們要判斷的屬性.可以取以下值或者是他們的組合.
R_OK檔案可以讀,W_OK檔案可以寫,X_OK檔案可以執行,F_OK檔案存在.當我們測試成功時
,函式返回0,否則如果有一個條件不符時,返回-1.
如果我們要獲得檔案的其他屬性,我們可以使用函式stat或者fstat.
#include <sys/stat.h>;
#include <unistd.h>;
int stat(const char *file_name,struct stat *buf);
int fstat(int filedes,struct stat *buf);
struct stat {
dev_t st_dev; /* 裝置 */
ino_t st_ino; /* 節點 */
mode_t st_mode; /* 模式 */
nlink_t st_nlink; /* 硬連線 */
uid_t st_uid; /* 使用者ID */
gid_t st_gid; /* 組ID */
dev_t st_rdev; /* 裝置型別 */
off_t st_off; /* 檔案位元組數 */
unsigned long st_blksize; /* 塊大小 */
unsigned long st_blocks; /* 塊數 */
time_t st_atime; /* 最後一次訪問時間 */
time_t st_mtime; /* 最後一次修改時間 */
time_t st_ctime; /* 最後一次改變時間(指屬性) */
};
stat用來判斷沒有開啟的檔案,而fstat用來判斷開啟的檔案.我們使用最多的屬性是st_
mode.通過著屬性我們可以判斷給定的檔案是一個普通檔案還是一個目錄,連線等等.可以
使用下面幾個巨集來判斷.
S_ISLNK(st_mode):是否是一個連線.S_ISREG是否是一個常規檔案.S_ISDIR是否是一個目
錄S_ISCHR是否是一個字元裝置.S_ISBLK是否是一個塊裝置S_ISFIFO是否 是一個FIFO文
件.S_ISSOCK是否是一個SOCKET檔案. 我們會在下面說明如何使用這幾個巨集的.
3。目錄檔案的操作
在我們編寫程式的時候,有時候會要得到我們當前的工作路徑。C庫函式提供了get
cwd來解決這個問題。
#include <unistd.h>;

char *getcwd(char *buffer,size_t size);
我們提供一個size大小的buffer,getcwd會把我們當前的路徑考到buffer中.如果buffer
太小,函式會返回-1和一個錯誤號.
Linux提供了大量的目錄操作函式,我們學習幾個比較簡單和常用的函式.
#include <dirent.h>;
#include <unistd.h>;
#include <fcntl.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
int mkdir(const char *path,mode_t mode);
DIR *opendir(const char *path);
struct dirent *readdir(DIR *dir);
void rewinddir(DIR *dir);
off_t telldir(DIR *dir);
void seekdir(DIR *dir,off_t off);
int closedir(DIR *dir);
struct dirent {
long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[NAME_MAX+1]; /* 檔名稱 */
mkdir很容易就是我們建立一個目錄,opendir開啟一個目錄為以後讀做準備.readdir讀一
個開啟的目錄.rewinddir是用來重讀目錄的和我們學的rewind函式一樣.closedir是關閉
一個目錄.telldir和seekdir類似與ftee和fseek函式.
下面我們開發一個小程式,這個程式有一個引數.如果這個引數是一個檔名,我們輸出這
個檔案的大小和最後修改的時間,如果是一個目錄我們輸出這個目錄下所有檔案的大小和
修改時間.
#include <unistd.h>;
#include <stdio.h>;
#include <errno.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <dirent.h>;
#include <time.h>;
static int get_file_size_time(const char *filename)
{
struct stat statbuf;
if(stat(filename,&statbuf)==-1)
{
printf("Get stat on %s Error:%s/n",
filename,strerror(errno));
return(-1);
}
if(S_ISDIR(statbuf.st_mode))return(1);
if(S_ISREG(statbuf.st_mode))
printf("%s size:%ld bytes/tmodified at %s",
filename,statbuf.st_size,ctime(&statbuf.st_mtime));

return(0);
}
int main(int argc,char **argv)
{
DIR *dirp;
struct dirent *direntp;
int stats;
if(argc!=2)
{
printf("Usage:%s filename/n/a",argv[0]);
exit(1);
}
if(((stats=get_file_size_time(argv[1]))==0)||(stats==-1))exit(1);
if((dirp=opendir(argv[1]))==NULL)
{
printf("Open Directory %s Error:%s/n",
argv[1],strerror(errno));
exit(1);
}
while((direntp=readdir(dirp))!=NULL)
if(get_file_size_time(direntp-<d_name)==-1)break;
closedir(dirp);
exit(1);
}
4。管道檔案
Linux提供了許多的過濾和重定向程式,比如more cat
等等.還提供了< >; | <<等等重定向操作符.在這些過濾和重 定向程式當中,都用到了管
道這種特殊的檔案.系統呼叫pipe可以建立一個管道.
#include<unistd.h>;

int pipe(int fildes[2]);
pipe呼叫可以建立一個管道(通訊緩衝區).當呼叫成功時,我們可以訪問檔案描述符fild
es[0],fildes[1].其中fildes[0]是用來讀的檔案描述符,而fildes[1]是用來寫的檔案描
述符.
在實際使用中我們是通過建立一個子程序,然後一個程序寫,一個程序讀來使用的.
關於程序通訊的詳細情況請檢視程序通訊
#include <stdio.h>;
#include <stdlib.h>;
#include <unistd.h>;
#include <string.h>;
#include <errno.h>;
#include <sys/types.h>;
#include <sys/wait.h>;
#define BUFFER 255
int main(int argc,char **argv)
{
char buffer[BUFFER+1];
int fd[2];
if(argc!=2)
{
fprintf(stderr,"Usage:%s string/n/a",argv[0]);
exit(1);
}
if(pipe(fd)!=0)
{
fprintf(stderr,"Pipe Error:%s/n/a",strerror(errno));
exit(1);
}
if(fork()==0)
{
close(fd[0]);
printf("Child[%d] Write to pipe/n/a",getpid());
snprintf(buffer,BUFFER,"%s",argv[1]);
write(fd[1],buffer,strlen(buffer));
printf("Child[%d] Quit/n/a",getpid());
exit(0);
}
else
{
close(fd[1]);
printf("Parent[%d] Read from pipe/n/a",getpid());
memset(buffer,'/0',BUFFER+1);
read(fd[0],buffer,BUFFER);
printf("Parent[%d] Read:%s/n",getpid(),buffer);
exit(1);
}
}
為了實現重定向操作,我們需要呼叫另外一個函式dup2.
#include <unistd.h>;

int dup2(int oldfd,int newfd);
dup2將用oldfd檔案描述符來代替newfd檔案描述符,同時關閉newfd檔案描述符.也就是說
,
所有向newfd操作都轉到oldfd上面.下面我們學習一個例子,這個例子將標準輸出重定向
到一個檔案.
#include <unistd.h>;
#include <stdio.h>;
#include <errno.h>;
#include <fcntl.h>;
#include <string.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#define BUFFER_SIZE 1024
int main(int argc,char **argv)
{
int fd;
char buffer[BUFFER_SIZE];
if(argc!=2)
{
fprintf(stderr,"Usage:%s outfilename/n/a",argv[0]);
exit(1);
}
if((fd=open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR))==-1)
{
fprintf(stderr,"Open %s Error:%s/n/a",argv[1],strerror(errno));
exit(1);
}
if(dup2(fd,STDOUT_FILENO)==-1)
{
fprintf(stderr,"Redirect Standard Out Error:%s/n/a",strerror(errno));
exit(1);
}
fprintf(stderr,"Now,please input string");
fprintf(stderr,"(To quit use CTRL+D)/n");
while(1)
{
fgets(buffer,BUFFER_SIZE,stdin);
if(feof(stdin))break;
write(STDOUT_FILENO,buffer,strlen(buffer));
}
exit(0);
}
好了,檔案一章我們就暫時先討論到這裡,學習好了檔案的操作我們其實已經可以寫出一
些比較有用的程式了.我們可以編寫一個實現例如dir,mkdir,cp,mv等等常用的檔案操作
命令了.
想不想自己寫幾個試一試呢?

4)程式設計入門--時間概念
前言:Linux下的時間概念
這一章我們學習Linux的時間表示和計算函式
時間的表示
時間的測量
計時器的使用
1。時間表示 在程式當中,我們經常要輸出系統當前的時間,比如我們使用date命令
的輸出結果.這個時候我們可以使用下面兩個函式
#include <time.h>;

time_t time(time_t *tloc);
char *ctime(const time_t *clock);
time函式返回從1970年1月1日0點以來的秒數.儲存在time_t結構之中.不過這個函式的返
回值對於我們來說沒有什麼實際意義.這個時候我們使用第二個函式將秒數轉化為字串
.. 這個函式的返回型別是固定的:一個可能值為. Thu Dec 7 14:58:59 2000 這個字串
的長度是固定的為26
2。時間的測量 有時候我們要計算程式執行的時間.比如我們要對演算法進行時間分析
..這個時候可以使用下面這個函式.
#include <sys/time.h>;

int gettimeofday(struct timeval *tv,struct timezone *tz);
strut timeval {
long tv_sec; /* 秒數 */
long tv_usec; /* 微秒數 */
};
gettimeofday將時間儲存在結構tv之中.tz一般我們使用NULL來代替.
#include <sys/time.h<
#include <stdio.h<
#include <math.h<
void function()
{
unsigned int i,j;
double y;
for(i=0;i<1000;i++)
for(j=0;j<1000;j++)
y=sin((double)i);
}
main()
{
struct timeval tpstart,tpend;
float timeuse;
gettimeofday(&tpstart,NULL);
function();
gettimeofday(&tpend,NULL);
timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+
tpend.tv_usec-tpstart.tv_usec;
timeuse/=1000000;
printf("Used Time:%f/n",timeuse);
exit(0);
}
這個程式輸出函式的執行時間,我們可以使用這個來進行系統性能的測試,或者是函式算
法的效率分析.在我機器上的一個輸出結果是: Used Time:0.556070
3。計時器的使用 Linux作業系統為每一個程序提供了3個內部間隔計時器.
ITIMER_REAL:減少實際時間.到時的時候發出SIGALRM訊號.
ITIMER_VIRTUAL:減少有效時間(程序執行的時間).產生SIGVTALRM訊號.
ITIMER_PROF:減少程序的有效時間和系統時間(為程序排程用的時間).這個經常和上面一
個使用用來計算系統核心時間和使用者時間.產生SIGPROF訊號.
具體的操作函式是:
#include <sys/time.h>;
int getitimer(int which,struct itimerval *value);
int setitimer(int which,struct itimerval *newval,
struct itimerval *oldval);
struct itimerval {
struct timeval it_interval;
struct timeval it_value;
}
getitimer函式得到間隔計時器的時間值.儲存在value中 setitimer函式設定間隔計時器
的時間值為newval.並將舊值儲存在oldval中. which表示使用三個計時器中的哪一個.
itimerval結構中的it_value是減少的時間,當這個值為0的時候就發出相應的訊號了. 然
後設置為it_interval值.
#include <sys/time.h>;
#include <stdio.h>;
#include <unistd.h>;
#include <signal.h>;
#include <string.h>;
#define PROMPT "時間已經過去了兩秒鐘/n/a"
char *prompt=PROMPT;
unsigned int len;
void prompt_info(int signo)
{
write(STDERR_FILENO,prompt,len);
}
void init_sigaction(void)
{
struct sigaction act;
act.sa_handler=prompt_info;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGPROF,&act,NULL);
}
void init_time()
{
struct itimerval value;
value.it_value.tv_sec=2;
value.it_value.tv_usec=0;
value.it_interval=value.it_value;
setitimer(ITIMER_PROF,&value,NULL);
}
int main()
{
len=strlen(prompt);
init_sigaction();
init_time();
while(1);
exit(0);
}
這個程式每執行兩秒中之後會輸出一個提示.

5)Linux程式設計入門--訊號處理
Linux下的訊號事件
前言:這一章我們討論一下Linux下的訊號處理函式.
Linux下的訊號處理函式:
訊號的產生
訊號的處理
其它訊號函式
一個例項
1。訊號的產生
Linux下的訊號可以類比於DOS下的INT或者是Windows下的事件.在有一個訊號發生時
候相信的訊號就會發送給相應的程序.在Linux下的訊號有以下幾個. 我們使用 kill -l
命令可以得到以下的輸出結果:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR
關於這些訊號的詳細解釋請檢視man 7 signal的輸出結果. 訊號事件的發生有兩個來源
:一個是硬體的原因(比如我們按下了鍵盤),一個是軟體的原因(比如我們使用系統函式或
者是命令發出訊號). 最常用的四個發出訊號的系統函式是kill, raise, alarm和setit
imer函式. setitimer函式我們在計時器的使用 那一章再學習.
#include <sys/types.h>;
#include <signal.h>;
#include <unistd.h>;
int kill(pid_t pid,int sig);
int raise(int sig);
unisigned int alarm(unsigned int seconds);
kill系統呼叫負責向程序傳送訊號sig.
如果pid是正數,那麼向訊號sig被髮送到程序pid.
如果pid等於0,那麼訊號sig被髮送到所以和pid程序在同一個程序組的程序
如果pid等於-1,那麼訊號發給所有的程序表中的程序,除了最大的哪個程序號.
如果pid由於-1,和0一樣,只是傳送程序組是-pid.
我們用最多的是第一個情況.還記得我們在守護程序那一節的例子嗎?我們那個時候用這
個函式殺死了父程序守護程序的建立
raise系統呼叫向自己傳送一個sig訊號.我們可以用上面那個函式來實現這個功能的.
alarm函式和時間有點關係了,這個函式可以在seconds秒後向自己傳送一個SIGALRM訊號
.. 下面這個函式會有什麼結果呢?
#include <unistd.h>;
main()
{
unsigned int i;
alarm(1);
for(i=0;1;i++)
printf("I=%d",i);
}
SIGALRM的預設操作是結束程序,所以程式在1秒之後結束,你可以看看你的最後I值為多少
,來比較一下大家的系統性能差異(我的是2232).
2。訊號操作 有時候我們希望程序正確的執行,而不想程序受到訊號的影響,比如我
們希望上面那個程式在1秒鐘之後不結束.這個時候我們就要進行訊號的操作了.
訊號操作最常用的方法是訊號遮蔽.訊號遮蔽要用到下面的幾個函式.
#include <signal.h>;
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(sigset_t *set,int signo);
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
sigemptyset函式初始化訊號集合set,將set設定為空.sigfillset也初始化訊號集合,只
是將訊號集合設定為所有訊號的集合.sigaddset將訊號signo加入到訊號集合之中,sigd
elset將訊號從訊號集合中刪除.sigismember查詢訊號是否在訊號集合之中.
sigprocmask是最為關鍵的一個函式.在使用之前要先設定好訊號集合set.這個函式的作
用是將指定的訊號集合set加入到程序的訊號阻塞集合之中去,如果提供了oset那麼當前
的程序訊號阻塞集合將會儲存在oset裡面.引數how決定函式的操作方式.
SIG_BLOCK:增加一個訊號集合到當前程序的阻塞集合之中.
SIG_UNBLOCK:從當前的阻塞集合之中刪除一個訊號集合.
SIG_SETMASK:將當前的訊號集合設定為訊號阻塞集合.
以一個例項來解釋使用這幾個函式.
#include <signal.h>;
#include <stdio.h>;
#include <math.h>;
#include <stdlib.h>;
int main(int argc,char **argv)
{
double y;
sigset_t intmask;
int i,repeat_factor;
if(argc!=2)
{
fprintf(stderr,"Usage:%s repeat_factor/n/a",argv[0]);
exit(1);
}
if((repeat_factor=atoi(argv[1]))<1)repeat_factor=10;
sigemptyset(&intmask); /* 將訊號集合設定為空 */
sigaddset(&intmask,SIGINT); /* 加入中斷 Ctrl+C 訊號*/
while(1)
{
/*阻塞訊號,我們不希望儲存原來的集合所以引數為NULL*/
sigprocmask(SIG_BLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal blocked/n");
for(i=0;i<repeat_factor;i++)y=sin((double)i);
fprintf(stderr,"Blocked calculation is finished/n");
/* 取消阻塞 */
sigprocmask(SIG_UNBLOCK,&intmask,NULL);
fprintf(stderr,"SIGINT signal unblocked/n");
for(i=0;i<repeat_factor;i++)y=sin((double)i);
fprintf(stderr,"Unblocked calculation is finished/n");
}
exit(0);
}
程式在執行的時候我們要使用Ctrl+C來結束.如果我們在第一計算的時候發出SIGINT訊號
,由於訊號已經遮蔽了,所以程式沒有反映.只有到訊號被取消阻塞的時候程式才會結束.
注意我們只要發出一次SIGINT訊號就可以了,因為訊號遮蔽只是將訊號加入到訊號阻塞
集合之中,並沒有丟棄這個訊號.一旦訊號遮蔽取消了,這個訊號就會發生作用.
有時候我們希望對訊號作出及時的反映的,比如當擁護按下Ctrl+C時,我們不想什麼事情
也不做,我們想告訴使用者你的這個操作不好,請不要重試,而不是什麼反映也沒有的. 這個
時候我們要用到sigaction函式.
#include <signal.h>;

int sigaction(int signo,const struct sigaction *act,
struct sigaction *oact);
struct sigaction {
void (*sa_handler)(int signo);
void (*sa_sigaction)(int siginfo_t *info,void *act);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore)(void);
}
這個函式和結構看起來是不是有點恐怖呢.不要被這個嚇著了,其實這個函式的使用相當
簡單的.我們先解釋一下各個引數的含義. signo很簡單就是我們要處理的訊號了,可以是
任何的合法的訊號.有兩個訊號不能夠使用(SIGKILL和SIGSTOP). act包含我們要對這個
訊號進行如何處理的資訊.oact更簡單了就是以前對這個函式的處理資訊了,主要用來保
存資訊的,一般用NULL就OK了.
訊號結構有點複雜.不要緊我們慢慢的學習.
sa_handler是一個函式型指標,這個指標指向一個函式,這個函式有一個引數.這個函式就
是我們要進行的訊號操作的函式. sa_sigaction,sa_restore和sa_handler差不多的,只
是引數不同罷了.這兩個元素我們很少使用,就不管了.
sa_flags用來設定訊號操作的各個情況.一般設定為0好了.sa_mask我們已經學習過了
在使用的時候我們用sa_handler指向我們的一個訊號操作函式,就可以了.sa_handler有
兩個特殊的值:SIG_DEL和SIG_IGN.SIG_DEL是使用預設的訊號操作函式,而SIG_IGN是使用
忽略該訊號的操作函式.
這個函式複雜,我們使用一個例項來說明.下面這個函式可以捕捉使用者的CTRL+C訊號.並輸
出一個提示語句.
#include <signal.h>;
#include <stdio.h>;
#include <string.h>;
#include <errno.h>;
#include <unistd.h>;
#define PROMPT "你想終止程式嗎?"
char *prompt=PROMPT;
void ctrl_c_op(int signo)
{
write(STDERR_FILENO,prompt,strlen(prompt));
}
int main()
{
struct sigaction act;
act.sa_handler=ctrl_c_op;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
if(sigaction(SIGINT,&act,NULL)<0)
{
fprintf(stderr,"Install Signal Action Error:%s/n/a",strerror(errno));
exit(1);
}
while(1);
}
在上面程式的訊號操作函式之中,我們使用了write函式而沒有使用fprintf函式.是因為
我們要考慮到下面這種情況.如果我們在訊號操作的時候又有一個訊號發生,那麼程式該
如何執行呢? 為了處理在訊號處理函式執行的時候訊號的發生,我們需要設定sa_mask成
員. 我們將我們要遮蔽的訊號新增到sa_mask結構當中去,這樣這些函式在訊號處理的時
候就會被遮蔽掉的.
3。其它訊號函式 由於訊號的操作和處理比較複雜,我們再介紹幾個訊號操作函式.

#include <unistd.h>;
#include <signal.h>;
int pause(void);
int sigsuspend(const sigset_t *sigmask);
pause函式很簡單,就是掛起程序直到一個訊號發生了.而sigsuspend也是掛起程序只是在
呼叫的時候用sigmask取代當前的訊號阻塞集合.
#include <sigsetjmp>;
int sigsetjmp(sigjmp_buf env,int val);
void siglongjmp(sigjmp_buf env,int val);
還記得goto函式或者是setjmp和longjmp函式嗎.這兩個訊號跳轉函式也可以實現程式的
跳轉讓我們可以從函式之中跳轉到我們需要的地方.
由於上面幾個函式,我們很少遇到,所以只是說明了一下,詳細情況請檢視聯機幫助.
4。一個例項 還記得我們在守護程序建立的哪個程式嗎?守護程序在這裡我們把那個
程式加強一下. 下面這個程式會在也可以檢查使用者的郵件.不過提供了一個開關,如果用
戶不想程式提示有新的郵件到來,可以向程式傳送SIGUSR2訊號,如果想程式提供提示可以
傳送SIGUSR1訊號.
#include <unistd.h>;
#include <stdio.h>;
#include <errno.h>;
#include <fcntl.h>;
#include <signal.h>;
#include <string.h>;
#include <pwd.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
/* Linux 的默任個人的郵箱地址是 /var/spool/mail/ */
#define MAIL_DIR "/var/spool/mail/"
/* 睡眠10秒鐘 */
#define SLEEP_TIME 10
#define MAX_FILENAME 255
unsigned char notifyflag=1;
long get_file_size(const char *filename)
{
struct stat buf;
if(stat(filename,&;buf)==-1)
{
if(errno==ENOENT)return 0;
else return -1;
}
return (long)buf.st_size;
}
void send_mail_notify(void)
{
fprintf(stderr,"New mail has arrived/007/n");
}
void turn_on_notify(int signo)
{
notifyflag=1;
}
void turn_off_notify(int signo)
{
notifyflag=0;
}
int check_mail(const char *filename)
{
long old_mail_size,new_mail_size;
sigset_t blockset,emptyset;
sigemptyset(&;blockset);
sigemptyset(&;emptyset);
sigaddset(&;blockset,SIGUSR1);
sigaddset(&;blockset,SIGUSR2);
old_mail_size=get_file_size(filename);
if(old_mail_size<0)return 1;
if(old_mail_size>;0) send_mail_notify();
sleep(SLEEP_TIME);
while(1)
{
if(sigprocmask(SIG_BLOCK,&;blockset,NULL)<0) return 1;
while(notifyflag==0)sigsuspend(&;emptyset);
if(sigprocmask(SIG_SETMASK,&;emptyset,NULL)<0) return 1;
new_mail_size=get_file_size(filename);
if(new_mail_size>;old_mail_size)send_mail_notify;
old_mail_size=new_mail_size;
sleep(SLEEP_TIME);
}
}
int main(void)
{
char mailfile[MAX_FILENAME];
struct sigaction newact;
struct passwd *pw;
if((pw=getpwuid(getuid()))==NULL)
{
fprintf(stderr,"Get Login Name Error:%s/n/a",strerror(errno));
exit(1);
}
strcpy(mailfile,MAIL_DIR);
strcat(mailfile,pw->;pw_name);
newact.sa_handler=turn_on_notify;
newact.sa_flags=0;
sigemptyset(&;newact.sa_mask);
sigaddset(&;newact.sa_mask,SIGUSR1);
sigaddset(&;newact.sa_mask,SIGUSR2);
if(sigaction(SIGUSR1,&;newact,NULL)<0)
fprintf(stderr,"Turn On Error:%s/n/a",strerror(errno));
newact.sa_handler=turn_off_notify;
if(sigaction(SIGUSR1,&;newact,NULL)<0)
fprintf(stderr,"Turn Off Error:%s/n/a",strerror(errno));
check_mail(mailfile);
exit(0);
}
訊號操作是一件非常複雜的事情,比我們想象之中的複雜程度還要複雜,如果你想徹底的
弄清楚訊號操作的各個問題,那麼除了大量的練習以外還要多看聯機手冊.不過如果我們
只是一般的使用的話,有了上面的幾個函式也就差不多了. 我們就介紹到這裡了.

6)Linux程式設計入門--訊息管理
前言:Linux下的程序通訊(IPC)
Linux下的程序通訊(IPC)
POSIX無名訊號量
System V訊號量
System V訊息佇列
System V共享記憶體
1。POSIX無名訊號量 如果你學習過作業系統,那麼肯定熟悉PV操作了.PV操作是原子
操作.也就是操作是不可以中斷的,在一定的時間內,只能夠有一個程序的程式碼在CPU上面
執行.在系統當中,有時候為了順利的使用和保護共享資源,大家提出了訊號的概念. 假設
我們要使用一臺印表機,如果在同一時刻有兩個程序在向印表機輸出,那麼最終的結果會
是什麼呢.為了處理這種情況,POSIX標準提出了有名訊號量和無名訊號量的概念,由於Li
nux只實現了無名訊號量,我們在這裡就只是介紹無名訊號量了. 訊號量的使用主要是用
來保護共享資源,使的資源在一個時刻只有一個程序所擁有.為此我們可以使用一個訊號
燈.當訊號燈的值為某個值的時候,就表明此時資源不可以使用.否則就表>;示可以使用.
為了提供效率,系統提供了下面幾個函式
POSIX的無名訊號量的函式有以下幾個:
#include <semaphore.h>;
int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem);
sem_init建立一個訊號燈,並初始化其值為value.pshared決定了訊號量能否在幾個程序
間共享.由於目前Linux還沒有實現程序間共享訊號燈,所以這個值只能夠取0. sem_dest
roy是用來刪除訊號燈的.sem_wait呼叫將阻塞程序,直到訊號燈的值大於0.這個函式返回
的時候自動的將訊號燈的值的件一.sem_post和sem_wait相反,是將訊號燈的內容加一同
時發出訊號喚醒等待的程序..sem_trywait和sem_wait相同,不過不阻塞的,當訊號燈的值
為0的時候返回EAGAIN,表示以後重試.sem_getvalue得到訊號燈的值.
由於Linux不支援,我們沒有辦法用源程式解釋了.
這幾個函式的使用相當簡單的.比如我們有一個程式要向一個系統印表機列印兩頁.我們
首先建立一個訊號燈,並使其初始值為1,表示我們有一個資源可用.然後一個程序呼叫se
m_wait由於這個時候訊號燈的值為1,所以這個函式返回,印表機開始列印了,同時訊號燈
的值為0 了. 如果第二個程序要列印,呼叫sem_wait時候,由於訊號燈的值為0,資源不可
用,於是被阻塞了.當第一個程序列印完成以後,呼叫sem_post訊號燈的值為1了,這個時候
系統通知第二個程序,於是第二個程序的sem_wait返回.第二個程序開始列印了.
不過我們可以使用執行緒來解決這個問題的.我們會在後面解釋什麼是執行緒的.編譯包含上
面這幾個函式的程式要加上 -lrt選賢,以連線librt.so庫
2。System V訊號量 為了解決上面哪個問題,我們也可以使用System V訊號量.很幸運的
是Linux實現了System V訊號量.這樣我們就可以用例項來解釋了. System V訊號量的函
數主要有下面幾個.
#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/sem.h>;
key_t ftok(char *pathname,char proj);
int semget(key_t key,int nsems,int semflg);
int semctl(int semid,int semnum,int cmd,union semun arg);
int semop(int semid,struct sembuf *spos,int nspos);
struct sembuf {
short sem_num; /* 使用那一個訊號 */
short sem_op; /* 進行什麼操作 */
short sem_flg; /* 操作的標誌 */
};
ftok函式是根據pathname和proj來建立一個關鍵字.semget建立一個訊號量.成功時返回
訊號的ID,key是一個關鍵字,可以是用ftok建立的也可以是IPC_PRIVATE表明由系統選用
一個關鍵字. nsems表明我們建立的訊號個數.semflg是建立的許可權標誌,和我們建立一個
檔案的標誌相同.
semctl對訊號量進行一系列的控制.semid是要操作的訊號標誌,semnum是訊號的個數,cm
d是操作的命令.經常用的兩個值是:SETVAL(設定訊號量的值)和IPC_RMID(刪除訊號燈).
arg是一個給cmd的引數.
semop是對訊號進行操作的函式.semid是訊號標誌,spos是一個運算元組表明要進行什麼
操作,nspos表明陣列的個數. 如果sem_op大於0,那麼操作將sem_op加入到訊號量的值中
,並喚醒等待訊號增加的程序. 如果為0,當訊號量的值是0的時候,函式返回,否則阻塞直
到訊號量的值為0. 如果小於0,函式判斷訊號量的值加上這個負值.如果結果為0喚醒等待
訊號量為0的程序,如果小與0函式阻塞.如果大於0,那麼從訊號量裡面減去這個值並返回
..
下面我們一以一個例項來說明這幾個函式的使用方法.這個程式用標準錯誤輸出來代替我
們用的印表機.
#include <stdio.h>;
#include <unistd.h>;
#include <limits.h>;
#include <errno.h>;
#include <string.h>;
#include <stdlib.h>;
#include <sys/stat.h>;
#include <sys/wait.h>;
#include <sys/ipc.h>;
#include <sys/sem.h>;
#define PERMS S_IRUSR|S_IWUSR
void init_semaphore_struct(struct sembuf *sem,int semnum,
int semop,int semflg)
{
/* 初始話訊號燈結構 */
sem->;sem_num=semnum;
sem->;sem_op=semop;
sem->;sem_flg=semflg;
}
int del_semaphore(int semid)
{
/* 訊號燈並不隨程式的結束而被刪除,如果我們沒刪除的話(將1改為0)
可以用ipcs命令檢視到訊號燈,用ipcrm可以刪除訊號燈的
*/
#if 1
return semctl(semid,0,IPC_RMID);
#endif
}
int main(int argc,char **argv)
{
char buffer[MAX_CANON],*c;
int i,n;
int semid,semop_ret,status;
pid_t childpid;
struct sembuf semwait,semsignal;
if((argc!=2)||((n=atoi(argv[1]))<1))
{
fprintf(stderr,"Usage:%s number/n/a",argv[0]);
exit(1);
}
/* 使用IPC_PRIVATE 表示由系統選擇一個關鍵字來建立 */
/* 建立以後訊號燈的初始值為0 */
if((semid=semget(IPC_PRIVATE,1,PERMS))==-1)
{
fprintf(stderr,"[%d]:Acess Semaphore Error:%s/n/a",
getpid(),strerror(errno));
exit(1);
}
/* semwait是要求資源的操作(-1) */
init_semaphore_struct(&semwait,0,-1,0);
/* semsignal是釋放資源的操作(+1) */
init_semaphore_struct(&semsignal,0,1,0);
/* 開始的時候有一個系統資源(一個標準錯誤輸出) */
if(semop(semid,&semsignal,1)==-1)
{
fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",
getpid(),strerror(errno));
if(del_semaphore(semid)==-1)
fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",
getpid(),strerror(errno));
exit(1);
}
/* 建立一個程序鏈 */
for(i=0;i<n;i++)
if(childpid=fork()) break;
sprintf(buffer,"[i=%d]-->;[Process=%d]-->;[Parent=%d]-->;[Child=%d]/n",
i,getpid(),getppid(),childpid);
c=buffer;
/* 這裡要求資源,進入原子操作 */
while(((semop_ret=semop(semid,&semwait,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
{
fprintf(stderr,"[%d]:Decrement Semaphore Error:%s/n/a",
getpid(),strerror(errno));
}
else
{
while(*c!='/0')fputc(*c++,stderr);
/* 原子操作完成,趕快釋放資源 */
while(((semop_ret=semop(semid,&semsignal,1))==-1)&&(errno==EINTR));
if(semop_ret==-1)
fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",
getpid(),strerror(errno));
}
/* 不能夠在其他程序反問訊號燈的時候,我們刪除了訊號燈 */
while((wait(&status)==-1)&&(errno==EINTR));
/* 訊號燈只能夠被刪除一次的 */
if(i==1)
if(del_semaphore(semid)==-1)
fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",
getpid(),strerror(errno));
exit(0);
}
訊號燈的主要用途是保護臨界資源(在一個時刻只被一個程序所擁有).
3。SystemV訊息佇列 為了便於程序之間通訊,我們可以使用管道通訊 SystemV也提供了
一些函式來實現程序的通訊.這就是訊息佇列.
#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/msg.h>;
int msgget(key_t key,int msgflg);
int msgsnd(int msgid,struct msgbuf *msgp,int msgsz,int msgflg);
int msgrcv(int msgid,struct msgbuf *msgp,int msgsz,
long msgtype,int msgflg);
int msgctl(Int msgid,int cmd,struct msqid_ds *buf);

struct msgbuf {
long msgtype; /* 訊息型別 */
....... /* 其他資料型別 */
}
msgget函式和semget一樣,返回一個訊息佇列的標誌.msgctl和semctl是對訊息進行控制
.. msgsnd和msgrcv函式是用來進行訊息通訊的.msgid是接受或者傳送的訊息佇列標誌.
msgp是接受或者傳送的內容.msgsz是訊息的大小. 結構msgbuf包含的內容是至少有一個
為msgtype.其他的成分是使用者定義的.對於傳送函式msgflg指出緩衝區用完時候的操作.
接受函式指出無訊息時候的處理.一般為0. 接收函式msgtype指出接收訊息時候的操作.

如果msgtype=0,接收訊息佇列的第一個訊息.大於0接收佇列中訊息型別等於這個值的第
一個訊息.小於0接收訊息佇列中小於或者等於msgtype絕對值的所有訊息中的最小一個消
息. 我們以一個例項來解釋程序通訊.下面這個程式有server和client組成.先