(一)目錄介紹
1)Linux程序設計入門(mén)--基礎知識 2)Linux程序設計入門(mén)--進(jìn)程介紹 3)Linux程序設計入門(mén)--文件操作 4)Linux程序設計入門(mén)--時(shí)間概念 5)Linux程序設計入門(mén)--信號處理 6)Linux程序設計入門(mén)--消息管理 7)Linux程序設計入門(mén)--線(xiàn)程操作 8)Linux程序設計入門(mén)--網(wǎng)絡(luò )編程 9)Linux下C開(kāi)發(fā)工具介紹 (二)具體內容 1)Linux程序設計入門(mén)--基礎知識 Linux下C語(yǔ)言編程基礎知識 前言: 這篇文章介紹在LINUX下進(jìn)行C語(yǔ)言編程所需要的基礎知識.在這篇文章當中,我們將 會(huì )學(xué)到以下內容: 源程序編譯 Makefile的編寫(xiě) 程序庫的鏈接 程序的調試 頭文件和系統求助 ---------------------------------------------------------------------------- ---- 1.源程序的編譯 在Linux下面,如果要編譯一個(gè)C語(yǔ)言源程序,我們要使用GNU的gcc編譯器. 下面我們 以一個(gè)實(shí)例來(lái)說(shuō)明如何使用gcc編譯器. 假設我們有下面一個(gè)非常簡(jiǎn)單的源程序(hello.c): int main(int argc,char **argv) { printf("Hello Linux\n"); } 要編譯這個(gè)程序,我們只要在命令行下執行: gcc -o hello hello.c gcc 編譯器就會(huì )為我們生成一個(gè)hello的可執行文件.執行./hello就可以看到程序的輸出 結果了.命令行中 gcc表示我們是用gcc來(lái)編譯我們的源程序,-o 選項表示我們要求編譯 器給我們輸出的可執行文件名為hello 而hello.c是我們的源程序文件. gcc編譯器有許多選項,一般來(lái)說(shuō)我們只要知道其中的幾個(gè)就夠了. -o選項我們已經(jīng)知道 了,表示我們要求輸出的可執行文件名. -c選項表示我們只要求編譯器輸出目標代碼,而 不必要輸出可執行文件. -g選項表示我們要求編譯器在編譯的時(shí)候提供我們以后對程序 進(jìn)行調試的信息. 知道了這三個(gè)選項,我們就可以編譯我們自己所寫(xiě)的簡(jiǎn)單的源程序了,如果你想要知道更 多的選項,可以查看gcc的幫助文檔,那里有著(zhù)許多對其它選項的詳細說(shuō)明. 2.Makefile的編寫(xiě) 假設我們有下面這樣的一個(gè)程序,源代碼如下: /* 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); } 當然由于這個(gè)程序是很短的我們可以這樣來(lái)編譯 gcc -c main.c gcc -c mytool1.c gcc -c mytool2.c gcc -o main main.o mytool1.o mytool2.o 這樣的話(huà)我們也可以產(chǎn)生main程序,而且也不時(shí)很麻煩.但是如果我們考慮一下如果有一 天我們修改了其中的一個(gè)文件(比如說(shuō)mytool1.c)那么我們難道還要重新輸入上面的命令 也許你會(huì )說(shuō),這個(gè)很容易解決啊,我寫(xiě)一個(gè)SHELL腳本,讓她幫我去完成不就可以了.是的 對于這個(gè)程序來(lái)說(shuō),是可以起到作用的.但是當我們把事情想的更復雜一點(diǎn),如果我們的程 序有幾百個(gè)源程序的時(shí)候,難道也要編譯器重新一個(gè)一個(gè)的去編譯? 為此,聰明的程序員們想出了一個(gè)很好的工具來(lái)做這件事情,這就是make.我們只要執行以 下make,就可以把上面的問(wèn)題解決掉.在我們執行make之前,我們要先編寫(xiě)一個(gè)非常重要的 文件.--Makefile.對于上面的那個(gè)程序來(lái)說(shuō),可能的一個(gè)Makefile的文件是: # 這是上面那個(gè)程序的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 有了這個(gè)Makefile文件,不過(guò)我們什么時(shí)候修改了源程序當中的什么文件,我們只要執行 make命令,我們的編譯器都只會(huì )去編譯和我們修改的文件有關(guān)的文件,其它的文件她連理 都不想去理的. 下面我們學(xué)習Makefile是如何編寫(xiě)的. 在Makefile中也#開(kāi)始的行都是注釋行.Makefile中最重要的是描述文件的依賴(lài)關(guān)系的說(shuō) 明.一般的格式是: target: components TAB rule 第一行表示的是依賴(lài)關(guān)系.第二行是規則. 比如說(shuō)我們上面的那個(gè)Makefile文件的第二行 main:main.o mytool1.o mytool2.o 表示我們的目標(target)main的依賴(lài)對象(components)是main.o mytool1.o mytool2.o 當倚賴(lài)的對象在目標修改后修改的話(huà),就要去執行規則一行所指定的命令.就象我們的上 面那個(gè)Makefile第三行所說(shuō)的一樣要執行 gcc -o main main.o mytool1.o mytool2.o 注意規則一行中的TAB表示那里是一個(gè)TAB鍵 Makefile有三個(gè)非常有用的變量.分別是$@,$^,$<代表的意義分別是: $@--目標文件,$^--所有的依賴(lài)文件,$<--第一個(gè)依賴(lài)文件. 如果我們使用上面三個(gè)變量,那么我們可以簡(jiǎn)化我們的Makefile文件為: # 這是簡(jiǎn)化后的Makefile main:main.o mytool1.o mytool2.o gcc -o $@ $^ 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 $< 經(jīng)過(guò)簡(jiǎn)化后我們的Makefile是簡(jiǎn)單了一點(diǎn),不過(guò)人們有時(shí)候還想簡(jiǎn)單一點(diǎn).這里我們學(xué)習 一個(gè)Makefile的缺省規則 ..c.o: gcc -c $< 這個(gè)規則表示所有的 .o文件都是依賴(lài)與相應的.c文件的.例如mytool.o依賴(lài)于mytool.c 這樣Makefile還可以變?yōu)? # 這是再一次簡(jiǎn)化后的Makefile main:main.o mytool1.o mytool2.o gcc -o $@ $^ ..c.o: gcc -c $< 好了,我們的Makefile 也差不多了,如果想知道更多的關(guān)于Makefile規則可以查看相應的 文檔. 3.程序庫的鏈接 試著(zhù)編譯下面這個(gè)程序 /* temp.c */ #include <math.h> int main(int argc,char **argv) { double value; printf("Value:%f\n",value); } 這個(gè)程序相當簡(jiǎn)單,但是當我們用 gcc -o temp temp.c 編譯時(shí)會(huì )出現下面所示的錯誤.
/tmp/cc33Kydu.o: In function `main‘: /tmp/cc33Kydu.o(.text+0xe): undefined reference to `log‘ collect2: ld returned 1 exit status 出現這個(gè)錯誤是因為編譯器找不到log的具體實(shí)現.雖然我們包括了正確的頭文件,但是我 們在編譯的時(shí)候還是要連接確定的庫.在Linux下,為了使用數學(xué)函數,我們必須和數學(xué)庫 連接,為此我們要加入 -lm 選項. gcc -o temp temp.c -lm這樣才能夠正確的編譯.也許 有人要問(wèn),前面我們用printf函數的時(shí)候怎么沒(méi)有連接庫呢?是這樣的,對于一些常用的函 數的實(shí)現,gcc編譯器會(huì )自動(dòng)去連接一些常用庫,這樣我們就沒(méi)有必要自己去指定了. 有時(shí) 候我們在編譯程序的時(shí)候還要指定庫的路徑,這個(gè)時(shí)候我們要用到編譯器的 -L選項指定 路徑.比如說(shuō)我們有一個(gè)庫在 /home/hoyt/mylib下,這樣我們編譯的時(shí)候還要加上 -L/h ome/hoyt/mylib.對于一些標準庫來(lái)說(shuō),我們沒(méi)有必要指出路徑.只要它們在起缺省庫的路 徑下就可以了.系統的缺省庫的路徑/lib /usr/lib /usr/local/lib 在這三個(gè)路徑下面 的庫,我們可以不指定路徑. 還有一個(gè)問(wèn)題,有時(shí)候我們使用了某個(gè)函數,但是我們不知道庫的名字,這個(gè)時(shí)候怎么辦呢 很抱歉,對于這個(gè)問(wèn)題我也不知道答案,我只有一個(gè)傻辦法.首先,我到標準庫路徑下面去 找看看有沒(méi)有和我用的函數相關(guān)的庫,我就這樣找到了線(xiàn)程(thread)函數的庫文件(libp thread.a). 當然,如果找不到,只有一個(gè)笨方法.比如我要找sin這個(gè)函數所在的庫. 就只 好用 nm -o /lib/*.so|grep sin>~/sin 命令,然后看~/sin文件,到那里面去找了. 在s in文件當中,我會(huì )找到這樣的一行libm-2.1.2.so:00009fa0 W sin 這樣我就知道了sin在 libm-2.1.2.so庫里面,我用 -lm選項就可以了(去掉前面的lib和后面的版本標志,就剩 下m了所以是 -lm). 如果你知道怎么找,請趕快告訴我,我回非常感激的.謝謝! 4.程序的調試 我們編寫(xiě)的程序不太可能一次性就會(huì )成功的,在我們的程序當中,會(huì )出現許許多多我 們想不到的錯誤,這個(gè)時(shí)候我們就要對我們的程序進(jìn)行調試了. 最常用的調試軟件是gdb.如果你想在圖形界面下調試程序,那么你現在可以選擇xxgdb.記 得要在編譯的時(shí)候加入 -g選項.關(guān)于gdb的使用可以看gdb的幫助文件.由于我沒(méi)有用過(guò)這 個(gè)軟件,所以我也不能夠說(shuō)出如何使用. 不過(guò)我不喜歡用gdb.跟蹤一個(gè)程序是很煩的事情 ,我一般用在程序當中輸出中間變量的值來(lái)調試程序的.當然你可以選擇自己的辦法,沒(méi)有 必要去學(xué)別人的.現在有了許多IDE環(huán)境,里面已經(jīng)自己帶了調試器了.你可以選擇幾個(gè)試 一試找出自己喜歡的一個(gè)用. 5.頭文件和系統求助 有時(shí)候我們只知道一個(gè)函數的大概形式,不記得確切的表達式,或者是不記得著(zhù)函數 在那個(gè)頭文件進(jìn)行了說(shuō)明.這個(gè)時(shí)候我們可以求助系統. 比如說(shuō)我們想知道fread這個(gè)函數的確切形式,我們只要執行 man fread 系統就會(huì )輸出著(zhù) 函數的詳細解釋的.和這個(gè)函數所在的頭文件<stdio.h>說(shuō)明了. 如果我們要write這個(gè)函 數的說(shuō)明,當我們執行man write時(shí),輸出的結果卻不是我們所需要的. 因為我們要的是w rite這個(gè)函數的說(shuō)明,可是出來(lái)的卻是write這個(gè)命令的說(shuō)明.為了得到write的函數說(shuō)明 我們要用 man 2 write. 2表示我們用的write這個(gè)函數是系統調用函數,還有一個(gè)我們常 用的是3表示函數是C的庫函數. 記住不管什么時(shí)候,man都是我們的最好助手. ------------------------------------------------------------------------ 好了,這一章就講這么多了,有了這些知識我們就可以進(jìn)入激動(dòng)人心的Linux下的C程序探 險活動(dòng).
[color=blue:24e76417e9][b:24e76417e9]2)Linux程序設計入門(mén)--進(jìn)程介紹[/b:24e76417e9][/color:24e76417e9] Linux下進(jìn)程的創(chuàng )建 前言: 這篇文章是用來(lái)介紹在Linux下和進(jìn)程相關(guān)的各個(gè)概念.我們將會(huì )學(xué)到: 進(jìn)程的概念 進(jìn)程的身份 進(jìn)程的創(chuàng )建 守護進(jìn)程的創(chuàng )建 ---------------------------------------------------------------------------- ---- 1。進(jìn)程的概念 Linux操作系統是面向多用戶(hù)的.在同一時(shí)間可以有許多用戶(hù)向操作系統發(fā)出各種命 令.那么操作系統是怎么實(shí)現多用戶(hù)的環(huán)境呢? 在現代的操作系統里面,都有程序和進(jìn)程 的概念.那么什么是程序,什么是進(jìn)程呢? 通俗的講程序是一個(gè)包含可以執行代碼的文件 ,是一個(gè)靜態(tài)的文件.而進(jìn)程是一個(gè)開(kāi)始執行但是還沒(méi)有結束的程序的實(shí)例.就是可執行文 件的具體實(shí)現. 一個(gè)程序可能有許多進(jìn)程,而每一個(gè)進(jìn)程又可以有許多子進(jìn)程.依次循環(huán) 下去,而產(chǎn)生子孫進(jìn)程. 當程序被系統調用到內存以后,系統會(huì )給程序分配一定的資源(內 存,設備等等)然后進(jìn)行一系列的復雜操作,使程序變成進(jìn)程以供系統調用.在系統里面只 有進(jìn)程沒(méi)有程序,為了區分各個(gè)不同的進(jìn)程,系統給每一個(gè)進(jìn)程分配了一個(gè)ID(就象我們的 身份證)以便識別. 為了充分的利用資源,系統還對進(jìn)程區分了不同的狀態(tài).將進(jìn)程分為新 建,運行,阻塞,就緒和完成五個(gè)狀態(tài). 新建表示進(jìn)程正在被創(chuàng )建,運行是進(jìn)程正在運行,阻 塞是進(jìn)程正在等待某一個(gè)事件發(fā)生,就緒是表示系統正在等待CPU來(lái)執行命令,而完成表示 進(jìn)程已經(jīng)結束了系統正在回收資源. 關(guān)于進(jìn)程五個(gè)狀態(tài)的詳細解說(shuō)我們可以看《操作系 統》上面有詳細的解說(shuō)。 2。進(jìn)程的標志 上面我們知道了進(jìn)程都有一個(gè)ID,那么我們怎么得到進(jìn)程的ID呢?系統調用getpid可 以得到進(jìn)程的ID,而getppid可以得到父進(jìn)程(創(chuàng )建調用該函數進(jìn)程的進(jìn)程)的ID. #include <unistd> pid_t getpid(void); pid_t getppid(void); 進(jìn)程是為程序服務(wù)的,而程序是為了用戶(hù)服務(wù)的.系統為了找到進(jìn)程的用戶(hù)名,還為進(jìn)程和 用戶(hù)建立聯(lián)系.這個(gè)用戶(hù)稱(chēng)為進(jìn)程的所有者.相應的每一個(gè)用戶(hù)也有一個(gè)用戶(hù)ID.通過(guò)系統 調用getuid可以得到進(jìn)程的所有者的ID.由于進(jìn)程要用到一些資源,而Linux對系統資源是 進(jìn)行保護的,為了獲取一定資源進(jìn)程還有一個(gè)有效用戶(hù)ID.這個(gè)ID和系統的資源使用有關(guān) ,涉及到進(jìn)程的權限. 通過(guò)系統調用geteuid我們可以得到進(jìn)程的有效用戶(hù)ID. 和用戶(hù)ID 相對應進(jìn)程還有一個(gè)組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); 有時(shí)候我們還會(huì )對用戶(hù)的其他信息感興趣(登錄名等等),這個(gè)時(shí)候我們可以調用getpwui d來(lái)得到. struct passwd { char *pw_name; /* 登錄名稱(chēng) */ char *pw_passwd; /* 登錄口令 */ uid_t pw_uid; /* 用戶(hù)ID */ gid_t pw_gid; /* 用戶(hù)組ID */ char *pw_gecos; /* 用戶(hù)的真名 */ char *pw_dir; /* 用戶(hù)的目錄 */ char *pw_shell; /* 用戶(hù)的SHELL */ }; #include <pwd.h> #include <sys/types.h>
struct passwd *getpwuid(uid_t uid); 下面我們學(xué)習一個(gè)實(shí)例來(lái)實(shí)踐一下上面我們所學(xué)習的幾個(gè)函數: #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。進(jìn)程的創(chuàng )建 創(chuàng )建一個(gè)進(jìn)程的系統調用很簡(jiǎn)單.我們只要調用fork函數就可以了. #include <unistd.h>
pid_t fork(); 當一個(gè)進(jìn)程調用了fork以后,系統會(huì )創(chuàng )建一個(gè)子進(jìn)程.這個(gè)子進(jìn)程和父進(jìn)程不同的地方只 有他的進(jìn)程ID和父進(jìn)程ID,其他的都是一樣.就象符進(jìn)程克隆(clone)自己一樣.當然創(chuàng )建 兩個(gè)一模一樣的進(jìn)程是沒(méi)有意義的.為了區分父進(jìn)程和子進(jìn)程,我們必須跟蹤fork的返回 值. 當fork掉用失敗的時(shí)候(內存不足或者是用戶(hù)的最大進(jìn)程數已到)fork返回-1,否則f ork的返回值有重要的作用.對于父進(jìn)程fork返回子進(jìn)程的ID,而對于fork子進(jìn)程返回0.我 們就是根據這個(gè)返回值來(lái)區分父子進(jìn)程的. 父進(jìn)程為什么要創(chuàng )建子進(jìn)程呢?前面我們已經(jīng) 說(shuō)過(guò)了Linux是一個(gè)多用戶(hù)操作系統,在同一時(shí)間會(huì )有許多的用戶(hù)在爭奪系統的資源.有時(shí) 進(jìn)程為了早一點(diǎn)完成任務(wù)就創(chuàng )建子進(jìn)程來(lái)爭奪資源. 一旦子進(jìn)程被創(chuàng )建,父子進(jìn)程一起從 fork處繼續執行,相互競爭系統的資源.有時(shí)候我們希望子進(jìn)程繼續執行,而父進(jìn)程阻塞直 到子進(jìn)程完成任務(wù).這個(gè)時(shí)候我們可以調用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系統調用會(huì )使父進(jìn)程阻塞直到一個(gè)子進(jìn)程結束或者是父進(jìn)程接受到了一個(gè)信號.如果 沒(méi)有父進(jìn)程沒(méi)有子進(jìn)程或者他的子進(jìn)程已經(jīng)結束了wait回立即返回.成功時(shí)(因一個(gè)子進(jìn) 程結束)wait將返回子進(jìn)程的ID,否則返回-1,并設置全局變量errno.stat_loc是子進(jìn)程的 退出狀態(tài).子進(jìn)程調用exit,_exit 或者是return來(lái)設置這個(gè)值. 為了得到這個(gè)值Linux定 義了幾個(gè)宏來(lái)測試這個(gè)返回值. WIFEXITED:判斷子進(jìn)程退出值是非0 WEXITSTATUS:判斷子進(jìn)程的退出值(當子進(jìn)程退出時(shí)非0). WIFSIGNALED:子進(jìn)程由于有沒(méi)有獲得的信號而退出. WTERMSIG:子進(jìn)程沒(méi)有獲得的信號號(在WIFSIGNALED為真時(shí)才有意義). waitpid等待指定的子進(jìn)程直到子進(jìn)程返回.如果pid為正值則等待指定的進(jìn)程(pid).如果 為0則等待任何一個(gè)組ID和調用者的組ID相同的進(jìn)程.為-1時(shí)等同于wait調用.小于-1時(shí)等 待任何一個(gè)組ID等于pid絕對值的進(jìn)程. stat_loc和wait的意義一樣. options可以決定 父進(jìn)程的狀態(tài).可以取兩個(gè)值 WNOHANG:父進(jìn)程立即返回當沒(méi)有子進(jìn)程存在時(shí). WUNTACHE D:當子進(jìn)程結束時(shí)waitpid返回,但是子進(jìn)程的退出狀態(tài)不可得到. 父進(jìn)程創(chuàng )建子進(jìn)程后,子進(jìn)程一般要執行不同的程序.為了調用系統程序,我們可以使用系 統調用exec族調用.exec族調用有著(zhù)5個(gè)函數. #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族調用可以執行給定程序.關(guān)于exec族調用的詳細解說(shuō)可以參考系統手冊(man exec l). 下面我們來(lái)學(xué)習一個(gè)實(shí)例.注意編譯的時(shí)候要加 -lm以便連接數學(xué)函數庫. #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函數會(huì )返回一個(gè)指定的錯誤號的錯誤信息的字符串. 4。守護進(jìn)程的創(chuàng )建 如果你在DOS時(shí)代編寫(xiě)過(guò)程序,那么你也許知道在DOS下為了編寫(xiě)一個(gè)常駐內存的程序 我們要編寫(xiě)多少代碼了.相反如果在Linux下編寫(xiě)一個(gè)"常駐內存"的程序卻是很容易的.我 們只要幾行代碼就可以做到. 實(shí)際上由于Linux是多任務(wù)操作系統,我們就是不編寫(xiě)代碼 也可以把一個(gè)程序放到后臺去執行的.我們只要在命令后面加上&符號SHELL就會(huì )把我們的 程序放到后臺去運行的. 這里我們"開(kāi)發(fā)"一個(gè)后臺檢查郵件的程序.這個(gè)程序每個(gè)一個(gè)指 定的時(shí)間回去檢查我們的郵箱,如果發(fā)現我們有郵件了,會(huì )不斷的報警(通過(guò)機箱上的小喇 叭來(lái)發(fā)出聲音). 后面有這個(gè)函數的加強版本加強版本 后臺進(jìn)程的創(chuàng )建思想: 首先父進(jìn)程創(chuàng )建一個(gè)子進(jìn)程.然后子進(jìn)程殺死父進(jìn)程(是不是很無(wú) 情?). 信號處理所有的工作由子進(jìn)程來(lái)處理. #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <signal.h> /* Linux 的默任個(gè)人的郵箱地址是 /var/spool/mail/用戶(hù)的登錄名 */ #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","7"); close(mailfd); } sleep(SLEEP_TIME); } } } 你可以在默認的路徑下創(chuàng )建你的郵箱文件,然后測試一下這個(gè)程序.當然這個(gè)程序還有很 多地方要改善的.我們后面會(huì )對這個(gè)小程序改善的,再看我的改善之前你可以嘗試自己改 善一下.比如讓用戶(hù)指定郵相的路徑和睡眠時(shí)間等等.相信自己可以做到的.動(dòng)手吧,勇敢 的探險者. 好了進(jìn)程一節的內容我們就先學(xué)到這里了.進(jìn)程是一個(gè)非常重要的概念,許多的程序都會(huì ) 用子進(jìn)程.創(chuàng )建一個(gè)子進(jìn)程是每一個(gè)程序員的基本要求!
[b:24e76417e9][color=blue:24e76417e9]3)Linux程序設計入門(mén)--文件操作[/color:24e76417e9][/b:24e76417e9] Linux下文件的操作 前言: 我們在這一節將要討論linux下文件操作的各個(gè)函數. 文件的創(chuàng )建和讀寫(xiě) 文件的各個(gè)屬性 目錄文件的操作 管道文件 ---------------------------------------------------------------------------- ---- 1。文件的創(chuàng )建和讀寫(xiě) 我假設你已經(jīng)知道了標準級的文件操作的各個(gè)函數(fopen,fread,fwrite等等).當然 如果你不清楚的話(huà)也不要著(zhù)急.我們討論的系統級的文件操作實(shí)際上是為標準級文件操作 服務(wù)的. 當我們需要打開(kāi)一個(gè)文件進(jìn)行讀寫(xiě)操作的時(shí)候,我們可以使用系統調用函數open.使用完 成以后我們調用另外一個(gè)close函數進(jìn)行關(guān)閉操作. #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函數有兩個(gè)形式.其中pathname是我們要打開(kāi)的文件名(包含路徑名稱(chēng),缺省是認為在 當前路徑下面).flags可以去下面的一個(gè)值或者是幾個(gè)值的組合. O_RDONLY:以只讀的方式打開(kāi)文件. O_WRONLY:以只寫(xiě)的方式打開(kāi)文件. O_RDWR:以讀寫(xiě)的方式打開(kāi)文件. O_APPEND:以追加的方式打開(kāi)文件. O_CREAT:創(chuàng )建一個(gè)文件. O_EXEC:如果使用了O_CREAT而且文件已經(jīng)存在,就會(huì )發(fā)生一個(gè)錯誤. O_NOBLOCK:以非阻塞的方式打開(kāi)一個(gè)文件. O_TRUNC:如果文件已經(jīng)存在,則刪除文件的內容. 前面三個(gè)標志只能使用任意的一個(gè).如果使用了O_CREATE標志,那么我們要使用open的第 二種形式.還要指定mode標志,用來(lái)表示文件的訪(fǎng)問(wèn)權限.mode可以是以下情況的組合. ----------------------------------------------------------------- S_IRUSR 用戶(hù)可以讀 S_IWUSR 用戶(hù)可以寫(xiě) S_IXUSR 用戶(hù)可以執行 S_IRWXU 用戶(hù)可以讀寫(xiě)執行 ----------------------------------------------------------------- S_IRGRP 組可以讀 S_IWGRP 組可以寫(xiě) S_IXGRP 組可以執行 S_IRWXG 組可以讀寫(xiě)執行 ----------------------------------------------------------------- S_IROTH 其他人可以讀 S_IWOTH 其他人可以寫(xiě) S_IXOTH 其他人可以執行 S_IRWXO 其他人可以讀寫(xiě)執行 ----------------------------------------------------------------- S_ISUID 設置用戶(hù)執行ID S_ISGID 設置組的執行ID ----------------------------------------------------------------- 我們也可以用數字來(lái)代表各個(gè)位的標志.Linux總共用5個(gè)數字來(lái)表示文件的各種權限. 00000.第一位表示設置用戶(hù)ID.第二位表示設置組ID,第三位表示用戶(hù)自己的權限位,第四 位表示組的權限,最后一位表示其他人的權限. 每個(gè)數字可以取1(執行權限),2(寫(xiě)權限),4(讀權限),0(什么也沒(méi)有)或者是這幾個(gè)值的和 .. 比如我們要創(chuàng )建一個(gè)用戶(hù)讀寫(xiě)執行,組沒(méi)有權限,其他人讀執行的文件.設置用戶(hù)ID位那么 我們可以使用的模式是--1(設置用戶(hù)ID)0(組沒(méi)有設置)7(1+2+4)0(沒(méi)有權限,使用缺省) 5(1+4)即10705: open("temp",O_CREAT,10705); 如果我們打開(kāi)文件成功,open會(huì )返回一個(gè)文件描述符.我們以后對文件的所有操作就可以 對這個(gè)文件描述符進(jìn)行操作了. 當我們操作完成以后,我們要關(guān)閉文件了,只要調用close就可以了,其中fd是我們要關(guān)閉 的文件描述符. 文件打開(kāi)了以后,我們就要對文件進(jìn)行讀寫(xiě)了.我們可以調用函數read和write進(jìn)行文件的 讀寫(xiě). #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是我們要進(jìn)行讀寫(xiě)操作的文件描述符,buffer是我們要寫(xiě)入文件內容或讀出文件內容的 內存地址.count是我們要讀寫(xiě)的字節數. 對于普通的文件read從指定的文件(fd)中讀取count字節到buffer緩沖區中(記住我們必 須提供一個(gè)足夠大的緩沖區),同時(shí)返回count. 如果read讀到了文件的結尾或者被一個(gè)信號所中斷,返回值會(huì )小于count.如果是由信號中 斷引起返回,而且沒(méi)有返回數據,read會(huì )返回-1,且設置errno為EINTR.當程序讀到了文件 結尾的時(shí)候,read會(huì )返回0. write從buffer中寫(xiě)count字節到文件fd中,成功時(shí)返回實(shí)際所寫(xiě)的字節數. 下面我們學(xué)習一個(gè)實(shí)例,這個(gè)實(shí)例用來(lái)拷貝文件. #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); } /* 打開(kāi)源文件 */ if((from_fd=open(argv[1],O_RDONLY))==-1) { fprintf(stderr,"Open %s Error:%s\n",argv[1],strerror(errno)); exit(1); } /* 創(chuàng )建目的文件 */ 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); } /* 以下代碼是一個(gè)經(jīng)典的拷貝文件的代碼 */ while(bytes_read=read(from_fd,buffer,BUFFER_SIZE)) { /* 一個(gè)致命的錯誤發(fā)生了 */ if((bytes_read==-1)&&(errno!=EINTR)) break; else if(bytes_read>0) { ptr=buffer; while(bytes_write=write(to_fd,ptr,bytes_read)) { /* 一個(gè)致命錯誤發(fā)生了 */ if((bytes_write==-1)&&(errno!=EINTR))break; /* 寫(xiě)完了所有讀的字節 */ else if(bytes_write==bytes_read) break; /* 只寫(xiě)了一部分,繼續寫(xiě) */ else if(bytes_write>0) { ptr+=bytes_write; bytes_read-=bytes_write; } } /* 寫(xiě)的時(shí)候發(fā)生的致命錯誤 */ if(bytes_write==-1)break; } } close(from_fd); close(to_fd); exit(0); } 2。文件的各個(gè)屬性 文件具有各種各樣的屬性,除了我們上面所知道的文件權限以外,文件還有創(chuàng )建時(shí)間 ,大小等等屬性. 有時(shí)侯我們要判斷文件是否可以進(jìn)行某種操作(讀,寫(xiě)等等).這個(gè)時(shí)候我們可以使用acce ss函數. #include <unistd.h>
int access(const char *pathname,int mode); pathname:是文件名稱(chēng),mode是我們要判斷的屬性.可以取以下值或者是他們的組合. R_OK文件可以讀,W_OK文件可以寫(xiě),X_OK文件可以執行,F_OK文件存在.當我們測試成功時(shí) ,函數返回0,否則如果有一個(gè)條件不符時(shí),返回-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; /* 節點(diǎn) */ mode_t st_mode; /* 模式 */ nlink_t st_nlink; /* 硬連接 */ uid_t st_uid; /* 用戶(hù)ID */ gid_t st_gid; /* 組ID */ dev_t st_rdev; /* 設備類(lèi)型 */ off_t st_off; /* 文件字節數 */ unsigned long st_blksize; /* 塊大小 */ unsigned long st_blocks; /* 塊數 */ time_t st_atime; /* 最后一次訪(fǎng)問(wèn)時(shí)間 */ time_t st_mtime; /* 最后一次修改時(shí)間 */ time_t st_ctime; /* 最后一次改變時(shí)間(指屬性) */ }; stat用來(lái)判斷沒(méi)有打開(kāi)的文件,而fstat用來(lái)判斷打開(kāi)的文件.我們使用最多的屬性是st_ mode.通過(guò)著(zhù)屬性我們可以判斷給定的文件是一個(gè)普通文件還是一個(gè)目錄,連接等等.可以 使用下面幾個(gè)宏來(lái)判斷. S_ISLNK(st_mode):是否是一個(gè)連接.S_ISREG是否是一個(gè)常規文件.S_ISDIR是否是一個(gè)目 錄S_ISCHR是否是一個(gè)字符設備.S_ISBLK是否是一個(gè)塊設備S_ISFIFO是否 是一個(gè)FIFO文 件.S_ISSOCK是否是一個(gè)SOCKET文件. 我們會(huì )在下面說(shuō)明如何使用這幾個(gè)宏的. 3。目錄文件的操作 在我們編寫(xiě)程序的時(shí)候,有時(shí)候會(huì )要得到我們當前的工作路徑。C庫函數提供了get cwd來(lái)解決這個(gè)問(wèn)題。 #include <unistd.h>
char *getcwd(char *buffer,size_t size); 我們提供一個(gè)size大小的buffer,getcwd會(huì )把我們當前的路徑考到buffer中.如果buffer 太小,函數會(huì )返回-1和一個(gè)錯誤號. Linux提供了大量的目錄操作函數,我們學(xué)習幾個(gè)比較簡(jiǎn)單和常用的函數. #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]; /* 文件名稱(chēng) */ mkdir很容易就是我們創(chuàng )建一個(gè)目錄,opendir打開(kāi)一個(gè)目錄為以后讀做準備.readdir讀一 個(gè)打開(kāi)的目錄.rewinddir是用來(lái)重讀目錄的和我們學(xué)的rewind函數一樣.closedir是關(guān)閉 一個(gè)目錄.telldir和seekdir類(lèi)似與ftee和fseek函數. 下面我們開(kāi)發(fā)一個(gè)小程序,這個(gè)程序有一個(gè)參數.如果這個(gè)參數是一個(gè)文件名,我們輸出這 個(gè)文件的大小和最后修改的時(shí)間,如果是一個(gè)目錄我們輸出這個(gè)目錄下所有文件的大小和 修改時(shí)間. #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提供了許多的過(guò)濾和重定向程序,比如more cat 等等.還提供了< > | <<等等重定向操作符.在這些過(guò)濾和重 定向程序當中,都用到了管 道這種特殊的文件.系統調用pipe可以創(chuàng )建一個(gè)管道. #include<unistd.h>
int pipe(int fildes[2]); pipe調用可以創(chuàng )建一個(gè)管道(通信緩沖區).當調用成功時(shí),我們可以訪(fǎng)問(wèn)文件描述符fild es[0],fildes[1].其中fildes[0]是用來(lái)讀的文件描述符,而fildes[1]是用來(lái)寫(xiě)的文件描 述符. 在實(shí)際使用中我們是通過(guò)創(chuàng )建一個(gè)子進(jìn)程,然后一個(gè)進(jìn)程寫(xiě),一個(gè)進(jìn)程讀來(lái)使用的. 關(guān)于進(jìn)程通信的詳細情況請查看進(jìn)程通信 #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,‘‘,BUFFER+1); read(fd[0],buffer,BUFFER); printf("Parent[%d] Read:%s\n",getpid(),buffer); exit(1); } } 為了實(shí)現重定向操作,我們需要調用另外一個(gè)函數dup2. #include <unistd.h>
int dup2(int oldfd,int newfd); dup2將用oldfd文件描述符來(lái)代替newfd文件描述符,同時(shí)關(guān)閉newfd文件描述符.也就是說(shuō) , 所有向newfd操作都轉到oldfd上面.下面我們學(xué)習一個(gè)例子,這個(gè)例子將標準輸出重定向 到一個(gè)文件. #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); } 好了,文件一章我們就暫時(shí)先討論到這里,學(xué)習好了文件的操作我們其實(shí)已經(jīng)可以寫(xiě)出一 些比較有用的程序了.我們可以編寫(xiě)一個(gè)實(shí)現例如dir,mkdir,cp,mv等等常用的文件操作 命令了. 想不想自己寫(xiě)幾個(gè)試一試呢?
[color=blue:24e76417e9][b:24e76417e9]4)程序設計入門(mén)--時(shí)間概念[/b:24e76417e9][/color:24e76417e9] 前言:Linux下的時(shí)間概念 這一章我們學(xué)習Linux的時(shí)間表示和計算函數 時(shí)間的表示 時(shí)間的測量 計時(shí)器的使用 1。時(shí)間表示 在程序當中,我們經(jīng)常要輸出系統當前的時(shí)間,比如我們使用date命令 的輸出結果.這個(gè)時(shí)候我們可以使用下面兩個(gè)函數 #include <time.h>
time_t time(time_t *tloc); char *ctime(const time_t *clock); time函數返回從1970年1月1日0點(diǎn)以來(lái)的秒數.存儲在time_t結構之中.不過(guò)這個(gè)函數的返 回值對于我們來(lái)說(shuō)沒(méi)有什么實(shí)際意義.這個(gè)時(shí)候我們使用第二個(gè)函數將秒數轉化為字符串 .. 這個(gè)函數的返回類(lèi)型是固定的:一個(gè)可能值為. Thu Dec 7 14:58:59 2000 這個(gè)字符串 的長(cháng)度是固定的為26 2。時(shí)間的測量 有時(shí)候我們要計算程序執行的時(shí)間.比如我們要對算法進(jìn)行時(shí)間分析 ..這個(gè)時(shí)候可以使用下面這個(gè)函數. #include <sys/time.h>
int gettimeofday(struct timeval *tv,struct timezone *tz); strut timeval { long tv_sec; /* 秒數 */ long tv_usec; /* 微秒數 */ }; gettimeofday將時(shí)間保存在結構tv之中.tz一般我們使用NULL來(lái)代替. #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); } 這個(gè)程序輸出函數的執行時(shí)間,我們可以使用這個(gè)來(lái)進(jìn)行系統性能的測試,或者是函數算 法的效率分析.在我機器上的一個(gè)輸出結果是: Used Time:0.556070 3。計時(shí)器的使用 Linux操作系統為每一個(gè)進(jìn)程提供了3個(gè)內部間隔計時(shí)器. ITIMER_REAL:減少實(shí)際時(shí)間.到時(shí)的時(shí)候發(fā)出SIGALRM信號. ITIMER_VIRTUAL:減少有效時(shí)間(進(jìn)程執行的時(shí)間).產(chǎn)生SIGVTALRM信號. ITIMER_PROF:減少進(jìn)程的有效時(shí)間和系統時(shí)間(為進(jìn)程調度用的時(shí)間).這個(gè)經(jīng)常和上面一 個(gè)使用用來(lái)計算系統內核時(shí)間和用戶(hù)時(shí)間.產(chǎn)生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函數得到間隔計時(shí)器的時(shí)間值.保存在value中 setitimer函數設置間隔計時(shí)器 的時(shí)間值為newval.并將舊值保存在oldval中. which表示使用三個(gè)計時(shí)器中的哪一個(gè). itimerval結構中的it_value是減少的時(shí)間,當這個(gè)值為0的時(shí)候就發(fā)出相應的信號了. 然 后設置為it_interval值. #include <sys/time.h> #include <stdio.h> #include <unistd.h> #include <signal.h> #include <string.h> #define PROMPT "時(shí)間已經(jīng)過(guò)去了兩秒鐘\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); } 這個(gè)程序每執行兩秒中之后會(huì )輸出一個(gè)提示.
[b:24e76417e9][color=blue:24e76417e9]5)Linux程序設計入門(mén)--信號處理[/color:24e76417e9][/b:24e76417e9] Linux下的信號事件 前言:這一章我們討論一下Linux下的信號處理函數. Linux下的信號處理函數: 信號的產(chǎn)生 信號的處理 其它信號函數 一個(gè)實(shí)例 1。信號的產(chǎn)生 Linux下的信號可以類(lèi)比于DOS下的INT或者是Windows下的事件.在有一個(gè)信號發(fā)生時(shí) 候相信的信號就會(huì )發(fā)送給相應的進(jìn)程.在Linux下的信號有以下幾個(gè). 我們使用 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 關(guān)于這些信號的詳細解釋請查看man 7 signal的輸出結果. 信號事件的發(fā)生有兩個(gè)來(lái)源 :一個(gè)是硬件的原因(比如我們按下了鍵盤(pán)),一個(gè)是軟件的原因(比如我們使用系統函數或 者是命令發(fā)出信號). 最常用的四個(gè)發(fā)出信號的系統函數是kill, raise, alarm和setit imer函數. setitimer函數我們在計時(shí)器的使用 那一章再學(xué)習. #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系統調用負責向進(jìn)程發(fā)送信號sig. 如果pid是正數,那么向信號sig被發(fā)送到進(jìn)程pid. 如果pid等于0,那么信號sig被發(fā)送到所以和pid進(jìn)程在同一個(gè)進(jìn)程組的進(jìn)程 如果pid等于-1,那么信號發(fā)給所有的進(jìn)程表中的進(jìn)程,除了最大的哪個(gè)進(jìn)程號. 如果pid由于-1,和0一樣,只是發(fā)送進(jìn)程組是-pid. 我們用最多的是第一個(gè)情況.還記得我們在守護進(jìn)程那一節的例子嗎?我們那個(gè)時(shí)候用這 個(gè)函數殺死了父進(jìn)程守護進(jìn)程的創(chuàng )建 raise系統調用向自己發(fā)送一個(gè)sig信號.我們可以用上面那個(gè)函數來(lái)實(shí)現這個(gè)功能的. alarm函數和時(shí)間有點(diǎn)關(guān)系了,這個(gè)函數可以在seconds秒后向自己發(fā)送一個(gè)SIGALRM信號 .. 下面這個(gè)函數會(huì )有什么結果呢? #include <unistd.h> main() { unsigned int i; alarm(1); for(i=0;1;i++) printf("I=%d",i); } SIGALRM的缺省操作是結束進(jìn)程,所以程序在1秒之后結束,你可以看看你的最后I值為多少 ,來(lái)比較一下大家的系統性能差異(我的是2232). 2。信號操作 有時(shí)候我們希望進(jìn)程正確的執行,而不想進(jìn)程受到信號的影響,比如我 們希望上面那個(gè)程序在1秒鐘之后不結束.這個(gè)時(shí)候我們就要進(jìn)行信號的操作了. 信號操作最常用的方法是信號屏蔽.信號屏蔽要用到下面的幾個(gè)函數. #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查詢(xún)信號是否在信號集合之中. sigprocmask是最為關(guān)鍵的一個(gè)函數.在使用之前要先設置好信號集合set.這個(gè)函數的作 用是將指定的信號集合set加入到進(jìn)程的信號阻塞集合之中去,如果提供了oset那么當前 的進(jìn)程信號阻塞集合將會(huì )保存在oset里面.參數how決定函數的操作方式. SIG_BLOCK:增加一個(gè)信號集合到當前進(jìn)程的阻塞集合之中. SIG_UNBLOCK:從當前的阻塞集合之中刪除一個(gè)信號集合. SIG_SETMASK:將當前的信號集合設置為信號阻塞集合. 以一個(gè)實(shí)例來(lái)解釋使用這幾個(gè)函數. #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) { /*阻塞信號,我們不希望保存原來(lái)的集合所以參數為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); } 程序在運行的時(shí)候我們要使用Ctrl+C來(lái)結束.如果我們在第一計算的時(shí)候發(fā)出SIGINT信號 ,由于信號已經(jīng)屏蔽了,所以程序沒(méi)有反映.只有到信號被取消阻塞的時(shí)候程序才會(huì )結束. 注意我們只要發(fā)出一次SIGINT信號就可以了,因為信號屏蔽只是將信號加入到信號阻塞 集合之中,并沒(méi)有丟棄這個(gè)信號.一旦信號屏蔽取消了,這個(gè)信號就會(huì )發(fā)生作用. 有時(shí)候我們希望對信號作出及時(shí)的反映的,比如當擁護按下Ctrl+C時(shí),我們不想什么事情 也不做,我們想告訴用戶(hù)你的這個(gè)操作不好,請不要重試,而不是什么反映也沒(méi)有的. 這個(gè) 時(shí)候我們要用到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); } 這個(gè)函數和結構看起來(lái)是不是有點(diǎn)恐怖呢.不要被這個(gè)嚇著(zhù)了,其實(shí)這個(gè)函數的使用相當 簡(jiǎn)單的.我們先解釋一下各個(gè)參數的含義. signo很簡(jiǎn)單就是我們要處理的信號了,可以是 任何的合法的信號.有兩個(gè)信號不能夠使用(SIGKILL和SIGSTOP). act包含我們要對這個(gè) 信號進(jìn)行如何處理的信息.oact更簡(jiǎn)單了就是以前對這個(gè)函數的處理信息了,主要用來(lái)保 存信息的,一般用NULL就OK了. 信號結構有點(diǎn)復雜.不要緊我們慢慢的學(xué)習. sa_handler是一個(gè)函數型指針,這個(gè)指針指向一個(gè)函數,這個(gè)函數有一個(gè)參數.這個(gè)函數就 是我們要進(jìn)行的信號操作的函數. sa_sigaction,sa_restore和sa_handler差不多的,只 是參數不同罷了.這兩個(gè)元素我們很少使用,就不管了. sa_flags用來(lái)設置信號操作的各個(gè)情況.一般設置為0好了.sa_mask我們已經(jīng)學(xué)習過(guò)了 在使用的時(shí)候我們用sa_handler指向我們的一個(gè)信號操作函數,就可以了.sa_handler有 兩個(gè)特殊的值:SIG_DEL和SIG_IGN.SIG_DEL是使用缺省的信號操作函數,而SIG_IGN是使用 忽略該信號的操作函數. 這個(gè)函數復雜,我們使用一個(gè)實(shí)例來(lái)說(shuō)明.下面這個(gè)函數可以捕捉用戶(hù)的CTRL+C信號.并輸 出一個(gè)提示語(yǔ)句. #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函數而沒(méi)有使用fprintf函數.是因為 我們要考慮到下面這種情況.如果我們在信號操作的時(shí)候又有一個(gè)信號發(fā)生,那么程序該 如何運行呢? 為了處理在信號處理函數運行的時(shí)候信號的發(fā)生,我們需要設置sa_mask成 員. 我們將我們要屏蔽的信號添加到sa_mask結構當中去,這樣這些函數在信號處理的時(shí) 候就會(huì )被屏蔽掉的. 3。其它信號函數 由于信號的操作和處理比較復雜,我們再介紹幾個(gè)信號操作函數.
#include <unistd.h> #include <signal.h> int pause(void); int sigsuspend(const sigset_t *sigmask); pause函數很簡(jiǎn)單,就是掛起進(jìn)程直到一個(gè)信號發(fā)生了.而sigsuspend也是掛起進(jìn)程只是在 調用的時(shí)候用sigmask取代當前的信號阻塞集合. #include <sigsetjmp> int sigsetjmp(sigjmp_buf env,int val); void siglongjmp(sigjmp_buf env,int val); 還記得goto函數或者是setjmp和longjmp函數嗎.這兩個(gè)信號跳轉函數也可以實(shí)現程序的 跳轉讓我們可以從函數之中跳轉到我們需要的地方. 由于上面幾個(gè)函數,我們很少遇到,所以只是說(shuō)明了一下,詳細情況請查看聯(lián)機幫助. 4。一個(gè)實(shí)例 還記得我們在守護進(jìn)程創(chuàng )建的哪個(gè)程序嗎?守護進(jìn)程在這里我們把那個(gè) 程序加強一下. 下面這個(gè)程序會(huì )在也可以檢查用戶(hù)的郵件.不過(guò)提供了一個(gè)開(kāi)關(guān),如果用 戶(hù)不想程序提示有新的郵件到來(lái),可以向程序發(fā)送SIGUSR2信號,如果想程序提供提示可以 發(fā)送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 的默任個(gè)人的郵箱地址是 /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 arrived7\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); } 信號操作是一件非常復雜的事情,比我們想象之中的復雜程度還要復雜,如果你想徹底的 弄清楚信號操作的各個(gè)問(wèn)題,那么除了大量的練習以外還要多看聯(lián)機手冊.不過(guò)如果我們 只是一般的使用的話(huà),有了上面的幾個(gè)函數也就差不多了. 我們就介紹到這里了.
[color=blue:24e76417e9][b:24e76417e9]6)Linux程序設計入門(mén)--消息管理[/b:24e76417e9][/color:24e76417e9] 前言:Linux下的進(jìn)程通信(IPC) Linux下的進(jìn)程通信(IPC) POSIX無(wú)名信號量 System V信號量 System V消息隊列 System V共享內存 1。POSIX無(wú)名信號量 如果你學(xué)習過(guò)操作系統,那么肯定熟悉PV操作了.PV操作是原子 操作.也就是操作是不可以中斷的,在一定的時(shí)間內,只能夠有一個(gè)進(jìn)程的代碼在CPU上面 執行.在系統當中,有時(shí)候為了順利的使用和保護共享資源,大家提出了信號的概念. 假設 我們要使用一臺打印機,如果在同一時(shí)刻有兩個(gè)進(jìn)程在向打印機輸出,那么最終的結果會(huì ) 是什么呢.為了處理這種情況,POSIX標準提出了有名信號量和無(wú)名信號量的概念,由于Li nux只實(shí)現了無(wú)名信號量,我們在這里就只是介紹無(wú)名信號量了. 信號量的使用主要是用 來(lái)保護共享資源,使的資源在一個(gè)時(shí)刻只有一個(gè)進(jìn)程所擁有.為此我們可以使用一個(gè)信號 燈.當信號燈的值為某個(gè)值的時(shí)候,就表明此時(shí)資源不可以使用.否則就表>示可以使用. 為了提供效率,系統提供了下面幾個(gè)函數 POSIX的無(wú)名信號量的函數有以下幾個(gè): #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創(chuàng )建一個(gè)信號燈,并初始化其值為value.pshared決定了信號量能否在幾個(gè)進(jìn)程 間共享.由于目前Linux還沒(méi)有實(shí)現進(jìn)程間共享信號燈,所以這個(gè)值只能夠取0. sem_dest roy是用來(lái)刪除信號燈的.sem_wait調用將阻塞進(jìn)程,直到信號燈的值大于0.這個(gè)函數返回 的時(shí)候自動(dòng)的將信號燈的值的件一.sem_post和sem_wait相反,是將信號燈的內容加一同 時(shí)發(fā)出信號喚醒等待的進(jìn)程..sem_trywait和sem_wait相同,不過(guò)不阻塞的,當信號燈的值 為0的時(shí)候返回EAGAIN,表示以后重試.sem_getvalue得到信號燈的值. 由于Linux不支持,我們沒(méi)有辦法用源程序解釋了. 這幾個(gè)函數的使用相當簡(jiǎn)單的.比如我們有一個(gè)程序要向一個(gè)系統打印機打印兩頁(yè).我們 首先創(chuàng )建一個(gè)信號燈,并使其初始值為1,表示我們有一個(gè)資源可用.然后一個(gè)進(jìn)程調用se m_wait由于這個(gè)時(shí)候信號燈的值為1,所以這個(gè)函數返回,打印機開(kāi)始打印了,同時(shí)信號燈 的值為0 了. 如果第二個(gè)進(jìn)程要打印,調用sem_wait時(shí)候,由于信號燈的值為0,資源不可 用,于是被阻塞了.當第一個(gè)進(jìn)程打印完成以后,調用sem_post信號燈的值為1了,這個(gè)時(shí)候 系統通知第二個(gè)進(jìn)程,于是第二個(gè)進(jìn)程的sem_wait返回.第二個(gè)進(jìn)程開(kāi)始打印了. 不過(guò)我們可以使用線(xiàn)程來(lái)解決這個(gè)問(wèn)題的.我們會(huì )在后面解釋什么是線(xiàn)程的.編譯包含上 面這幾個(gè)函數的程序要加上 -lrt選賢,以連接librt.so庫 2。System V信號量 為了解決上面哪個(gè)問(wèn)題,我們也可以使用System V信號量.很幸運的 是Linux實(shí)現了System V信號量.這樣我們就可以用實(shí)例來(lái)解釋了. System V信號量的函 數主要有下面幾個(gè). #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; /* 使用那一個(gè)信號 */ short sem_op; /* 進(jìn)行什么操作 */ short sem_flg; /* 操作的標志 */ }; ftok函數是根據pathname和proj來(lái)創(chuàng )建一個(gè)關(guān)鍵字.semget創(chuàng )建一個(gè)信號量.成功時(shí)返回 信號的ID,key是一個(gè)關(guān)鍵字,可以是用ftok創(chuàng )建的也可以是IPC_PRIVATE表明由系統選用 一個(gè)關(guān)鍵字. nsems表明我們創(chuàng )建的信號個(gè)數.semflg是創(chuàng )建的權限標志,和我們創(chuàng )建一個(gè) 文件的標志相同. semctl對信號量進(jìn)行一系列的控制.semid是要操作的信號標志,semnum是信號的個(gè)數,cm d是操作的命令.經(jīng)常用的兩個(gè)值是:SETVAL(設置信號量的值)和IPC_RMID(刪除信號燈). arg是一個(gè)給cmd的參數. semop是對信號進(jìn)行操作的函數.semid是信號標志,spos是一個(gè)操作數組表明要進(jìn)行什么 操作,nspos表明數組的個(gè)數. 如果sem_op大于0,那么操作將sem_op加入到信號量的值中 ,并喚醒等待信號增加的進(jìn)程. 如果為0,當信號量的值是0的時(shí)候,函數返回,否則阻塞直 到信號量的值為0. 如果小于0,函數判斷信號量的值加上這個(gè)負值.如果結果為0喚醒等待 信號量為0的進(jìn)程,如果小與0函數阻塞.如果大于0,那么從信號量里面減去這個(gè)值并返回 .. 下面我們一以一個(gè)實(shí)例來(lái)說(shuō)明這幾個(gè)函數的使用方法.這個(gè)程序用標準錯誤輸出來(lái)代替我 們用的打印機. #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) { /* 初始話(huà)信號燈結構 */ sem->sem_num=semnum; sem->sem_op=semop; sem->sem_flg=semflg; } int del_semaphore(int semid) { /* 信號燈并不隨程序的結束而被刪除,如果我們沒(méi)刪除的話(huà)(將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 表示由系統選擇一個(gè)關(guān)鍵字來(lái)創(chuàng )建 */ /* 創(chuàng )建以后信號燈的初始值為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); /* 開(kāi)始的時(shí)候有一個(gè)系統資源(一個(gè)標準錯誤輸出) */ 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); } /* 創(chuàng )建一個(gè)進(jìn)程鏈 */ 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; /* 這里要求資源,進(jìn)入原子操作 */ 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!=‘‘)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)); } /* 不能夠在其他進(jìn)程反問(wèn)信號燈的時(shí)候,我們刪除了信號燈 */ 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); } 信號燈的主要用途是保護臨界資源(在一個(gè)時(shí)刻只被一個(gè)進(jìn)程所擁有). 3。SystemV消息隊列 為了便于進(jìn)程之間通信,我們可以使用管道通信 SystemV也提供了 一些函數來(lái)實(shí)現進(jìn)程的通信.這就是消息隊列. #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; /* 消息類(lèi)型 */ ....... /* 其他數據類(lèi)型 */ } msgget函數和semget一樣,返回一個(gè)消息隊列的標志.msgctl和semctl是對消息進(jìn)行控制 .. msgsnd和msgrcv函數是用來(lái)進(jìn)行消息通訊的.msgid是接受或者發(fā)送的消息隊列標志. msgp是接受或者發(fā)送的內容.msgsz是消息的大小. 結構msgbuf包含的內容是至少有一個(gè) 為msgtype.其他的成分是用戶(hù)定義的.對于發(fā)送函數msgflg指出緩沖區用完時(shí)候的操作. 接受函數指出無(wú)消息時(shí)候的處理.一般為0. 接收函數msgtype指出接收消息時(shí)候的操作.
如果msgtype=0,接收消息隊列的第一個(gè)消息.大于0接收隊列中消息類(lèi)型等于這個(gè)值的第 一個(gè)消息.小于0接收消息隊列中小于或者等于msgtype絕對值的所有消息中的最小一個(gè)消 息. 我們以一個(gè)實(shí)例來(lái)解釋進(jìn)程通信.下面這個(gè)程序有server和client組成.先運行服務(wù) 端后運行客戶(hù)端. 服務(wù)端 server.c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/stat.h> #include <sys/msg.h> #define MSG_FILE "server.c" #define BUFFER 255 #define PERM S_IRUSR|S_IWUSR struct msgtype { long mtype; char buffer[BUFFER+1]; }; int main() { struct msgtype msg; key_t key; int msgid; if((key=ftok(MSG_FILE,‘a(chǎn)‘))==-1) { fprintf(stderr,"Creat Key Error:%s\a\n",strerror(errno)); exit(1); } if((msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL))==-1) { fprintf(stderr,"Creat Message Error:%s\a\n",strerror(errno)); exit(1); } while(1) { msgrcv(msgid,&msg,sizeof(struct msgtype),1,0); fprintf(stderr,"Server Receive:%s\n",msg.buffer); msg.mtype=2; msgsnd(msgid,&msg,sizeof(struct msgtype),0); } exit(0); } ---------------------------------------------------------------------------- ---- 客戶(hù)端(client.c) #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h> #define MSG_FILE "server.c" #define BUFFER 255 #define PERM S_IRUSR|S_IWUSR struct msgtype { long mtype; char buffer[BUFFER+1]; }; int main(int argc,char **argv) { struct msgtype msg; key_t key; int msgid; if(argc!=2) { fprintf(stderr,"Usage:%s string\n\a",argv[0]); exit(1); } if((key=ftok(MSG_FILE,‘a(chǎn)‘))==-1) { fprintf(stderr,"Creat Key Error:%s\a\n",strerror(errno)); exit(1); } if((msgid=msgget(key,PERM))==-1) { fprintf(stderr,"Creat Message Error:%s\a\n",strerror(errno)); exit(1); } msg.mtype=1; strncpy(msg.buffer,argv[1],BUFFER); msgsnd(msgid,&msg,sizeof(struct msgtype),0); memset(&msg,‘‘,sizeof(struct msgtype)); msgrcv(msgid,&msg,sizeof(struct msgtype),2,0); fprintf(stderr,"Client receive:%s\n",msg.buffer); exit(0); |