title: linux/unix編程手冊-26_30


title: linux/unix編程手冊-26_30 date: 2018-06-20 11:53:07 categories: programming tags: tips

linux/unix編程手冊-26(監控子進程)

wait()

#include<sys/wait.h>

pid_t wait(int *status);

複製代碼
  • 阻塞直到一個子進程終止
  • status非空則子進程終止信息經過status指向的int變量返回
  • 內核爲父進程下全部子進程的運行總量追加進程CPU時間和資源使用數據
  • 終止子進程ID做爲返回
  • 若是wait()返回-1,可能的緣由是無等待子進程,此時errno會被置位ECHILD

侷限性linux

  • 沒法等待某個特定子進程完成
  • 沒法非阻塞等待
  • 只能檢測終止,對於中止或其餘情況沒法檢測

waitpid()

#include<sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

複製代碼
  • status 和 返回值和wait()一致
  • pid>0,爲等待進程的pid
  • pid=0,爲父進程同一進程組的全部子進程
  • pid<-1,取絕對值
  • pid=-1,爲任意子進程,wait(&status) eq waitpid(-1, &status, 0)
  • option = WUNTRACED:除了子進程終止信息外,還返回因信號而中止的進程信息
  • option = WCONTINUED:返回收到SIGCONT信號而恢復執行的已中止進程的狀態信息
  • option = WNOHANG:指定子進程未發生改變馬上返回不會阻塞,若是無匹配pid的子進程,waitpid()報錯,錯誤號設置爲ECHILD
  • 克隆子進程會有option擴展,見28章

status所指代值的意義

<sys/wait.h>定義了一組宏對status處理shell

  • WIFEXITED(status):子進程正常退出返回true
  • WIFSIGNALED(status):經過信號殺掉子進程爲true,WTERMSIG(status)返回信號編號,WCOREDUMP(status)true代表生成核心轉儲文件
  • WIFSTOPPED(status):因信號中止返回true, WSTOPSIG(status)返回信號編號
  • WIFCONTINUED(status):因受到SIGCONT恢復執行返回true

從信號處理器函數終止進程

若是在函數中調用_exit(EXIT_SUCCESS)父進程會認爲子進程正常終止編程

// 函數應當以下,wait才能正常捕捉是信號致使終止
void handler(int sig){
    /*func body*/
    signal(sig, SIG_DFL);
    raise(sig);
}
複製代碼

系統調用waittid()wait3()wait4()

  • waittid()更精準的控制,略

孤兒進程和殭屍進程

孤兒進程: 父進程終止後,init進程接管子進程, getppid()返回1 殭屍進程: 在父進程調用wait()以前,子進程就已經終止bash

  • 系統容許父進程在以後某一時刻執行wait(),肯定子進程爲什麼
  • 內核經過將子進程轉化爲殭屍進程處理此狀況,內核將釋放子進程大部分資源,僅保留內核進程表中的一條記錄(包含子進程ID,終止狀態,資源使用數據)
  • 沒法經過信號殺死殭屍進程,即便SIGKILL也不行
  • 父進程調用wait(),或者父進程未調用後退出,init接管後自動調用wait(),從系統移除殭屍進程

SIGCHLD信號

在一個進程終止或者中止時,將SIGCHLD信號發送給其父進程函數

若是每次收到SIGCHLD的時調用wait()由於在調用時對同一信號(SIGCHLD)要麼忽略要麼阻塞,阻塞的話標準信號之會保留一次,可能會遺漏SIGCHLD信號。工具

一個比較醜的解決殭屍進程的方法(調用前有殭屍進程產生不能解決)性能

while(waitpid(-1, NULL, WNOHANG) > 0)
    continue;
複製代碼

一些特殊spa

  • 一般子進程中止也會向父進程發送SIGCHLD
    • 經過sigaction()調用,傳入SA_NOCLDSTOP,可取消這個行爲
  • 雖然默認SIGCHLD是忽略
    • 若是顯示設置SIGCHLD爲SIG_IGN,以後不會把符合條件的子進程其轉化爲殭屍進程,而是直接刪除(有些UNIX實現可能會把以前殭屍進程給刪除)
    • 經過sigaction()調用,傳入SA_NOCLDWAIT,也可終止殭屍進程的產生,可是不保證是否發送SIGCHLD信號

