linux操作系統下c語言編程入門(1)

火星人 @ 2014-03-26 , reply:0



(一)目錄介紹

1)Linux程序設計入門--基礎知識
2)Linux程序設計入門--進程介紹
3)Linux程序設計入門--文件操作
4)Linux程序設計入門--時間概念
5)Linux程序設計入門--信號處理
6)Linux程序設計入門--消息管理
7)Linux程序設計入門--線程操作
8)Linux程序設計入門--網路編程
9)Linux下C開發工具介紹

(二)具體內容

1)Linux程序設計入門--基礎知識
Linux下C語言編程基礎知識
前言:
這篇文章介紹在LINUX下進行C語言編程所需要的基礎知識.在這篇文章當中,我們將
會學到以下內容:
源程序編譯
Makefile的編寫
程序庫的鏈接
程序的調試
頭文件和系統求助
----------------------------------------------------------------------------
----
1.源程序的編譯
在Linux下面,如果要編譯一個C語言源程序,我們要使用GNU的gcc編譯器. 下面我們
以一個實例來說明如何使用gcc編譯器.
假設我們有下面一個非常簡單的源程序(hello.c):
QUOTE:
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的編寫
假設我們有下面這樣的一個程序,源代碼如下:
QUOTE:
/* 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有三個非常有用的變數.分別是$@,$^,$<代表的意義分別是:
$@--目標文件,$^--所有的依賴文件,$<--第一個依賴文件.
如果我們使用上面三個變數,那麼我們可以簡化我們的Makefile文件為:
# 這是簡化后的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 $<
經過簡化后我們的Makefile是簡單了一點,不過人們有時候還想簡單一點.這裡我們學習
一個Makefile的預設規則
..c.o:
gcc -c $<
這個規則表示所有的 .o文件都是依賴與相應的.c文件的.例如mytool.o依賴於mytool.c
這樣Makefile還可以變為:
# 這是再一次簡化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
..c.o:
gcc -c $<
好了,我們的Makefile 也差不多了,如果想知道更多的關於Makefile規則可以查看相應的
文檔.
3.程序庫的鏈接
試著編譯下面這個程序
/* temp.c */
#include
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 系統就會輸出著
函數的詳細解釋的.和這個函數所在的頭文件說明了. 如果我們要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
pid_t getpid(void);
pid_t getppid(void);
進程是為程序服務的,而程序是為了用戶服務的.系統為了找到進程的用戶名,還為進程和
用戶建立聯繫.這個用戶稱為進程的所有者.相應的每一個用戶也有一個用戶ID.通過系統
調用getuid可以得到進程的所有者的ID.由於進程要用到一些資源,而Linux對系統資源是
進行保護的,為了獲取一定資源進程還有一個有效用戶ID.這個ID和系統的資源使用有關
,涉及到進程的許可權. 通過系統調用geteuid我們可以得到進程的有效用戶ID. 和用戶ID
相對應進程還有一個組ID和有效組ID系統調用getgid和getegid可以分別得到組ID和有效
組ID
#include
#include

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
#include

struct passwd *getpwuid(uid_t uid);
下面我們學習一個實例來實踐一下上面我們所學習的幾個函數:
#include
#include
#include
#include
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

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

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
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
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
/* 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
#include
#include
#include

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
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
#include
#include
#include
#include
#include
#include
#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

int access(const char *pathname,int mode);
pathname:是文件名稱,mode是我們要判斷的屬性.可以取以下值或者是他們的組合.
R_OK文件可以讀,W_OK文件可以寫,X_OK文件可以執行,F_OK文件存在.當我們測試成功時
,函數返回0,否則如果有一個條件不符時,返回-1.
如果我們要獲得文件的其他屬性,我們可以使用函數stat或者fstat.
#include
#include
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

char *getcwd(char *buffer,size_t size);
我們提供一個size大小的buffer,getcwd會把我們當前的路徑考到buffer中.如果buffer
太小,函數會返回-1和一個錯誤號.
Linux提供了大量的目錄操作函數,我們學習幾個比較簡單和常用的函數.
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
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- closedir(dirp);
exit(1);
}
4。管道文件
Linux提供了許多的過濾和重定向程序,比如more cat
等等.還提供了< > |
道這種特殊的文件.系統調用pipe可以創建一個管道.
#include

int pipe(int fildes[2]);
pipe調用可以創建一個管道(通信緩衝區).當調用成功時,我們可以訪問文件描述符fild
es[0],fildes[1].其中fildes[0]是用來讀的文件描述符,而fildes[1]是用來寫的文件描
述符.
在實際使用中我們是通過創建一個子進程,然後一個進程寫,一個進程讀來使用的.
關於進程通信的詳細情況請查看進程通信
#include
#include
#include
#include
#include
#include
#include
#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

int dup2(int oldfd,int newfd);
dup2將用oldfd文件描述符來代替newfd文件描述符,同時關閉newfd文件描述符.也就是說
,
所有向newfd操作都轉到oldfd上面.下面我們學習一個例子,這個例子將標準輸出重定向
到一個文件.
#include
#include
#include
#include
#include
#include
#include
#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等等常用的文件操作
命令了.
想不想自己寫幾個試一試呢?



[火星人 via ] linux操作系統下c語言編程入門(1)已經有83次圍觀

http://www.coctec.com/docs/linux/show-post-190409.html