標籤(空格分隔): Linux
Author:atao
程序映像佈局
(高)命令行參數,環境變量->棧->共享內存->堆->BSS->DATA->代碼段(低)
第一章 linux系統編程概述
一、Linux系統層次結構
1、APP負責系統的功能與行爲
2、OS負責與硬件交互
3、結構如下
應用程序(APP)->C庫->Linux系統調用接口->Linux操作系統(OS)->硬件接口
二、基本概念
1、進程:一個正在執行的程序實例,有自己的地址空間和執行狀態
特點:1)進程是一個實體
2)進程是一個“執行中的程序”
2、線程:是“進程”中某個單一順序的控制流。
特點:1)一個進程中允許有多個線程執行
2)線程是進程中的一個實體
三、Linux出錯處理
1、strerror函數
原型:char *strerror(int errnum);
int errnum;
errnum = errno;
printf("錯誤提示%s\n", strerror(errnum));
2.perror函數
原型:void perror(const char *str);
perror("open file failed:");
四、時間函數
1.實例
#include <stdio.h>
#include <time.h>
// clock_t clock(void);
int main(void)
{
clock_t clk;
int i;
for(i = 0; i < 100000000; i++);
clk = clock();
printf("clock = %d, secs = %f\n", clk,
clk * 1.0 /CLOCKS_PER_SEC);//將毫秒轉換爲秒
printf("CLOCKS_PER_SEC = %d\n", CLOCKS_PER_SEC);
return 0;
}
2.time命令測試程序運行時間
- time 可執行文件
3.strace 命令跟蹤一種程序系統調用使用情況
- strace 可執行文件
第二章 系統I/O操作
一、Linux文件
1、普通文件(regular file)
2、目錄:包含了其他文件的名字以及指向與這些文件在關信息的指針
3、設備文件(/dev):字符特殊文件、塊特殊文件
4、FIFO:用於進程間的通信,有時也將其稱爲命名管道
5、套接口(socket):用於宿主機間網絡通信,也可用於在一臺宿主機上的進程間的通信
常用的五個基本函數
open、close、read、write和ioctl
二、文件描述符
1、Linux內核是通過文件描述符區分和引用文件的。
2、通常,一個進程啓動時,會打開3個文件
STDIN_FILENO //對應的描述符爲:0(標準輸入)
STDOUT_FILENO //對應的描述符爲:1(標準輸出)
STDERR_FILENO //對應的描述符爲:2(標準錯誤處理)
三、文件操作
1、文件處理函數
– open – 打開或創建一個文件
– creat – 建立一個空文件
– close – 關閉一個文件
– read – 從文件讀入數據
– write – 向文件寫入一個數據
– lseek – 在文件中移動讀寫位置
– unlink – 刪除一個文件
– remove – 刪除一個文件本身
– fcntl – 控制一個文件屬性
– link –創建一個硬鏈接
– symlink –創建一個軟鏈接
– mkdir – 創建一個目錄
– rmdir – 刪除一個目錄
– chdir – 切換一個目錄
– getcwd – 把當前目錄的名字寫到給定的緩衝區buf裏
– opendir – 打開一個目錄並建立一個目錄流
– readdir – 指針指向的結構裏保存着目錄流drip中下一個目錄項的有關資料
– telldir – 記錄着一個目錄流裏的當前位置
– seekdir – 設置目錄流dir的目錄項指針位置
– closedir – 關閉一個目錄流並釋放與之關聯的資源
– stat – 返回指定文件的詳細情況
– fstat – 根據文件描述符獲取狀態
– lstat – 根據文件名來獲取狀態(常用)
2、模式位(flags)
– O_WRONLY //以只寫方式打開文件
– O_RDONLY //以只讀方式打開文件
– O_RDWR //以可讀寫方式打開文件
– O_CREAT //若欲打開的文件不存在則自動建立該文件
– O_EXCL //表示可執行,不能與O_CREAT同時設置,否則會出錯
– O_TRUNC //覆蓋已存在的文件
– O_APPEND //表示追加讀寫
3、權限位(mode)
Linux總共用5個八進制數字來表示文件的各種權限,0000
第一位:表示設置用戶ID
第二位:表示設置組ID
第三位:表示用戶自己的權限位,即所有者權限
第四位:表示組的權限
第五位:表示其他人的權限
4、返回值
1)錯誤代碼,成功打開文件返回文件描述符,否則返回一個負數。
2)失敗的原因可以用全局的errno來指明
EEXIST //表示文件已存在
EACCESS //表示文件
ENOTDIR //表示不是目錄
5、lseek函數中的whence參數
– SEEK_SET 從文件頭增加
– SEEK_CUR 從當前位置增加
– SEEK_END 從文件尾後增加
6、access判斷當前里程是否有某個權限
如:access(argv[1], F_OK);//檢測該文件是否存在
權限參數:
R_OK //有讀權限
W_OK //寫權限
X_OK //執行權限
F_OK //測試文件是否存在
7、用法
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
// ssize_t write(int fd, const void *buf, size_t count);
int main(void)
{
int fd = -1;
char buf[128] = "Hello world";
ssize_t count;
char ch = 0;
// 打開文件
fd = open("test", O_WRONLY);
if(-1 == fd)
{
perror("open failed");
return -1;
}
// 寫文件
count = write(fd, buf, strlen(buf));
if(-1 == count)
{
perror("write failed");
goto _out;
}
else if(0 == count)
{
printf("write nothing\n");
}
// 顯示寫入的內容
printf("%s\n", buf);
// 製造文件空洞
lseek(fd, 10000, SEEK_SET);
write(fd, &ch, 1);
_out:
// 關閉文件
close(fd);
return 0;
}
8、fcntl文件屬性控制函數
原型:int fcntl(int fd, int cmd, struct flock *lock);
cmd參數:
F_GETLK //獲取文件鎖
F_SETLK //設置文件鎖
通常爲整個文件加鎖的方法是:
l_whence = SEEK_SET;
l_start = 0;
l_len = 0;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#if 0
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
struct flock {
...
short l_type; /* Type of lock: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock
(F_GETLK only) */
...
};
#endif
int set_lock(int fd, short type);
int main(int argc, char **argv)
{
int fd;
char *p;
/*
命令行提供 4 個參數:
1. 命令
2. 文件名
3. l/u(l - 加寫鎖, u - 不加寫鎖)
4. 要寫入文件中的字符串
*/
if((argc != 4) || ((argv[2][0] != 'l') &&
(argv[2][0] != 'u')) || (argv[2][1] != '\0'))
{
printf("Usage: %s filename l/u(lock/unlock) string\n", argv[0]);
return -1;
}
fd = open(argv[1], O_WRONLY | O_APPEND);
if(-1 == fd)
{
perror("open failed");
return -1;
}
if(argv[2][0] == 'l')
{
// 加寫鎖
if(-1 == set_lock(fd, F_WRLCK))
{
goto _out;
}
}
// 開始寫文件
p = argv[3];
while(*p)
{
write(fd, p, 1);
fprintf(stderr, "%c", *p); // stderr 標準出錯, 不帶緩衝
//fprintf(stdout, "%c", *p); // stdout 標準輸出, 行緩衝
sleep(1);
p++;
}
if(argv[2][0] == 'l')
{
// 解鎖
if(-1 == set_lock(fd, F_UNLCK))
{
goto _out;
}
}
_out:
close(fd);
return 0;
}
// 設置文件鎖: F_RDLCK 讀鎖, F_WRLCK 寫鎖, F_UNLCK 解鎖
// 返回 0 成功, -1 失敗
int set_lock(int fd, short type)
{
// lock 必須先清 0, 否則獲取鎖時會產生無效參數的錯誤
struct flock lock = {0};
int ret = 0;
// 獲取文件鎖狀態
if(-1 == fcntl(fd, F_GETLK, &lock))
{
perror("fcntl getlk failed");
ret = -1;
goto _out;
}
// 對整個文件設置鎖
lock.l_type = type;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
// 設置文件鎖
while(-1 == fcntl(fd, F_SETLK, &lock));
_out:
return ret;
}
9.文件描述符變爲非阻塞
fcntl( sockfd, F_SETFL, O_NONBLOCK);
// sockfd 是要改變狀態的文件描述符.
// F_SETFL 表明要改變文件描述符的狀態
// O_NONBLOCK 表示將文件描述符變爲非阻塞的.
四、I/O模型
1.FILE結構提供了三各緩衝方式
1)全緩衝:磁盤文件通常是完全緩衝的
2)行緩衝:與終端相關的文件是行緩衝的
3)不緩衝:標準出錯是不緩衝的,如stderr
2.高效處理I/O複用的函數:select函數
原型:int select(int numfds, fd_set *readfds, fd_set *writefds, \
fd_set *exeptfds, struct timeval *timeout);
參數說明:
readfds:select監視的可讀文件句柄集合。
writefds: select監視的可寫文件句柄集合。
exceptfds:select監視的異常文件句柄集合。
timeout:本次select()的超時結束時間。
相關的宏解釋:
FD_ZERO(fd_set *fdset):清空fdset與所有文件句柄的聯繫。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd與fdset的聯繫。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd與fdset的聯繫。
FD_ISSET(int fd, fdset *fdset):檢查fdset聯繫的文件句柄fd是否可讀寫,>0表示可讀寫。
關於超時的設置:
struct timeval
{
long tv_sec; /* seconds */設置秒
long tv_usec; /* microseconds */設置微秒
};
實用舉例:
main()
{
int sock;
FILE *fp;
struct fd_set fds;
struct timeval timeout; //用於設置超時時間
char buffer[256]={0}; //256字節的接收緩衝區
//select等待3秒,3秒輪詢,要非阻塞就置0
timeout.tv_sec = 3;
timeout.tv_usec = 0;
/* 假定已經建立TCP/UDP連接 */
sock=socket(...);
bind(...);
fp=fopen(...);
while(1)
{
FD_ZERO(&fds);
//每次循環都要清空集合,否則不能檢測描述符變化
FD_SET(sock,&fds); //添加通信描述符
FD_SET(fp,&fds); //添加打開文件描述符
maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1
switch(select(maxfdp,&fds,&fds,NULL,&timeout))//select使用
{
case -1:
exit(-1);
break; //select錯誤,退出程序
case 0:
break; //再次輪詢
default:
if(FD_ISSET(sock,&fds))
//測試sock是否可讀,即是否網絡上有數據
{
recvfrom(sock,buffer,256,.....);//接受網絡數據 if(FD_ISSET(fp,&fds)) //測試文件是否可寫
{
fwrite(fp,buffer...);//寫入文件
}
buffer清空;
}// end if break;
}// end switch
}//end while
}//end main
3.高效處理I/O複用的函數:poll函數
原型:int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
返回值說明:
大於0,代表滿足響應事件的文件描述符的個數
等於0,超時,代表在規定事件內沒有事件發生
小於0,函數使用出錯
參數說明:
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 實際發生了的事件 */
};
nfds //給出要監視的描述符的數目
timeout //是一個以毫秒爲單位的時間
/*
如果timeout爲0,poll不阻塞
如果timeout 爲-1,poll永遠不會超時,直到有事件返回
*/
關於ufds參數的說明:
events //包含所有該文件描述符需要監視的正常事件(讀/寫).
revents //包含poll返回時該文件描述符已發生的事件(包含異常事件).
events和revents是通過對各種事件的標誌符進行邏輯或(|)運算構建的.
事件標誌定義:
POLLIN 數據可讀
POLLOUT 數據可寫
POLLHUP 設備已經斷開
POLLRPI 高優先級數據可讀
POLLERR 出現異常
POLLNVAL 文件描述符無效
五、進程編程
1.常見進程控制函數
- fork //創建一個新進程
- exec //用一個新進程去執行一個命令行
- exit //正常退出進程
- abort //非正常退出一個進程
- kill //殺死進程或向一個進程發送信號
- wait //等待子進程結束
- sleep //將當前進程休眠一段時間
- getpid //取得當前進程編號
- getppid //取得父進程編號
2.wait 和 waitpid
– wait將暫停父進程直到它有一個子進程結束.
– waitpid 可以指定等待哪一個子進程結束.
– 成功則返回結束的子進程號.
1)函數原型:
– pid_t wait (int *status)
– pid_t waitpid (pid_t pid, int *status, int options);
2)參數與返回值:
– status返回子進程退出時的狀態.
– pid > 0 等待指定pid的子進程結束
– pid = -1 等待任何一個子進程結束
– options 爲0,阻塞等待.
如果爲WNOHANG,不阻塞,沒有等到子進程結束返回0.
3)退出時的狀態(status)說明:
– WIFEXITED(status)
• 如果子進程正常結束,宏非零
• WEXISTSTATUS(status)
WIFEXITED非零,宏返回子進程退出碼低8位
– WIFSIGNALED(status)
• 子進程被信號終止,宏非零
• WTERMSIG(status)
WIFSIGNALED非零,宏返回終止子進程的信號編碼
– WIFSOPPED(status)
• 子進程被信號停止,宏非零
• WSTOPSIG(status)
WIFSOPPED非零,宏返回停止子進程的信號編碼
3.exec函數族:
相關函數:
execl\execv\execle\execve\execlp\execvp
說明:其中只有execve是真正意義上的系統調用,其它都是在此基礎上經過包裝的庫函數
4.system函數
格式:system(“控制檯命令”);
六、守護進程
1.特性:能在後臺運行的進程
2.守護進程的編程要點
1)調用fork(),將讓父進程終止
實現:if(pid = fork()) {exit(0);}
2)脫離控制終端,登錄會話和進程組
實現:調用setsid()函數使進程成爲會話組長
3)禁止進程重新打開控制終端
實現:if(pid = fork()) {exit(0);}//結束第一子進程,創建第二子進程
4)關閉打開的文件描述符
實現:for(i = 0; 關閉打開的文件描述符; i++) {close(i);}
5)改變當前工作目錄爲根目錄
實現:chdir("/");
6)重設文件創建掩模
實現:umask(0);
第三章 進程間通信
一、無名管道(pipe)和命名管道(fifo)
1.無名管道的讀寫
實現:
/*目的:父進程寫,子進程讀*/
int pipe_fd[2];
pipe(pipe_fd);
//fork()一個進程
/*這裏是子進程
1.關閉寫端close(pipe_fd[1]);
2.sleep(2);讓父進程先寫
3.read(pipe_fd[0], buf, sizeof(buf));子進程讀
4.關閉讀端close(pipe_fd[0]);
5.exit(0);
*/
/*這裏是父親進程
1.關閉讀端close(pipe_fd[0]);
2.write(pipe_fd[1], buf, sizeof(buf));父進程寫
3.關閉寫端close(pipe_fd[1]);
4.waitpid(pid, NULL, 0);等待子進程結束
5.exit(0);
*/
2.創建有名管道
原型:int mkfifo(const char *pathname,mode_t mode);
/*創建有名管道*/
if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL|O_RDWR)<0)&&(errno!=EEXIST))
{
perror("mkfifo failed:");
exit(1);
}
/*
errno != EEXIST //說明不是文件已經存在錯誤
*/
/*打開管道*/
fd=open(FIFO_SERVER,O_WRONLY |O_NONBLOCK,0);
if(fd==-1)
{
perror("open failed:");
exit(1);
}
/*
1.設置了O_NONBLOCK 說明要求無法滿足時不阻塞,立即出錯返回(ENXIO).
2.未設置O_NONBLOCK 說明訪問要求無法滿足時進程阻塞,如讀取FIFO爲空時
*/
close(fd); //關閉管道
unlink(FIFO_SERVER); //刪除文件
第四章 信號與定時器
一、常用命令
1.查看當前系統的所有信號(kill -l)
2.查看當前系統終端的可執行命令(stty -a)
二、signal信號處理機制
1.調用signal()註冊一個信號捕捉函數
原型爲:
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
參數說明:
第一個參數(signum):表示要捕捉的信號
第二個參數(handler):是函數指針,表示要對該信號進行捕捉的函數
注意第二個參數:
1)SIG_IGN //表示忽略捕捉到的這個信號
2)SIG_DEF //採用默認處理
3)SIGKILL和SIGSTOP信號即不能忽略也不能被捕捉
三、用程序發送信號
1.向其他進程發送信號
1)kill -9 進程號 //在終端殺死指定進程
2)原型:int kill(pid_t pid, int sig);//編程殺進程
實現:kill(getppid(), SIGTERM);//殺死父親
2.向自己發送信號
1)原型:int raise(int sig);
實現:
- raise(SIGTERM);//自殺
- raise(SIGSTOP);//自閉
小技巧:子進程自殺或自閉後,父進程通過wait可以檢查子進程的狀態。
2)void abort(void);
功能描述:向進程發送SIGABORT信號,默認情況下進程會異常退出
3)int pause(void);
功能描述:將調用進程掛起直到捕捉到信號爲止
3.sigqueue信號發送函數
原型:int sigqueue(pid_t pid,int sig, const union sigval value);
功能描述:即可以發送信號,並且能傳遞附加的信息.
參數說明:
第一個參數pid爲接收信號的進程;
第二個參數sig爲要發送的信號;
第三個參數value爲一整型與指針類型的聯合體:
union sigval
{
int sival_int;
void *sival_ptr;
};
作用:由sigqueue函數發送的信號的第3個參數value的值,可以被進程的信號處理函數的第2個參數info->si_ptr接收到。
4. sigprocmask信號阻塞
原型:
int sigprocmask(int how,const sigset_t *set, sigset_t *oldset);
功能描述:函數sigprocmask是全程阻塞,在sigprocmask中設置了阻塞集合後,被阻塞的信號將不能再被信號處理函數捕捉,直到重新設置阻塞信號集合。
參數說明:
第一個參數how的值爲如下3者之一:
a:SIG_BLOCK ,將參數2的信號集合添加到進程原有的阻塞信號集合中
b:SIG_UNBLOCK ,從進程原有的阻塞信號集合移除參數2中包含的信號
c:SIG_SET,重新設置進程的阻塞信號集爲參數2的信號集
第二個參數set爲阻塞信號集
第三個參數oldset是傳出參數,存放進程原有的信號集。
四、計時器與信號
- Linux下的兩個睡眠函數
原型:
unsigned int sleep(unsigned int seconds);
void usleep(unsigned long usec);
功能描述:
函數sleep讓進程睡眠seconds秒,函數usleep讓進程睡眠usec毫秒。
注意:
因爲sleep在內部是用alarm實現的,所以在程序中最好不要sleep與alarm混用,以免造成混亂。
2.定時器alarm()函數
原型:
unsigned int alarm(uinsigned int seconds);
功能描述:
實際是一個延時器,好比sleep,兩者用其一。
3.定時器setitimer()
原型:int setitimer(int which, const struct itimerval *value,
struct itimerval *ovalue));
參數說明:
第一個參數(which):指定了3種類型計時器
ITIMER_REAL(真實計時器)
ITIMER_VITUAL(虛擬計時器)
ITIMER_PROF(實用計時器)
第二個參數(value):value爲一結構體的傳入參數,指定該計時器的初始間隔時間和重複間隔時間。
第三個參數(ovalue):ovalue爲一結構體傳出參數,用於傳出以前的計時器時間設置。
struct itimerval
{
struct timeval it_interval; /* next value */ //重複間隔
struct timeval it_value; /* current value */ //初始間隔
};
五、信號量
1.概述
1)信號量的使用主要是用來保護共享資源,使得資源在一個時刻只有一個進程(線程)所擁有。
2)信號量爲正->說明是空閒的
3)信號量爲0->說明被佔用,測試的線程要進入睡眠隊列中,等待被喚醒
2.無名信號量(互斥鎖)
sem_t sem_id;//創建無名信號量
sem_init(&sem_id, 0, 1);//初始化無名信號量
sem_wait(&sem_id);//加鎖
//讀寫操作
sem_post(&sem_id);//解鎖
3.有名信號量(/dev/shm)
區別:
1)使用時與無名信號量一樣,共享sem_wait和sem_post函數.
2)有名信號量使用sem_open代替sem_init.
3)在結束的時候要像關閉文件一樣去關閉這個有名信號量.
實現步驟:
//1.新建有名信號量,設置權限和信號量的值
shm_fd = sem_open(mysem, ,O_CREAT,0755, 1);
sem_wait(shm_fd);//加鎖
//讀寫操作
sem_post(shm_fd);//解鎖
sem_close(shm_fd);//關閉打開的命名信號量
sem_unlink(mysem);//銷燬有名信號量文件
注意:
編譯程序時要帶:-lrt或-lpthread
4.sem_getvalue()函數獲取當前信號量值:
原型:int sem_getvalue(sem_t *sem,int *valp);
返回值:返回0表示當前信號量已上鎖,否則返回等待該信號量解鎖的進程數