不登高山,不知天之高也; linux
不臨深溪,不知地之厚也。編程
荀子《勸學》數組
linux應用層主要是一個個獨立任務的進程在運行,可是不少時候,在工做中咱們可能不多去從新寫一個進程,緩存
大部分的工做都是分配到了一個進程內的模塊或者提供進程內特定功能的接口開發,這篇文章是想簡單說明下,微信
做爲一個進程,在實際開發過程當中,可能用到的一些編程方法好比:main參數解析,信號註冊、回調函數、數據結構
線程建立、文件操做(FIFE *fp)、進程間通訊(socket);每一種我都會附一個簡單的實例。多線程
一、main參數解析,參數較少,使用簡單判斷argc並取出對應的argv[i]值就就能夠處理,代碼以下:異步
1 #include <stdio.h> 2 int main(int argc, char *argv[]) 3 { 4 int i = 0; 5 printf("argc is %d\n", argc); 6 for(i = 0; i < argc; i++) 7 printf("argv[%d] is %s\n", i, argv[i]); 8 return 0; 9 }
參數較多時就能夠調用getopt/getopt_long接口來完成工做。socket
函數的定義tcp
int getopt(int argc, char * const argv[], const char *optstring);
參數說明:
argc:main()函數傳遞過來的參數的個數
argv:main()函數傳遞過來的參數的字符串指針數組
optstring:選項字符串,告知 getopt()能夠處理哪一個選項以及哪一個選項須要參數
代碼樣列
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<getopt.h> 4 int main(int argc, char *argv[]) 5 { 6 int opt; 7 /*單個字符表示選項沒有參數 輸入格式:-A便可,不加參數 8 *單字符加冒號表示選項有且必須加參數 輸入格式:-B xiaocang或-Bxiaobo(二選一) 9 *單字符加兩個冒號表示選項能夠有也能夠無 輸入格式:-Cxiaobo(必須挨着) 10 */ 11 char *string = "AB:C::"; 12 while ((opt = getopt(argc, argv, string))!= -1) 13 { 14 /* 下面是經常使用的兩個獲取選項及其值得變量optarg無需定義,全局變量 15 * opt '-' 後面的字符,也就是參數字符 16 * optarg 指向當前選項參數(若是有)的指針。 17 */ 18 printf("opt = %c\t\t", opt); 19 printf("optarg = %s\t\t\n", optarg); 20 } 21 return 0; 22 }
樣列輸出:
./argc-opt -A -B xiaocang -Cxiaobo opt = A optarg = (null) opt = B optarg = xiaocang opt = C optarg = xiaobo
二、信號註冊,做爲一個進程,頗有必要註冊必定的信號,防止進程異常退出時,本身蒙圈。
函數定義:
1 #include <signal.h> 2 typedef void (*sighandler_t)(int); 3 sighandler_t signal(int signum, sighandler_t handler);
參數說明:
signum:要捕捉的信號(查看信號:kill -l,9號SIGKILL信號不能被捕捉);
handler:咱們要對信號進行的處理方式。
示例代碼:
1 #include <stdio.h> 2 #include <signal.h> 3 void signal_handler(int signal) 4 { 5 printf("Received signal %d\n", signal); 6 printf("do something...\n"); 7 return; 8 } 9 int main(int argc, char *argv[]) 10 { 11 signal(SIGHUP, signal_handler); 12 while(1) 13 sleep(2000); 14 return 0; 15 }
示例測試:
一個窗口後臺掛進程運行 ./a.out &
開另外的窗口發送信號 kill -1 pid(a.out進程號)
第一個窗口收到將會收到
Received signal 1
do something...
三、回調函數
其實在信號註冊中咱們就使用了回調函數,可是此回調函數不是咱們本身定義的類型,本身用來調用執行,
咱們這裏作一個回調函數的調用示例,差異就在於回調函數的定義與調用時機,根據本身的實際須要定義就能夠。
1 #include <stdio.h> 2 3 /* 定義一個函數指針 肯定入參與返回值類型 */ 4 typedef int (* MyCallbak)(int PanJinLian, int XiMengQin); 5 /* 實現一個與上面定義的函數指針入參與返回值類型相同的函數 */ 6 int ThisMyFunc(int PanJinLian, int XiMengQin) 7 { 8 printf("PanJinLian is %d\n", PanJinLian); 9 printf("XiMengQin is %d\n", XiMengQin); 10 printf("do something...\n"); 11 return 0; 12 } 13 int main(int argc, char *argv[]) 14 { 15 int P_adrenaline = 99; 16 int X_adrenaline = 101; 17 MyCallbak CallbakPointer;/* 定義一個函數指針變量 */ 18 CallbakPointer = ThisMyFunc;/* 將函數地址賦予定義的指針 也叫掛鉤子*/ 19 int ret = CallbakPointer(P_adrenaline, X_adrenaline);/* 調用函數,執行回調 */ 20 printf("ret is %d\n", ret); 21 return 0; 22 }
執行返回
1 PanJinLian is 99 2 XiMengQin is 101 3 do something... 4 ret is 0
四、線程建立
線程主要是用來阻塞接受異步消息,或者完成耗時與週期性的任務,重點須要關注的是線程結束時線程資源的回收問題,
不少人會忽略這部分,會用到 pthread_detach 或者 pthread_join(阻塞等待線程結束並回收資源); 多線程必將引入同步與
互斥問題,則對於全局變量,必需要加鎖保護,數據流防止丟失咱們會用到隊列。
1 #include <pthread.h> 2 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 3 void *(*start_routine) (void *), void *arg) 4 //Compile and link with -pthread.
參數說明
thread:指向線程標識符的指針。
attr:用來設置線程屬性。
start_routine:線程運行函數的起始地址。
arg:運行函數的參數。
樣例代碼:
1 #include<stdio.h> 2 #include <pthread.h> 3 static void mythreadfun( void *arg ) 4 { 5 /*這將該子線程的狀態設置爲detached,則該線程運行結束後會自動釋放全部資源*/ 6 pthread_detach(pthread_self()); 7 printf("arg is %s\n", (char *)arg); 8 int i = 0; 9 while(1) 10 { 11 printf("do something...\n"); 12 if(i++ == 10) 13 break; 14 sleep(2); 15 } 16 return ; 17 } 18 19 int main(int argc, char *argv[]) 20 { 21 pthread_t pthreadid = 0; 22 int ret = 0; 23 char *param = "good"; 24 /* 建立線程 */ 25 ret = pthread_create(&pthreadid, NULL, (void *)mythreadfun, (void *)param); 26 if(ret != 0) 27 { 28 printf("create pthread failed."); 29 return; 30 } 31 printf("create pthread success."); 32 while(1) 33 sleep(2000); 34 return 0; 35 }
執行返回
1 ./a.out 2 create pthread success.arg is good 3 do something... 4 do something... 5 do something... 6 do something...
五、文件操做,文件操做很廣泛,如記錄數據,讀入數據等。
文件操做通常要注意,明確操做的fp在文件中的位置,fclose前刷新緩存,對寫入或者讀出的返回作判斷,異常或者結束
操做時關閉fp,一樣還有open read write接口。二者區別:
一、緩衝文件系統與非緩衝系統的區別
緩衝文件系統(fopen):在內存爲每一個文件開闢一個緩存區,當執行讀操做,從磁盤文件將數據讀入內存緩衝區,裝滿後從
內存緩衝區依次讀取數據。寫操做同理。
內存緩衝區的大小影響着實際操做外存的次數,緩衝區越大,操做外存的次數越少,執行速度快,效率高。緩衝區大小由機
器而定。藉助文件結構體指針對文件管理,可讀寫字符串、格式化數據、二進制數據。
非緩衝文件系統(open):依賴操做系統功能對文件讀寫,不設文件結構體指針,只能讀寫二進制文件。
二、open屬於低級IO,fopen屬於高級IO
三、open返回文件描述符,屬於用戶態,讀寫需進行用戶態與內核態切換。 fopen返回文件指針
四、open是系統函數,不可移植 fopen是標準C函數,可移植
五、通常用fopen打開普通文件,open打開設備文件
六、若是順序訪問文件,fopen比open快、 若是隨機訪問文件,open比fopen快
1 #include <stdio.h> 2 #include <string.h> 3 int main() 4 { 5 FILE *fp; 6 char *msg = "hello world"; 7 char buffer[20]; 8 fp = fopen("test.txt", "w+");/* 打開文件用於讀寫 */ 9 fwrite(msg, strlen(msg) + 1, 1, fp);/* 寫入數據到文件 */ 10 fseek(fp, 0, SEEK_SET);/* 移動到文件的開頭 */ 11 fread(buffer, strlen(msg) + 1, 1, fp); /* 讀取並顯示數據 */ 12 printf("%s\n", buffer); 13 fsync(fileno(fp));/* 刷新緩存 */ 14 fclose(fp); 15 return(0); 16 }
執行結果
1 hello world 2 cat test.txt 3 hello world
六、進程間通訊:包括管道(pipe)、有名管道(named pipe)、信號量(semophore)、消息隊列(message queue)、
信號(signal)、共享內存(shared memory)、套接字(socket),這裏只說下socket,tcp服務端與客戶端的簡單編
碼流程實現。
server端
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/un.h> 4 #define SERVER_SOCKET_FILE "/tmp/server_socket" 5 int main() 6 { 7 int ret = 0; 8 int listen_fd = -1; 9 int size = 0; 10 int conn_fd = -1; 11 int max_fd = 0; 12 fd_set reads; 13 struct sockaddr_un clt_addr; 14 socklen_t len = sizeof(clt_addr); 15 struct sockaddr_un srv_addr; 16 unsigned char buf[4096]; 17 18 listen_fd = socket(PF_UNIX, SOCK_STREAM, 0); 19 if(listen_fd < 0) 20 { 21 printf("can not create listen socket\n"); 22 return -1; 23 } 24 25 srv_addr.sun_family = AF_UNIX; 26 strncpy(srv_addr.sun_path, SERVER_SOCKET_FILE, sizeof(srv_addr.sun_path)); 27 unlink(SERVER_SOCKET_FILE); 28 ret = bind(listen_fd,(struct sockaddr*)&srv_addr, sizeof(srv_addr)); 29 if(ret < 0) 30 { 31 printf("can not bind server socket"); 32 close(listen_fd); 33 return -1; 34 } 35 36 ret = listen(listen_fd, 5); 37 if(ret < 0) 38 { 39 printf("can not listen the client"); 40 close(listen_fd); 41 return -1; 42 } 43 while(1) 44 { 45 FD_ZERO(&reads); 46 FD_SET(listen_fd, &reads); 47 max_fd = listen_fd; 48 if(conn_fd > 0) 49 { 50 FD_SET(conn_fd, &reads); 51 if(conn_fd > max_fd) 52 { 53 max_fd = conn_fd; 54 } 55 } 56 ret = select(max_fd+1, &reads, 0, 0, NULL); 57 if(ret <= 0) 58 { 59 perror("select fail\n"); 60 return -1; 61 } 62 else 63 { 64 memset(buf, 0, sizeof(buf)); 65 if(FD_ISSET(listen_fd, &reads)) 66 { 67 conn_fd = accept(listen_fd, (struct sockaddr*)&clt_addr, &len); 68 } 69 if(FD_ISSET(conn_fd, &reads)) 70 { 71 printf("recv client msg,conn_fd:%d\n",conn_fd); 72 read(conn_fd, buf, sizeof(buf)); 73 sleep(3); 74 write(conn_fd, "i am server", strlen("i am server")); 75 } 76 } 77 } 78 }
client端
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/un.h> 4 #define SERVER_SOCKET_FILE "/tmp/server_socket" 5 int main() 6 { 7 int ret = 0; 8 int retry = 5; 9 int i = 0; 10 int client_fd = 0; 11 struct sockaddr_un server_addr; 12 char buff[4096] = {0}; 13 int recv_data_len = 0; 14 struct sockaddr_un client_addr; 15 socklen_t sock_len = sizeof(client_addr); 16 if ((client_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) 17 { 18 printf("create sockfd failed\n"); 19 return -1; 20 } 21 printf("update socket create success.\n"); 22 memset(&server_addr,0x00,sizeof(server_addr)); 23 server_addr.sun_family = AF_UNIX; 24 strncpy(server_addr.sun_path, SERVER_SOCKET_FILE, strlen(SERVER_SOCKET_FILE)); 25 for(i = 0; i < retry; i++) 26 { 27 ret = connect(client_fd,(struct sockaddr*)&server_addr,sizeof(server_addr)); 28 if(0 != ret) 29 { 30 printf("cannot connect to the server, retry %d time.\n", i); 31 sleep(1); 32 continue; 33 } 34 else 35 { 36 printf("connect server success.\n"); 37 break ; 38 } 39 } 40 write(client_fd, "hello", strlen("hello")); 41 while(1) 42 { 43 memset(buff, 0, sizeof(buff)); 44 recv_data_len = recvfrom(client_fd, buff, sizeof(buff), 0, (struct sockaddr *)&client_addr, &sock_len); 45 if(recv_data_len <= 0) 46 { 47 sleep(1); //sleep 100ms and receive from socket again 48 printf("recv_data_len is %d.\n", recv_data_len); 49 50 /* 從新鏈接 省略*/ 51 close(client_fd); 52 continue; 53 } 54 printf("recv data [%s]\n", buff); 55 /* do something */ 56 sleep(1); 57 write(client_fd, "recv data form server", strlen("recv data form server")); 58 } 59 return 0; 60 }
分別編譯命名server client,先運行server 後運行client便可以下所示:
以上就是linux c進程,會涉及到的一些基本的操做,還有不少的比較重要且基本的內容沒有這裏並無講到。
接觸了很多項目,總結下來大型項目中涉及多進程,多線程,難點在於弄清楚通訊消息的流向、數據結構的巧妙定義。剩下
的其實就是特定任務的邏輯部分了,若是做者有很好的註釋或者編碼規範,那理解起來也是很快的,添加功能也是很容易的事情。
萬變不離其宗。搞清楚基本的內容、基本原理,我認爲在技術成長中很重要。
關注微信公衆號【嵌入式C部落】,獲取更多精華文章,海量編程資料,讓咱們一塊兒進步,一塊兒成長。