【Linux】Linux 學習筆記2015(系統程序設計)

標籤(空格分隔): 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是傳出參數,存放進程原有的信號集。

四、計時器與信號

  1. 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表示當前信號量已上鎖,否則返回等待該信號量解鎖的進程數

六、共享內存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章