linux C進程常用操作
- 2019 年 11 月 5 日
- 筆記
不登高山,不知天之高也;
不臨深溪,不知地之厚也。
荀子《勸學》
linux應用層主要是一個個獨立任務的進程在運行,但是很多時候,在工作中我們可能很少去重新寫一個進程,
大部分的工作都是分配到了一個進程內的模組或者提供進程內特定功能的介面開發,這篇文章是想簡單說明下,
作為一個進程,在實際開發過程中,可能用到的一些編程方法比如:main參數解析,訊號註冊、回調函數、
執行緒創建、文件操作(FIFE *fp)、進程間通訊(socket);每一種我都會附一個簡單的實例。
1、main參數解析,參數較少,使用簡單判斷argc並取出對應的argv[i]值就就可以處理,程式碼如下:
1 #include <stdio.h> 2 int main(int argc, char *argv[]) 3 { 4 int i = 0; 5 printf("argc is %dn", argc); 6 for(i = 0; i < argc; i++) 7 printf("argv[%d] is %sn", i, argv[i]); 8 return 0; 9 }
參數較多時就可以調用getopt/getopt_long介面來完成工作。
函數的定義
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 = %ctt", opt); 19 printf("optarg = %sttn", optarg); 20 } 21 return 0; 22 }
樣列輸出:
./argc-opt -A -B xiaocang -Cxiaobo opt = A optarg = (null) opt = B optarg = xiaocang opt = C optarg = xiaobo
2、訊號註冊,作為一個進程,很有必要註冊一定的訊號,防止進程異常退出時,自己蒙圈。
函數定義:
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 %dn", 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...
3、回調函數
其實在訊號註冊中我們就使用了回調函數,但是此回調函數不是我們自己定義的類型,自己用來調用執行,
我們這裡做一個回調函數的調用示例,差別就在於回調函數的定義與調用時機,根據自己的實際需要定義就可以。
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 %dn", PanJinLian); 9 printf("XiMengQin is %dn", 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 %dn", ret); 21 return 0; 22 }
執行返回
1 PanJinLian is 99 2 XiMengQin is 101 3 do something... 4 ret is 0
4、執行緒創建
執行緒主要是用來阻塞接受非同步消息,或者完成耗時與周期性的任務,重點需要關注的是執行緒結束時執行緒資源的回收問題,
很多人會忽略這部分,會用到 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 %sn", (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...
5、文件操作,文件操作很普遍,如記錄數據,讀入數據等。
文件操作一般要注意,明確操作的fp在文件中的位置,fclose前刷新快取,對寫入或者讀出的返回做判斷,異常或者結束
操作時關閉fp,同樣還有open read write介面。兩者區別:
1、緩衝文件系統與非緩衝系統的區別
緩衝文件系統(fopen):在記憶體為每個文件開闢一個快取區,當執行讀操作,從磁碟文件將數據讀入記憶體緩衝區,裝滿後從
記憶體緩衝區依次讀取數據。寫操作同理。
記憶體緩衝區的大小影響著實際操作外存的次數,緩衝區越大,操作外存的次數越少,執行速度快,效率高。緩衝區大小由機
器而定。藉助文件結構體指針對文件管理,可讀寫字元串、格式化數據、二進位數據。
非緩衝文件系統(open):依賴作業系統功能對文件讀寫,不設文件結構體指針,只能讀寫二進位文件。
2、open屬於低級IO,fopen屬於高級IO
3、open返迴文件描述符,屬於用戶態,讀寫需進行用戶態與內核態切換。 fopen返迴文件指針
4、open是系統函數,不可移植 fopen是標準C函數,可移植
5、一般用fopen打開普通文件,open打開設備文件
6、如果順序訪問文件,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("%sn", buffer); 13 fsync(fileno(fp));/* 刷新快取 */ 14 fclose(fp); 15 return(0); 16 }
執行結果
1 hello world 2 cat test.txt 3 hello world
6、進程間通訊:包括管道(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 socketn"); 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 failn"); 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:%dn",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 failedn"); 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部落】,獲取更多精華文章,海量編程資料,讓我們一起進步,一起成長。