linux/unix編程手冊-27(程序的執行)

#include<unistd.h>

int execve(const char *pathname, char *const argv[], char *const envp[]) // envp 成員字符串格式爲name=value 複製代碼
  • 若是pathname文件設置了set-user-id權限位,會將有效用戶ID設爲文件屬主ID
  • 不論有沒set-user-id,均會將當前進程的有效用戶id覆蓋已保存的set-user-id

解釋器腳本

文件描述符和exec()

默認由exec()調用程序所打開的全部文件描述符在exec()執行過程當中都會保持打開線程

$ ls /tmp > dit.txt
複製代碼
execlp()
複製代碼
  • 調用fork()建立子進程
  • 子shell進程以描述符1打開dir.txt用於輸出,打開的方式可能以下:
    • 子shell關閉描述符1,隨機打開文件dir.txt,由於open老是取最小值,標準輸入0此時已經打開
    • 打開dir.txt 若是獲取的描述符不是標準輸出,shell會調用dup2強制將標準輸出複製爲新描述符的副本dup2(fd, STDOUT_FILENO), 以後再close(fd)
  • 子進程執行ls, 將結果輸出到標準輸出

shell有內建命令如cd,不會調用fork()或者exec()unix

執行時關閉標誌(FD_CLOEXEC)

  • 庫函數應該老是爲其打開文件設置執行時關閉標誌
  • 若是設置了執行時關閉
    • 成功執行exec()時, 會自動關閉此描述符
    • 若是調用exec()失敗,文件描述符會保持打開

信號與exec()

執行shell命令。system()

#include<stdlib.h>

int system(const char *command);

system(cmd);

//等同於

execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
複製代碼

linux/unix編程手冊-28(詳述進程的建立和程序的執行)

進程記帳

#define _BSD_SOURCE
#include<unistd.h>

int acct(const char *acctfile) // 特權進程能夠調用, acctfile 一般是路徑字符串/var/log/pacct 或 /usr/account/pacct的指針,若取消記帳功能,accfile爲NULL便可 複製代碼

系統調用clone()

#define _GNU_SOURCE
#include<sched.h>

int clone(int (*func)(void *), void *child_stack, int flags, void *func_arg, ...)
複製代碼
  • flags 由高位和低位組成
    • 低位保存子進程終止時發個父進程的信號, 爲0則不發送,若是子進程是被信號終止,則仍是發SIGCHLD給父進程
    • 高位

KSE(kernel sheduling entity)內核調度所處理的對象

進程和線程都是KSE

略略略結合線程

linux/unix編程手冊-29(線程)

  • 共享數據
  • 建立線程比建立進程快10倍多(經過clone())

Pthread API

類型 描述
pthread_t 線程ID
pthread_mutex_t 互斥對象
pthread_mutexattr_t 互斥屬性對象
pthread_cond_t 條件變量
pthread_condattr_t 條件變量的屬性對象
pthread_key_t 線程持有數據的鍵
pthread_once_t 一次性初始化控制上下文
pthread_attr_t 線程的屬性對象

在線程中errno被標記成宏,展開後是返回一個可修改左值的函數

編譯Pthread API程序

設置cc -pthread

建立線程

#include<pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start) (void *), void *arg);
複製代碼
  • 新線程經過調用start(arg)開始
  • start的返回值不該等於PTHREAD_CANCELED,會被pthread_join捕獲?,不該在線程棧中分配start的返回值
  • pthread參數保存線程的惟一標識
  • attr指定了新線程的各類屬性

終止線程

  • start函數執行return並返回返回值
  • 線程調用pthread_exit()
  • 調用pthread_cancel()取消線程
  • 任意線程調用了exit(),或主線程執行了return 語句,致使進程中全部線程當即終止
#include<pthread.h>

void pthread_exit(void *retval);
複製代碼
  • retval指定了線程的返回值,同start返回值同樣不該再線程棧中分配
  • 若主線程調用pthread_exit則其餘線程繼續運行

獲取線程

# include<pthread.h>

pthread_t pthread_self(void);

int pthread_equal(pthread_t t1, pthread_t t2);

複製代碼

linux中線程ID全部進程中都惟一,其餘不必定

鏈接已終止線程

