--- title: 'Linux OS programming' --- [TOC] 文件 === 可以通過這些函示進行 創建 打開 讀寫 光標: ```c int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int creat(const char *pathname, mode_t mode); ssize_t write(int fildes, const void *buf, size_t nbyte); ssize_t read(int fd, void *buf, size_t count); off_t lseek(int fd, off_t offset, int whence); int close(int fd); read/write 都會影響光標, 使用lseek來改變: whence: SEEK_SET - head SEEK_END - tail SEEK_CUR - current position ``` >返回的數字fd都是文件描述符file descriptor, 而系統默認的有0, 1, 2 分別為:讀,寫,錯誤。 #include <unistd.h> 也有定義這3個宏(STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) 開啟文件也可以用fopen, 搭配: fread, fwrite: ```c FILE *fopen(const char *pathname, const char *mode); size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); int fclose(FILE *stream) int fflush(FILE* stream) ``` ## open與fopen的優缺點 open 主要用於內核態的設備文件調用,無緩存。 fopen 主要給用戶態讀寫文件,有緩存(大塊大塊的讀寫,少次數的切換內和態),需注意fclose或fflush確保文件的保存。 ___ # 進程 程序是未被執行的executable(靜態), 進程是運行起來的executable(動態) 進程裡可以包含多個線程(Thread),好比工廠(process)裡面的工人(thread)。 ## 進程狀態 就緒:獲取出CPU外的所有資源、只要處理器分配資源就可以馬上執行 運行:獲得處理器分配的資源,程序開始執行 阻塞:當程序條件不夠的時候,需要等待提交滿足的時候才能執行。 終止:進程結束,或是出現錯誤而被系統終止,進入終止狀態,無法再執行。 ## 查看進程(process) ```bash ps -aux // check all precesses top // working manager ``` 每個進程創建後都會有個名字 pid, 系統默認的有 調度進程(pid=0), 系統初始化,界面(pid=1) ## 程式存储空间分配 ![](https://i.imgur.com/oItVl7R.png) 主要分為6個部分: 從高地址->低地址 | | | | |-|-|-| |高地址|命令行參數和環境變量 | argc, argv| | |stack(棧or堆棧) |返回使用中(函數,局部變量)的地址,靜態內存分配| | |heap (堆or堆積)| malloc 返回值,動態存儲分配| | |未初始化『數據』bss(block started by symbol)|沒有 被賦值的變量| | |初始化的『數據』|有 被賦值的變量| |低地址 |正文 | 代碼段: (流程控制, 算法)| ## 子進程 ```c pid_t fork(void); pid_t vfork(void); ``` On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately. ||fork()|vfork| |-|-|-| |空間|與父進程不同空間;創新空間且copy-on-write pages|與父進程共享空間| |順序|由系統調度決定|父進程 保證 子進程優先運行;直到子進程exit才還給父進程| ### 正常/異常退出 |正常退出|異常退出| |-|-|-| |main調用return|abort()放棄當前進程| |exit()`標諄C庫`|當程式接收到類似關閉的訊號e.g Ctrl-c, kill -9| |_exit() or _Exit(); `系統調用`|最後一個線程取消請求做出反應| |進程中的最後一個線程調用thread_exit()|| ### 獲取退出狀態 子進程使用`exit`, `_exit`, `_Exit` 回傳 `status`碼 ||作用| |-|-| |_exit|最簡單的調用; 直接使程序停止, 清除其使用的記憶體空間 和 銷毀其在內核中的各種資料結構| |exit|則是在_exit基礎上做了一些包裝, 如 `保存程序狀態於某個檔案`, `解開對某共享資料的鎖`| > 當子進程退出,SIGCHLD會回傳給父進程,若不被收集的子進程 將變成 殭屍進程 Z+ , 造成`資源上的浪費` ![](https://i.imgur.com/vh3SqKK.png) 父進程使用wait或waitpid來獲取返回值。 |wait|waitpid| |-|-| |阻塞直至某个子进程终止(沒有指定) |waitpid则可以通过设置一个选项来设置为非阻塞,另外waitpid并不是等待第一个结束的进程而是等待参数中pid指定的进程| wait 定義的macro請查看[man page](https://man7.org/linux/man-pages/man2/waitid.2.html) ![](https://i.imgur.com/SIGN23O.png) :::spoiler wait的demo ```c #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> void print_status(int status); int main() { int status; pid_t pid; // child process with "normal" exit if((pid = fork()) == 0) { exit(7); } wait(&status); print_status(status); // child process with "abort" exit: /* generated SIGABRT */ if((pid = fork()) == 0) { abort(); } wait(&status); print_status(status); // child process: divide status with 0 generated /* SIGFPE */ if((pid = fork()) == 0) { status /= 0; } wait(&status); print_status(status); return 0; } void print_status(int status) { if(WIFEXITED(status)) printf("normal termination, exit status: %d\n", WEXITSTATUS(status)); if(WIFSIGNALED(status)) printf("abnormal termination, signal number: %d%s\n", WTERMSIG(status), #ifdef WCOREDUMP WCOREDUMP(status) ? " (core file generated)" : ""); #else ""); #endif else if(WIFSTOPPED(status)) printf("child stopped, singal number = %d\n", WSTOPSIG(status)); } ``` ::: [补充: 处理SIGCHLD信号](https://blog.csdn.net/u012877472/article/details/50165083) ### 替換進程影像 在shell命令執行ls,實質上是調用fork()創建了一個子進程然後利用exec系統調用將新產生的子進程替換成ls進程。 並且新進程的PID與原來的進程PID相同。 [參考](https://www.cntofu.com/book/46/linux_system/linuxxi_tong_bian_cheng_zhi_jin_cheng_ff08_wu_ff09.md) 以下是exec系列的函數: ```c int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); ``` > 主要兩種傳遞方式: > l系列: 可變動參數, 以空指針結尾 > v系列: vecter 為參數。 > e系列: 將環境變亮傳遞給需要替換的進程 示範```ls -l``` * l系列: ```c execl("/bin/ls","ls","-l",NULL); // absolute path execlp("ls","ls","-l",NULL); // 找env中找第一個出現的ls, 不安全 ``` * v系列: ```c char *argv[] = {"ls","-l",NULL}; execv("/bin/ls",argv); char *argv[] = {"ls","-l",NULL}; execvp("ls",argv); ``` * e系列: execle.c ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { char * const envp[] = {"AA=11", "BB=22", NULL}; printf("Entering main ...\n"); int ret; //ret =execl("./hello", "hello", NULL); ret =execle("./hello", "hello", NULL, envp); if(ret == -1) perror("execl error"); printf("Exiting main ...\n"); return 0; } ``` hello.c ```c #include <unistd.h> #include <stdio.h> extern char** environ; int main(void) { printf("hello pid=%d\n", getpid()); int i; for (i=0; environ[i]!=NULL; ++i) { printf("%s\n", environ[i]); } return 0; } ``` 結果: ![](https://i.imgur.com/Kws9Zeb.png) ## System() 基本上是fork、execve、waitpid的再次封裝。 ```c int system(const char *command); ``` system()函數調用"/bin/sh -c command"執行特定的命令,阻塞當前進程直到command命令執行完畢 返回值: 如果無法啟動shell運行命令,system將返回127;出現不能執行system調用的其他錯誤時返回-1。如果system能夠順利執行,返回那個命令的退出碼。 system原碼: ```c= int system(const char * cmdstring) { pid_t pid; int status; if(cmdstring == NULL){ return (1); } if((pid = fork())<0){ status = -1; } else if(pid == 0){ execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); _exit(127); //子進程正常執行則不會執行此語句 } else{ while(waitpid(pid, &status, 0) < 0){ if(errno != EINTER){ status = -1; break; } } } return status; } ``` 注意 最後有返回 "狀態碼" 可以做更進一步的檢查程式的運行狀態。 範例: ```c= #include <stdio.h> #include <unistd.h> #include <stdlib.h> #define EXIT_ERR(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }\ while (0);\ int main(void) { int status ; status = system("ls -l|wc -l"); if(status == -1){ EXIT_ERR("system error"); } else{ if(WIFEXITED(status)) { if(WEXITSTATUS(status) == 0) printf("run command successful\n"); else printf("run command fail and exit code is %d\n",WEXITSTATUS(status)); } else printf("exit status = %d\n",WEXITSTATUS(status)); } return 0; } ``` 結果: ![](https://i.imgur.com/a3kGOQ5.png) ## popen() 上面那些都是運行指令 但無法返回結果; 使用了popen函數就可以做到獲取運行結果了。 [man page](https://man7.org/linux/man-pages/man3/popen.3.html) ```c #include <stdio.h> FILE *popen(const char *command, const char *type); int pclose(FILE *stream); ``` 範例: ```c= #include <stdio.h> #include <stdlib.h> #include <errno.h> int main() { FILE *fd = popen("ls", "r"); char tmp[1024] = {'\0'}; // read the output to tmp array fread(tmp, sizeof(tmp), 1, openfd); printf("%s\n", tmp); fclose(fd); return 0; } ``` ![](https://i.imgur.com/IEbLNBW.png) # 信號 ```man 7 signal``` 系統中的信號有: ```kill -l``` ![](https://i.imgur.com/qvZgjvw.png) 處理方式: * 忽略 * 大部份可被忽略,唯有SIGKILL,SIGSTOP不能 * 捕捉 * 某信好發生時,由內核來調用的至定義函數 * 默認 * 每個信號系統都對應了一個處理動作 * 結束 * kill -9 , kill -SIGKILL 都可以結束一個進程 ## basic - 接收信號 調用函數 函數: ```c #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); ``` ```c= #include <signal.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> void handler(int signum) { printf("signum: %d\n", signum); } int main(int argc, char **argv) { printf("pid: %d\n", getpid()); //signal(SIGINT, SIG_IGN); // ignore, ctl+c signal signal(SIGINT, handler); /* // use system to send cmd to kill pid int signum = atoi(argv[1]); int pid = atoi(argv[2]); char cmd[128] = {0}; sprintf(cmd, "kill -%d %d", signum, pid); system(cmd); */ while(1); return 0; } ``` ## 進階版 函數: 發送/接收: `sigqueue`, `sigaction` ```c int sigqueue(pid_t pid, int sig, const union sigval值); int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); ``` ```c struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; ``` 注意:hand:若要使用 `void (*sa_sigaction)(int, siginfo_t *, void *);` 的話 `sa_flag` 要聲明: `SA_SIGINFO` send ```c #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char **argv) { int signum = atoi(argv[1]); pid_t pid = atoi(argv[2]); union sigval value; value.sival_int = 100; sigqueue(pid, signum, value); printf("Pid: %d Finish.\n", getpid()); sleep(10); return 0; } ``` recieve ```c #include <signal.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> void handler(int sig, siginfo_t *info, void *ucontext) { if(ucontext != NULL) { printf("PID from: %d\n", info->si_pid); printf("Signum: %d\n", sig); printf("Data: %d\n", info->si_int); // one way to get data printf("Data: %d\n", info->si_value.sival_int); // another way to get data perror("WHY"); } } int main() { printf("pid %d\n", getpid()); struct sigaction act; act.sa_sigaction = handler; act.sa_flags = SA_SIGINFO; sigaction(SIGUSR1, &act, NULL); while(1); return 0; } ``` # 線程(執行序) 現成狀態: 就緒:指線程具備運行的所有條件,邏輯上可以運行,在等待處理機 運行:指線程占用處理機正在運行 阻塞:線程在等待一個事件,邏輯上不可執行 * 地址空間 * 要分配給進程(代碼段,樹鋸斷(初始化,為初始化),堆,棧,命令行參環境變量) * 而現成共享和read on write * 效率 * 線程間的切換上的時間遠遠小於進程間切換的時間 * 總體開銷約一個進程的30倍左右 * 通信 * 進程很不方便 - 耗費內存 CPU, 需要用(IPC - 管道,消息隊列,共享內存,信號,信號量) * 解決數據共享問題 * 條件變量 * 互斥鎖 ## 創建/刪除/回傳參數 ```c #include <pthread.h> int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg); int pthread_join(pthread_t thread, void **retval); void pthread_exit(void* retval); ``` 範例: 創建/刪除/戴回傳參數 ```c #include <stdio.h> #include <pthread.h> #include <unistd.h> void *func1(void *param) { printf("Number: %d\n", *((int *)param)); /* // pase int static int ret = 666; pthread_exit((void*)&ret); */ static char *ret = "hello linux"; pthread_exit((void *) ret); } int main() { pthread_t t1; int param = 100; pthread_create((pthread_t *)&t1, NULL, func1, &param); /* //recieve int int *ret; pthread_join(t1, (void **) &ret); printf("%d\n", ret); */ // recieve char* char *ret; pthread_join(t1, (void **) &ret); printf("%s\n", ret); return 0; } ``` > 使用pthread_exit回傳參數為`整數`和`字符串`的差別是: 字串不需要再多加上地址(本身就是個指針)。 ## 同步問題 下面示範了: 創建兩個thread訪問addOne()資源進行各1次萬次的調用,但執行的結果和我們期望的不同,counter未滿2千萬。 為甚麼呢?因為`同步問題` > 這個問題只有多和處理器會發生呦 ```c= #include <stdio.h> #include <pthread.h> #include <unistd.h> int counter; void* addOne() { for(int i=0; i<10000000; i++) counter++; } int main() { if(_POSIX_THREADS) perror("support posix_thread"); pthread_t t1; pthread_t t2; pthread_create(&t1, NULL, &addOne, NULL); pthread_create(&t2, NULL, &addOne, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("total: %d\n", total); return 0; } ``` ![](https://i.imgur.com/2Otl0Gu.png) ## 互斥鎖 (mutex lock) 我們這時就需要有鎖的機制了。就好比說共享資源是一間廁所,你不會想要在你使用的時候歡迎別人一起共用吧:smile: 那就使用鎖的機制 用完再打開下一個人就可以使用了。 count++ 在cpu指令裡: 基本上可看成1.讀取 2.加+1 3.寫入 只要有打亂的步驟就會得到錯誤的結果喔 ```c int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); ``` 下面是剛剛的範例下上鎖的版本: ```c= #include <stdio.h> #include <pthread.h> #include <unistd.h> int counter; pthread_mutex_t mutex; void* addOne() { pthread_mutex_lock(&mutex); for(int i=0; i<10000000; i++) counter++; pthread_mutex_unlock(&mutex); } int main() { if(_POSIX_THREADS) perror("support posix_thread"); pthread_mutex_init(&mutex, NULL); pthread_t t1; pthread_t t2; pthread_create(&t1, NULL, &addOne, NULL); pthread_create(&t2, NULL, &addOne, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("total: %d\n", total); pthread_mutex_destroy(&mutex); return 0; } ``` 得到了我們所希望的結果了:smile: ![](https://i.imgur.com/A6plhvJ.png) ## 死鎖(dead lock) 死鎖: 請求的資源未得到釋放而沒有進展的等待。 一把鎖的情況: 在同一個執行續中 使用一把鎖, 鎖兩次。 (在第一次請求資源 得到資源後未釋放, 又接著再次請求 但此時是沒有資源可取得 所以一直等待 而產生死鎖) >* 解決同一把鎖多次的使用 可以配置 mutexAttributex的state: recurssive來解決,但上鎖的次數必須對應解鎖的次數、如果享用不對稱的鎖就要使用到semaphore了:smile: 兩把鎖的情況: 兩個執行續 各有: 鎖A和B, 1.各自鎖上自己的鎖之後 2.又想要鎖上對方的鎖。 (各自都需要兩種資源來完成他們的任務所以就互相的卡死對方。 >預防死結: >* Mutual exclusion:對不可共用的資源類型而言,互斥一定成立,而可共用的資源類型,因為可以同時讀取相同檔案,所以一定不會產生。 >* Hold and Wait:process必須保證一個行程在要求一項資源時,不可以佔用任何其它的資源。 >* No preemption:只要某個處理元要不到所要求的資源時,便把它已經擁有的資源釋放,然後再重新要求所要資源。 >* Circular Wait:確保循環式等候的條件不成立,我們對所有的資源型式強迫安排一個線性的順序。 >* [資料來源](https://sls.weco.net/node/21327) 以下是deadlock的小範例: ```c= #include <unistd.h> #include <pthread.h> #include <stdio.h> pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void* func1() { pthread_mutex_lock(&mutex1); printf("fun1 lock mutex1\n"); sleep(1); pthread_mutex_lock(&mutex2); printf("fun2 lock mutex1\n"); printf("text from func1\n"); pthread_mutex_unlock(&mutex2); pthread_mutex_unlock(&mutex1); } void* func2() { pthread_mutex_lock(&mutex2); printf("fun2 lock mutex2\n"); sleep(1); pthread_mutex_lock(&mutex1); printf("fun1 lock mutex1\n"); printf("text from func2\n"); pthread_mutex_unlock(&mutex1); pthread_mutex_unlock(&mutex2); } int main() { pthread_t t1,t2; pthread_create(&t1, NULL, func1, NULL); pthread_create(&t1, NULL, func2, NULL); pthread_join(t1,NULL); pthread_join(t2,NULL); pthread_mutex_destroy(&mutex1); pthread_mutex_destroy(&mutex2); return 0; } ``` ![](https://i.imgur.com/gecNS8Z.png) ## 資源同步 條件控制 我們可以使用的函數: ```c int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); ``` 範例: 模擬媽媽們去買(搶)衣服 :::spoiler code ```c= #include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int MAX_MOM = 6; int t_shirt_per_day = 6; int t_shirt; int isWorking; void* shopping(void *param) { pthread_mutex_lock(&mutex); while(t_shirt < 1 && isWorking) { printf("\t\t\t\tNo.%d is waiting...\n", *(int*)param); pthread_cond_wait(&cond, &mutex); } if(t_shirt > 0) { t_shirt--; printf("\t\t\t\tNo.%d bought one, %d left\n", *(int*)param, t_shirt); usleep(100000); } else { printf("\t\t\t\tNo. %d missed.\n", *(int*)param); } pthread_mutex_unlock(&mutex); } void makeT_shirt() { isWorking = 1; for(int i=0; i < t_shirt_per_day; i++) { pthread_mutex_lock(&mutex); t_shirt += 2; printf("Made %d new T-shirt. total: %d\n", i, t_shirt); if(i == t_shirt_per_day-1) isWorking = 0; pthread_mutex_unlock(&mutex); pthread_cond_signal(&cond); //pthread_cond_broadcast(&cond); usleep(300000); } printf("worker: job off\n"); } int main() { pthread_t worker; pthread_t moms[MAX_MOM]; pthread_create(&worker, NULL, (void*) makeT_shirt, NULL); for(int i=0; i<MAX_MOM; i++) { int *pi = malloc(sizeof(int)); *pi = i; pthread_create(&moms[i], NULL, &shopping, pi); } for(int i=0; i<MAX_MOM; i++) { pthread_join(moms[i], NULL); } pthread_join(worker,NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; } ``` ::: > 我們這裡使用的是`pthread_cond_singal`方式隨機喚起一個使用`pthread_cond_wait`在等待的媽媽,所以worker做了1件衣服就會銷售掉,這個情況就很完美。 但如果工人一次製造2件衣服的話呢? 如果依然使用cond_signal還就只會呼叫1個媽媽來買然後剩下一件,顯然呼叫所有的媽媽來搶購才是我們想要的,這樣就可以一次賣出現有的庫存。 這時我們就可以使用`pthread_cond_broadcast`來代替`pthread_cond_signal`來呼叫所有的媽媽來搶購了。 這樣就會很公平是不是 醬子就很棒 哈哈 :satisfied: ![](https://i.imgur.com/dNzflKZ.png) ## 線程同步(代碼) 我們透過了barrier來保證 在運行某行指令時有足夠多的threads, 實現代碼上的同步。 下面的例子 創建了rolldice和calculated的barrier, 各自等待9個threads(8個threads, 加上main 總共就是9個), 來保證8個threads的statusVal值生成 後運行calculate status, 和 保證8個threads的status計算完畢後運行result的列印。 ![](https://i.imgur.com/Zvpa1Bg.png) 使用的函數: ```c #include <pthread.h> int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count); ``` :::spoiler ```c= #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> int NUM_THREADS = 8; int status[8] = {0}; int statusVal[8] = {0}; pthread_barrier_t barrierRollDice; pthread_barrier_t barrierCalculated; void* rolldices(void *arg) { int index = *(int*)arg; while(1) { // roll dices for(int i=0; i<NUM_THREADS; i++) { statusVal[i] = rand() %6 + 1; } pthread_barrier_wait(&barrierRollDice); pthread_barrier_wait(&barrierCalculated); // guaranteed status are calculated if(status[index] == 1) printf("%d rolled %d, won\n", index, statusVal[index]); else printf("%d rolled %d, lost\n", index, statusVal[index]); } } int main() { srand(time(NULL)); pthread_t threads[NUM_THREADS]; pthread_barrier_init(&barrierRollDice, NULL, NUM_THREADS+1); pthread_barrier_init(&barrierCalculated, NULL, NUM_THREADS+1); // create threads for(int i=0; i < NUM_THREADS; i++) { int *index = malloc(sizeof(int)); *index = i; pthread_create(&threads[i], NULL, rolldices, index); } while(1) { pthread_barrier_wait(&barrierRollDice); // guaranteed dice rolled are calculated printf("\nNEW ROUND:\n"); // calculate int max; for(int i=0; i<NUM_THREADS; i++) { if(statusVal[i] > max) max = statusVal[i]; } for(int i=0; i<NUM_THREADS; i++) { if(statusVal[i] == max) status[i] = 1; else status[i] = 0; } sleep(3); pthread_barrier_wait(&barrierCalculated); // guaranteed status are calculated } // clean up for(int i=0; i < NUM_THREADS; i++) { pthread_join(threads[i], NULL); } pthread_barrier_destroy(&barrierRollDice); pthread_barrier_destroy(&barrierCalculated); return 0; } ``` ::: ![](https://i.imgur.com/3DEJsmU.png) ## detach 以前都是`pthread_join`等thread運行完釋放資源,現在又學新的一招讓他自己完成後釋放資源, 用`pthread_detch`就搞定了。 ```c= #include <unistd.h> #include <stdio.h> #include <pthread.h> void* f() { sleep(3); printf("hello linux\n"); } int main(){ pthread_t thread; pthread_create(&thread, NULL, f, NULL); pthread_detach(thread); pthread_exit(0); //return 0; } ``` 但實質上`pthread_detch`是把一個不是non-detch的thread狀態改為detach的狀態、所以在創建thread和更改狀態上有個空隙 是有可能導致狀態沒改上 thread就結束的狀況(資源就得不到釋放)。 因此是時候讓不知道有甚麼用的attribute登場了:satisfied: ```c= #include <unistd.h> #include <stdio.h> #include <pthread.h> void f() { sleep(3); printf("hello linux\n"); } int main(){ pthread_t thread; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&thread, &attr, (void*)f, NULL); pthread_detach(thread); pthread_exit(0); //return 0; } ``` ## factory and consumer (with semaphore) ![](https://i.imgur.com/mQPRRVr.png) [picture source](https://www.ktustudents.in/2017/10/program-for-producer-consumer-problem-in-c-cs331-lab.html) 一、V operation:V()會將semaphore的值加1,signal函數或是sem_post()。 二、P operation:P()會將semaphore的值減1,wait函數或是sem_wait()。 [Semaphore原理與操作說明](https://www.syscom.com.tw/ePaper_Content_EPArticledetail.aspx?id=213&EPID=176&j=5&HeaderName=NonStop%E5%B0%88%E6%AC%84) 我們使用2個threads在buffer[10]的數組做讀寫的動作, 並且也對臨界資源做了保護, 此時會看到 執行續雖然跑得很快 搶到了進入了讀寫的機會 但有很多時候都是無效的讀寫 因為裡面沒有數據, 這些都是cpu的浪費。 因此就可以使用semaphore來做資源量的管理、可以看到我們創建了2個semaphore分別是semEmpty, semFull。 我們對semEmpty 做10的初始化表示10個空箱子可以讓producer來裝箱、和semFull初始化0表示0個為填滿的箱子。 在producer做裝箱前使用sem_wait(&empty)來對empty的信號量-1,表示取了一個空箱子裝箱, 裝完箱子後使用sem_post(&semFull)對full的信號量+1,表示放入一個裝滿貨物的箱子, 反之亦然 在consumer的作法也就是反過來做囉。 可以看到輸出的結果一點資源都沒有浪費, 我們就可以和自己說 醬就好棒棒呀 :smile_cat: :+1: :::spoiler 第一種 ```c= #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> int THREAD_TOTAL = 2; int storage[10]; int count = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void producer(void *args) { while(1) { pthread_mutex_lock(&mutex); if(count < 10) { storage[count] = rand() % 100; printf("count: %d, val: %d\n", count, storage[count]); count++; } else { printf("produce skipped\n"); } pthread_mutex_unlock(&mutex); } } void comsumer(void *args) { int val; while(1) { pthread_mutex_lock(&mutex); if(count > 0) { val = storage[count-1]; count--; } else { printf("\t\t\t\t\tcomsum failed\n"); } printf("\t\t\t\t\tcount: %d, got val: %d\n", count, val); pthread_mutex_unlock(&mutex); } } int main() { srand(time(NULL)); pthread_t thread[THREAD_TOTAL]; for(int i = 0; i < THREAD_TOTAL; i++) { if(i % 2 == 0) pthread_create(&thread[i], NULL, (void*) producer, NULL); else pthread_create(&thread[i], NULL, (void*) comsumer, NULL); } for(int i = 0; i < THREAD_TOTAL; i++) { pthread_join(thread[i], NULL); } pthread_mutex_destroy(&mutex); return 0; } ``` ::: ![](https://i.imgur.com/bxJNd75.png) :::spoiler better version... ```c= #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> int THREAD_TOTAL = 2; int storage[10]; int count = 0; sem_t semEmpty, semFull; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void producer(void *args) { while(1) { sem_wait(&semEmpty); pthread_mutex_lock(&mutex); if(count < 10) { storage[count] = rand() % 100; printf("count: %d, val: %d\n", count, storage[count]); count++; } else { printf("produce skipped\n"); } pthread_mutex_unlock(&mutex); sem_post(&semFull); } } void comsumer(void *args) { int val; while(1) { sem_wait(&semFull); pthread_mutex_lock(&mutex); if(count > 0) { val = storage[count-1]; count--; } else { printf("\t\t\t\t\tcomsum failed\n"); } printf("\t\t\t\t\tcount: %d, got val: %d\n", count, val); pthread_mutex_unlock(&mutex); sem_post(&semEmpty); } } int main() { srand(time(NULL)); sem_init(&semEmpty, 0, 10); sem_init(&semFull, 0, 0); pthread_t thread[THREAD_TOTAL]; for(int i = 0; i < THREAD_TOTAL; i++) { if(i % 2 == 0) pthread_create(&thread[i], NULL, (void*) producer, NULL); else pthread_create(&thread[i], NULL, (void*) comsumer, NULL); } for(int i = 0; i < THREAD_TOTAL; i++) { pthread_join(thread[i], NULL); } sem_destroy(&semEmpty); sem_destroy(&semFull); pthread_mutex_destroy(&mutex); return 0; } ``` ::: ![](https://i.imgur.com/N3yPhp2.png) ## threadpool ```c= #include <stdlib.h> #include <pthread.h> #include <stdio.h> typedef struct Task { int a,b; }Task; pthread_mutex_t mutexQueue = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condQueue = PTHREAD_COND_INITIALIZER; int MAX_THREADS = 1; int taskcount = 0; Task taskqueue[256]; void executeTask(Task *task) { int sum = task->a + task->b; printf("result: %d\n", sum); } void submitTask(Task task) { pthread_mutex_lock(&mutexQueue); taskqueue[taskcount] = task; taskcount++; pthread_mutex_unlock(&mutexQueue); pthread_cond_signal(&condQueue); } void startThread() { while(1) { pthread_mutex_lock(&mutexQueue); while(taskcount < 0) pthread_cond_wait(&condQueue, &mutexQueue); Task task = taskqueue[0]; taskcount--; for(int i=0; i<taskcount; i++) { taskqueue[i] = taskqueue[i+1]; } pthread_mutex_unlock(&mutexQueue); executeTask(&task); } } int main() { pthread_t threadpool[MAX_THREADS]; int i; for(i=0; i<MAX_THREADS; i++) { pthread_create(&threadpool[i], NULL, (void*)startThread, NULL); } srand(time(NULL)); for(i=0; i<100; i++) { Task task = { .a = rand() % 100, .b = rand() % 100 }; submitTask(task); } for(i=0; i<MAX_THREADS; i++) { pthread_join(threadpool[i], NULL); } pthread_mutex_destroy(&mutexQueue); pthread_cond_destroy(&condQueue); return 0; } ``` ## threadpool with taskptr ```c= #include <stdlib.h> #include <pthread.h> #include <stdio.h> typedef struct Task { void (*ptr)(); int a,b; }Task; pthread_mutex_t mutexQueue = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t condQueue = PTHREAD_COND_INITIALIZER; int MAX_THREADS = 1; int taskcount = 0; Task taskqueue[256]; void sum(int a, int b) { int total = a + b; printf("sum of %d and %d is %d\n", a, b, total); } void mult(int a, int b) { int total = a * b; printf("multi of %d and %d is %d\n", a, b, total); } void executeTask(Task *task) { task->ptr(task->a, task->b); // int sum = task->a + task->b; // printf("result: %d\n", sum); } void submitTask(Task task) { pthread_mutex_lock(&mutexQueue); taskqueue[taskcount] = task; taskcount++; pthread_mutex_unlock(&mutexQueue); pthread_cond_signal(&condQueue); } void startThread() { while(1) { pthread_mutex_lock(&mutexQueue); while(taskcount < 0) pthread_cond_wait(&condQueue, &mutexQueue); Task task = taskqueue[0]; taskcount--; for(int i=0; i<taskcount; i++) { taskqueue[i] = taskqueue[i+1]; } pthread_mutex_unlock(&mutexQueue); executeTask(&task); } } int main() { pthread_t threadpool[MAX_THREADS]; int i; for(i=0; i<MAX_THREADS; i++) { pthread_create(&threadpool[i], NULL, (void*)startThread, NULL); } srand(time(NULL)); for(i=0; i<100; i++) { Task task = { task.ptr = (i%2==0)? sum:mult, .a = rand() % 100, .b = rand() % 100 }; submitTask(task); } for(i=0; i<MAX_THREADS; i++) { pthread_join(threadpool[i], NULL); } pthread_mutex_destroy(&mutexQueue); pthread_cond_destroy(&condQueue); return 0; } ``` ## 而外補充: 主題分享: 淺談 Semaphore 與 Mutex * `mutex 是給行程獨立的鎖 而semephore是給多少量的行程可通行` {%youtube JEXwO_EoyZo%} :::info [更多用法可以參考Beej的網站](https://beej.us/guide/bgc/html/#file-inputoutput) [CodeVault](https://www.youtube.com/playlist?list=PLfqABt5AS4FmuQf70psXrsMLEDQXNkLq2) ::: ###### tags: `Linux`