一 linux進程模型
在傳統的unix模型裡有兩種船艦或修改進程的操作
fork用於建立一個新的進程
execve可以在進程中用另外的程式來替換當前啟動並執行進程
線程:提供了獨立的執行線索和堆棧段 卻共用資料區段
二 進程屬性
一個進程是一個正在執行的程式的執行個體,是linux基本調度單位,一個進程由如下元素組成:
程式的當前上下文,它是程式當前執行的狀態
程式的當前執行目錄
程式訪問的檔案和目錄
程式的信任狀態或者說存取權限,比如它的檔案模式和所有權
記憶體和其他分配給進程的系統資源
1 進程標識號
進程號 PID
父進程號 PPID
init進程 1 是核心第一個啟動的進程,init引導系統、啟動守護進程並且運行必要的程式。init是所有進程的父進程。
PID常見用法之一就是建立唯一的檔案名稱或目錄
另一個典型的用途是把PID寫入記錄檔作為日誌訊息的一部分
一個進程能夠用它的PPID向父進程發送訊號或訊息
getpid()
getppid();
2 Real和Effective標識號
<sys/types.h>
<unistd.h>
進程ID getpid() pid_t
父進程ID getppid() pid_t
真實使用者ID getuid() uid_t
有效使用者ID geteuid() uid_t
真實使用者組ID getgid() gid_t
有效使用者組ID getegid() gid_t
3 SetUID和SetGID程式
進程的真是ID和有效ID不相同的情況是正在運行中的程式設定了setUID或setGID後才出現的。
setUID程式和setGID程式是因為其有效UID或GID被設定為檔案的UID或GID而不是執行該程式的所有者或使用者組的UID或GID。setUID和setGID程式的目的就是讓使用者能夠執行具有特殊許可權的程式。
passwd程式是setUID為超級使用者的程式,也就是說,當執行該程式時,它的有效UID被設定為超級使用者的UID,從而讓它能夠修改/etc/passwd檔案,
4 使用者和使用者組資訊
有兩種方法能把UID轉換為人們可讀的名字
getlogin函數返回執行程式的使用者的登入名稱,一旦擁有了登入名稱,就可以把它作為參數傳遞給getpwname函數,這個函數能夠返回/etc/passwd檔案中與登入名稱相應的一行完整資訊。
另一種方法是把進程的UID傳遞給getpwuid()函數,這個函數也能返回/etc/passwd檔案中恰當的條目
#include<unistd.h>
char *gelogin();
返回一個只想字串的指標,這個字串包含有運行該進程的使用者的登入名稱,如果沒有得到這一資訊,則函數返回NULL
一旦有用了登入名稱,可以調用getpwnam檢索相應於使用者名稱的UID
#include<pwd.h>
struct passwd *getpwnam(const char *name);
返回的passwd結構指標指向靜態分配的記憶體,下次調用getpwnam時會覆蓋其中的內容
5 附加的進程資訊
除了進程、使用者和組ID之外,系統調用和庫函數還能夠檢索進程的其他屬性,比如資源利用情況和執行次數。
Wall clock time(牆上時鐘時間) 是流逝的時間
User CPU time(使用者CPU時間)是進程花在執行使用者模式代碼上的時間總量
System CPU time(系統CPU時間)是花在執行核心代碼上的時間總量
通過times或getrusage可以獲得這些資訊
進程的資源利用情況 getrusage
#include <sys/teimes.h>
clock_t times(struct tms *buf);
times返回牆上時鐘時間,buf是指向tms結構的指標,這個結構儲存當前進程的時間。
#include<sys/times.h>
struct tms{
clock_t tms_utime;使用者CPU時間
clock_t tms_stime;系統CPU時間
clock_t tms_cutime;孩子的使用者CPU時間
clock_t tms_cstime;孩子的系統CPU時間
}
這些值都是始終滴答數而不是秒數,使用sysconf函數能夠把始終滴答數轉換為秒數
long tps=sysconf(_SC_CLK_TCK);
_SC_CLK_TCK
是定義每秒有多少滴答的宏,sysconf傳回值的類型是long,程式用他來計算進程啟動並執行時間有多少秒,
當程式調用system函數時,它產生一個子進程,然後是子進程而不是父進程完成所有工作並消耗了CPU時間
grep操作是I/O密集型而非CPU密集型的操作
進程的資源還包括進程的記憶體使用量量、記憶體如何進行組織、進程訪問記憶體方式的類型、進程進行I/O操作的類型和數量以及進程產生的網路活動的數量和種類。
<sys/resource.h>
rusage結構
struct rusage
{
struct timeval ru_utime;//執行使用者模式代碼的時間
struct timeval ru_sutime;//執行核心代碼的時間
long ru_maxrss;
long ru_maxixrss;
long ru_maxidrss
long ru_maxisrss;
long ru_minflt//次要失效數(記憶體訪問沒有引起磁碟訪問)
long ru_majflt;//主要失效數(記憶體訪問引起磁碟訪問)
long ru_nswap;//因主要錯誤而從磁碟讀取的記憶體頁數
long ru_inblock;
long ru_outblock;
long ru_msgsnd;
long ru_msgrcv;
long ru_nsiquals;
long ru_nvcsw;
long ru_nivcsw;
}
#include <sys/times.h>
#include <sys/resource.h>
#include <unistd.h>
int getrusage(int who,struct rusage *usage);
who:
RUSAGE_SELF
RUSAGE_CHILDREN
6 會話和進程組
一個進程組是相關進程的幾何,這些相關進程通常是在一個管道中的命令序列。
在進程組中的所有進程都具有相同的進程組號,即PGID。
會話 是由一個或多個進程組構成的,會話領導進程是建立會話的進程。每個繪畫都有唯一的標識號,成為會話ID,它只是會話領導進程的PID。會話對進程組起的作用和進程組對單個進程起的作用一樣。
建立進程:
7 使用system函數
system傳遞給/bin/sh -c來執行string所指定的命令,string中可以包含選項和參數,接著整個命令列又傳遞給系統調用execve。如果沒有找到/bin/sh,system返回127
#include<stdlib.h>
int system(const char *string);
#include<stdio.h>
void perror(const char *s);
perror()用來將上一個函數發生錯誤的原因輸出到標準錯誤(stderr)。參數s所指的字串會先列印出,後面再加上錯誤原因字串。此錯誤原因依照全域變數errno的值來決定要輸出的字串。
8 fork系統調用
#include<unistd.h>
pid_t fork(void);
如果fork執行成功,就會向父進程返回子進程的PID,並向子進程返回0,這意味著即使你只調用嗯fork一次,它可以返回兩次。
fork建立的新進程是和父進程一樣的副本
子進程沒有繼承父進程的逾時設定、父進程建立的檔案鎖,或者未決訊號。
fork的非同步行為意味著不能在子進程中執行以來於父進程的代碼。
exec函數族
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,const char *const argv[]);
int execve(const char *path,const char *const argv[],char *const envp[]);
int execvp(const char *file,char *const argv[]);
execve接受3個參數:path、argv和envp。path是要執行的二進位檔案或指令碼的完整路徑。
argv是要傳遞給程式的完整參數列表,包括argv[0],它一般是執行程式的名字
envp是只想用於執行execed程式的專門環境指標。
三個名字中含有l的函數希望接收以逗號分割的參數列表,列表以NULL指標作為結束標誌,這些參數將床底給被執行的程式。
但是名字中包含v的函數接受一個向量,也就是指向以空結尾的字串的指標數組。
兩個以e結尾的函數 execve execle可以讓你為被執行的程式建立專門的環境,這個環境儲存在envp中,它也是一個指向以空結尾的字串數組的指標,數組中每個字串也是以空結尾的字串數組的指標,數組中每個字串也是以空結尾。每個字串的形式為“name=value”,name是環境變數的名字,而value是值。
例如
char *envp[]={"PATH=/bin:/usr/bin","USR=joeblow",NULL};
其他4個函數隱式的通過全域變數environ接受他們的環境
使用putenv和getenv可以操控這些函數繼承的環境
#include<stdlib.h>
int putenv(cosnt char *string);
char *getenv(const char *name);
char envval[]={"MYPATH=/usr/local/someapp/bin"};
putenv(envval);
getenv("MYPATH");
9 popen函數
popen使用管道來工作
#include<stdio.h>
FILE *popen(const char *conmmand,const char *type);
int pclose(FILE *stream);
10 等待進程--wait函數族
僵進程:在父進程有機會使用wait或waitpid手機它的退出狀態之前就終止的子進程。
使用wait或waitpid調用可以收集子進程的退出狀態。
#include<sys/wait.h>
#include<sys/types.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int options);
status儲存子進程的退出狀態,pid是等待進程的PID,取值:
-1 等待任何PGID等於PID的絕對值的子進程
1 等待任何子進程
0 等待任何PGID等於調用進程的子進程
》0 等待PID等於pid的子進程
options:
WNOHANG,沒有子進程退出時,立即返回。
WUNTRACED,應該因為存在沒有報告狀態的進程而返回。
11 殺死程式
一個進程由於以下5個原因中的一個而終止:
它的main函數調用了return
它調用了exit
它調用了_exit
它調用了abort
它被一個訊號終止
exit導致程式正常終止,並且返回父進程的狀態,用atexit登記的函數也被執行。
_exit立即終止調用它的進程,用atexit登記的函數不被執行
#include <stdlib.h>
void abort(void);
一個進程可以使用kill函數殺死另一個進程
#include<signal.h>
#include<sys/types.h>
int kill(pid_t pid,int sig);
pid指定了要殺死的進程,而sig是要發送的訊號。
12 訊號
訊號是由相同或不同的進程向一個進程傳遞的事件,訊號通常用來向一個進程通知例外狀況事件
每個訊號都以SIG開頭,這些名字對應於正整數常量,成為訊號量<signal.h>
當進程受到一個訊號後,它可以對訊號採取如下三種措施之一:
忽略這個訊號
捕獲這個訊號,這將導致執行一段成為訊號處理器的代碼,這叫做處理訊號
允許執行訊號的預設操作
訊號集合是一個C資料類型sigset_t <signal.h>
發送訊號:
從編程的角度看,向正在啟動並執行程式發送訊號的方法有兩種:使用kill命令和使用kill函數。
kill能夠用來發送出了殺死一個進程(SIGKILL,SIGTREM,SIGQUIT)之外的其他訊號。
每個進程都能決定怎樣響應除了SIGSTOP和SIGKILL之外的其他所有訊號,而這兩個訊號不能被捕捉或者忽略。捕捉訊號最簡單的方法不是真正去捕獲訊號而是等待他們被發送過來。alarm函數設定了一個定時器,當定時器時間到時就發送SIGALRM訊號,pause函數也有類似功能,但是它是把進程掛起直至進程受到任何訊號。
#include<unistd.h>
unsigned int alarm(unisgned int seconds);
seconds是計時器時間到後時鐘的秒數,如果沒有設定其他逾時,則傳回值為0,否則傳回值為前面安排的逾時中保留的秒數。一個進程只能設定一次逾時,把seconds設定為0就會取消前面設定。
#include<unistd.h>
int pause();
只有進程捕獲到一個訊號時,pause才返回調用進程。
建立一個訊號集合----設定想要捕獲的訊號------向核心登記一個訊號處理器----等候捕獲訊號
定義一個訊號處理器
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signum);
int sigdelset(sigset_t *set,int signum);
int sigismember(const sigset_t *set,int signum);
set是一個sigset_t類型的訊號集合。sigemptyset初始化訊號集合set,從集合中取出所有的訊號。sigfillset初始化訊號集合,在集合中包含所有的訊號。sigaddset把訊號signum添加到訊號集合中。sigdelset從幾何中刪除訊號signum。
建立一個訊號集合
使用sigemptyset或sigfillset初始化一個訊號集合。如果你建立一個空的訊號集合,需要使用sigaddset向感興趣的集合中加入訊號。如果建立了一個滿的訊號集合,需要使用sigdelset從訊號掩碼中刪除訊號。
sigset_t newset;
sigemptyset(&newset);//建立空的訊號集合
sigaddset(&newset,SIGCHLD);//往訊號集合中添加訊號
sigismember(&newset,SIGCHLD);//檢查訊號集合中是否有指定訊號
登記一個訊號處理器
sigprocmask 設定或修改當前的訊號掩碼
sigaction 調用為訊號或要捕獲的訊號登記一個訊號處理器
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
how:
SIG_BLOCK set包含其他要阻塞的訊號
SIG_UNBLOCK set包含要解除阻塞的訊號
SIG_SETMASK set包含新的訊號掩碼
NULL,被忽略
如果set為NULL,當前的掩碼就儲存在oldset中,如果oldset為NULL,被忽略。
#include<signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
sigaction為signum所指定的訊號設定訊號處理器,sigaction結構描述了訊號的部署。
struct sigaction{
void (*sa_hundler) (int);
sigset_t sa_mask;
int sa_flags;
void (*sa_hundler) (void);
}
sa_handler是函數指標,這個函數規定了當signum中的訊號產生後,要調用的處理器或函數。
也可以為
SIG_DFL signum預設動作
SIG_IGN 忽略signum這個訊號
13 活動訊號
#include<signal.h>
int sigpending(sigset_t *set);
未決訊號的集合在set中返回。
14 進程調度
#include<sched.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>
int sched_setcheduler(pid_t pid,int policy,const struct sched_param *p);
int sched_get_prority_max(int policy);
int sched_get_prority_min(int policy);
int getprority(int which,int who);
int setprority(int which,int who,int prio);
int nice(int inc);