#include<pthread.h>
int pthread_join(pthread_t thread, void **retval);
// 等待thread標誌的線程終止,若是已經終止馬上返回
// 傳入以前鏈接過的thread 會致使未定義結果
複製代碼

pthread_joinwaitpid比較

  • 線程之間沒有層級關係,ex:線程A 建立B,B建立C,A能夠pthread_joinC, C能夠pthread_joinA, fork以後只有父進程能wait子進程
  • 沒法鏈接任意進程,已不能以非阻塞方式鏈接

若是線程既沒有分離也沒有鏈接,會產生殭屍線程

線程的分離

  • 不關心線程的返回,但願在線程終止時自動清理而且移除
  • 一旦線程detach,不能再join
  • 其餘線程調用了exit()或者主線程執行return,遭分離的線程仍是會受到影響
#inlcude<pthread.h>

int pthread_detach(pthread_t thread);
複製代碼

linux/unix編程手冊-30(線程同步)

互斥量(保護共享變量的訪問)

靜態分配互斥量

#include<pthread.h>

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);
複製代碼
  • 若是發起pthread_mutex_lock()的進程已然將目標互斥量鎖定
    • 線程死鎖(linux默認)
    • 返回EDEADLK錯誤
  • pthread_mutex_trylock()不會阻塞,失敗會返回EBUSY
  • pthread_mutex_timedlock() 能夠傳參數abstime, 超時後返回ETIMEOUT
  • 加鎖減鎖的操做比之間代碼邏輯操做小的多時,性能影響較小,比文件鎖,信號量加減鎖性能要高
  • 互斥量實現採用了機器語言級原子操做,內存中執行
死鎖
  • 相同順序對互斥量加鎖
  • 使用pthread_mutex_lock()鎖定第一個,以後用pthread_mutex_trylock()鎖定以後,若是鎖定失敗,釋放全部互斥量在重來,效率較低

動態初始化互斥量

#include<pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

void pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
複製代碼

必須動態初始化的case

  • 互斥量動態分配於堆中
  • 互斥量在棧中自動分配
  • 初始化由靜態分配,不使用默認屬性的互斥量
  • 動態分配的必須destory,在堆free或是函數棧消失前釋放

互斥量的類型(pthread_mutexattr_t)

  • PTHREAD_MUTEX_NORMAL 不具備死鎖檢測,對本身鎖定互斥量加鎖則死鎖,對未鎖定或其餘線程鎖定的互斥量解鎖會致使不肯定結果(Liunx上都會成功)
  • PTHREAD_MUTEX_ERRORCHECK 以上三種狀況都會犯回錯誤,但運行比前者慢,可做爲調試工具
  • PTHREAD_MUTEX_RECURSIVE 同一線程每次加鎖鎖計數器會加一,解鎖到0其餘線程才能加鎖,對於後兩種異常,解鎖都會失敗

條件變量(通知狀態的改變)

容許一個線程就某個共享資源的狀態變化通知其餘線程,並讓其餘線程等待這一通知

靜態分配條件變量

#include<pthread.h>

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

//喚醒任意單個線程
int pthread_cond_signal(pthread_cond_t *cond);

//喚醒全部線程
int pthread_cond_broadcast(pthread_cond_t *cond);

//解鎖互斥量mutex -> 阻塞調用線程,直到另外一線程cond發出型號->從新鎖定mutex (若是在2->3中發現mutex被鎖了,會再次休眠)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
複製代碼

pthread_cond_wait的操做

  • 解鎖互斥量mutex
  • 阻塞調用線程,直到另外一線程cond發出信號
  • 從新鎖定mutex

若是在2->3中發現mutex被鎖了,會再次休眠

阻塞時其餘線程得到CPU使用權,而不會盲輪訓之類的浪費資源

#include<pthread.h>

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

s = pthread_mutex_lock(&mtx);

while (...){
    s = pthread_cond_wait(&cond, &mtx);
}
s = pthread_mutex_unlock(&mtx); 
複製代碼

之因此while

  • 其餘線程搶先
  • 寬鬆原則
  • 虛假喚醒

動態分配條件變量

#include<pthread.h>

int pthread_cond_init(pthread_cond_t *mutex, const pthread_condattr_t * attr);

int pthread_cond_destroy(pthread_cond_t *mutex);
複製代碼
相關文章
相關標籤/搜索