進程對象[TODO]

進程對象

進程的特權

  • 進程的特權基於用戶ID,因此更改進程對象中用戶ID就像更改進程的特權,以下是若干個能夠更改進程對象用戶ID的接口:node

  • setuid(uid)shell

if(進程具備超級用戶特權)/* 是指有效用戶ID==0 */
    實際用戶 ID=uid;
    有效用戶 ID=uid;
    保存的設置用戶 ID=uid;
else if(uid==實際用戶 ID || uid==保存的設置用戶 ID)
    有效用戶 ID=uid;
else return -1;
  • setgid(gid)編程

if(進程具備超級用戶特權)/* 是指有效用戶ID==0 */
    實際組 ID=gid;
    有效組 ID=gid;
    保存的設置組 ID=gid;
else if(gid==實際組 ID || gid==保存的設置組 ID)
    有效組 ID=gid;
else return -1;
  • setreuid(ruid,euid)數組

if(ruid == 實際用戶ID || ruid == 有效用戶ID)
    實際用戶ID = ruid;
if(euid == 實際用戶ID || euid == 有效用戶ID || euid==保存的設置用戶ID)
    有效用戶ID = euid;
    保存的設置用戶ID = 有效用戶ID;
/* If the real user ID is set or the effective user ID is set to a value not equal to the previous real user ID, the saved set-user-ID will be set to the new effective user ID.
   TODO,如何理解這句話,  */
    • 我對這句話(上面註釋裏買你的英語)的理解就是"當實際用戶ID或有效用戶ID更改成一個不一樣於原有的實際用戶ID的值時,保存的設置用戶ID會設置爲新的有效用戶ID",按照這個理論也就是說,數據結構

    1. 生成一個程序,全部者爲root;設置 SUID 標誌,而後以普通用戶(如 ww)啓動,此時進程的三個ID應該是:ruid=ww;euid=root;保存的設置用戶ID=root函數

    2. 此時調用 setreuid(getuid(),getuid());此時:ruid=ww;euid=ww;保存的設置用戶ID=root;[由於此時 euid 的新值與原來的 ruid 一致,因此不更改保存的設置用戶ID]測試

    3. 再調用 setreuid(getuid(),root);此時 root== 保存的設置用戶ID,因此理論上應該會將有效用戶ID設置爲 root;可是測試發現此時提示 Operation not permitted,並且有效用戶ID沒變,猜想應該是第2步將保存的設置用戶ID修改成了有效用戶ID的新值(即ww),who know;ui

ww$ cat main.c
#include <unistd.h>
#include <stdio.h>
 
int main(int argc,char *argv[]){
    int ruid = getuid();
    int euid = geteuid();
    printf("ruid:%d;euid:%d\n",getuid(),geteuid());
    if( setreuid(ruid,ruid)<0 )
        printf("%m\n");
    printf("ruid:%d;euid:%d\n",getuid(),geteuid());
    if( setreuid(ruid,euid)<0 )
        printf("%m\n");
    printf("ruid:%d;euid:%d\n",getuid(),geteuid());
    return 0;
}
ww$ gcc main.c -o test
ww$ su
密碼: 
root# chown root:root test 
root# chmod +s test 
root# ls -l test 
-rwsrwsr-x 1 root root 8671  2月  4 00:03 test
root# exit
ww$ ./test 
ruid:1000;euid:0
ruid:1000;euid:1000
Operation not permitted
ruid:1000;euid:1000
  • setregid(),同 setreuid(),只是將用戶ID更改成組ID.spa

  • seteuid(uid)命令行

if(進程具備超級用戶特權)/* 是指有效用戶ID==0 */
    有效用戶 ID=uid;
else if(uid==實際用戶 ID || uid==保存的設置用戶 ID)
    有效用戶 ID=uid;
else return -1;
  • setegid(),同 seteuid(),

umask

  • 文件建立模式屏蔽字,在建立文件(包括任何類型的文件)時起做用.如:在調用 open(),mkdir() 建立文件時,文件的實際權限 = mode(參數指定的權限) & ~umask.

/* 此時新建目錄的權限應該是 rwxrwxrwx(0777),可是因爲 umask=0002,
因此目錄的實際權限: 0777 & ~0002 = 111111101b,即 rwxrwxr-x. */
safe_mkdir(argv[1],0777);

當前工做目錄

  • 在進程對象中,並無保存當前工做目錄的完整路徑名,而只是存放着一個指針,該指針指向着當前工做目錄對應的 v-node(參考Unix環境高級編程第三章,打開文件的數據結構).

int origin_cwd_fd = safe_open(".",O_RDONLY);/* 保存當前工做目錄,即不使用 getcwd() 來保存當前工做目錄 */
safe_chdir("..");/* 切換當前工做目錄 */
safe_fchdir(origin_cwd_fd);/* 恢復當前工做目錄 */

附加組ID

  • 附加組 ID,是一個 gid_t 類型的數組,主要用於測試進程對文件是否具備訪問的權限.

  • 經過 getgroups()/setgroups() 來設置與獲取進程組 ID,具體函數說明以下:

/**
 * 獲取進程當前附加組 ID.
 * @param size 若爲0,則返回附加組 ID 的數目,進程能夠先獲取附加組 ID 的數目,而後在複製足夠的空間.
 */
int getgroups(int size, gid_t *list);

/** 將 list 指向的長度爲 size 的數組設置爲進程的附加組 ID,內核不會檢測附加組 ID 的有效性.如
@code
    gid_t group_list[]={1,2,3,4,5};//內核不會檢測是否存在 ID 爲 1 的用戶組.
    setgroups(sizeof(group_list)/sizeof(gid_t),group_list);
@endcode
 */
int setgroups(size_t size, const gid_t *list);

資源限制

  • 每一個進程都有一組資源限制,用於限制該進程對資源的使用,防止過分使用,相關數據結構與接口以下:

struct rlimit {
   rlim_t rlim_cur;/* 資源的的當前限制值,即進程對資源的使用不能超過該值 */
   rlim_t rlim_max;/* rlim_cur 的最大值, */
};
/* 若 rlim_cur,rlim_max 值爲 RLIM_INFINITY,則表示無限制  */

int getrlimit(int resource, struct rlimit *rlim);/* 獲取對進程在資源 resource 的限制,並存放在 rlim 指向的結構體中. */
int setrlimit(int resource, const struct rlimit *rlim);
int main(int argc,char *argv[]){
    struct rlimit res_limit;
    getrlimit(RLIMIT_CPU,&res_limit);/* 獲取 CPU 使用時間的資源限制 */
    ByteArray str("cpu: ");
    print_rlimit(&res_limit,str);
    puts(str.constData());

    res_limit.rlim_cur = 1;
    setrlimit(RLIMIT_CPU,&res_limit);/* 將對 CPU 使用時間的資源限制設置爲 1 s. */
    for(;;);/* 執行 1s 後將收到信號 SIGXCPU */
    return 0;
}
// 執行輸出:
$./Test
cpu: rlim_cur: 無限制,rlim_max: 無限制
超出 CPU 時限 (核心已轉儲)

剩餘鬧鐘時間

  • uint alarm(uint seconds);將剩餘鬧鐘時間設置爲 seconds,並返回以前的剩餘鬧鐘時間,當 seconds 大於0時,剩餘鬧鐘事件會每隔1s自減1直至爲0,當剩餘鬧鐘時間減至0時,會發送 SIGALRM 信號到當前進程.

屏蔽信號集

  • 屏蔽信號集,參見"Unix環境高級編程-9-信號"記錄了其詳細用途.

  • int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);設置屏蔽信號集,或者獲取當前屏蔽信號集,流程以下:

  1. 若是 oldset 不爲0,則將當前屏蔽信號集複製到 oldset 指向的信號集中.

  2. 若是 set 不爲0,則按照 how 的值來設置屏蔽信號集,how 可取:

    • SIG_BLOCK,此時新的屏蔽信號集爲原有的屏蔽信號集與 set 的並集.

    • SIG_UNBLOCK,此時新的屏蔽信號集爲原有的屏蔽信號集與 set 補集的交集,即從原有的屏蔽信號集中移除在 set 中包含的信號

    • SIG_SETMASK,此時將 set 設置爲新的屏蔽信號集.

未決信號集

  • 未決信號集,存放着已經產生,可是因爲被進程阻塞而未遞送給進程的信號.

  • int sigpending(sigset_t *set);將當前線程與當前進程未決信號集的並集存入 set 中.

信號處理方式

  • int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);用來獲取當前的信號處理方式,或者設置新的信號處理方式,流程以下:

  1. 若是 oldact 不爲0,則將當前對信號 signum 的處理方式複製到 oldact 指向的緩衝區中.

  2. 若是 act 不爲0,則將 act 設置爲信號 signum 新的處理方式.

struct sigaction

  • struct sigaction,具體地描述了信號處理方式,具體結構:

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
};
  • sa_handlder,能夠是信號處理函數的指針,或者是 SIG_DFL,SIG_IGN.

  • sa_sigaction,只能是信號處理函數的指針,當 sa_flags 中設置了 SA_SIGINFO 標誌時,調用 sa_sigaction 指向的信號處理函數;不然調用 sa_handler 指向的信號處理函數.

  • sa_mask,在調用信號處理函數以前,會將 sa_mask 添加到進程的屏蔽信號集中(SIG_BLOCK),並在調用信號處理函數以後,恢復進程的屏蔽信號集.

  • sa_flags,位掩碼,常見的標誌以下:

    • SA_RESTART,自動重啓被中斷的系統調用,以下:

int main(int argc,char *argv[]){
    struct sigaction int_sigaction;
    sigemptyset(&int_sigaction.sa_mask);
    int_sigaction.sa_flags = argc>1 ? SA_RESTART : 0;
    int_sigaction.sa_handler = sig_handler;
    sigaction(SIGINT,&int_sigaction,0);

    printf("sa_flags=%s\n",int_sigaction.sa_flags==SA_RESTART ? "restart" : "0");
    int ret = read(0,&argc,sizeof(argc));
    if(ret < 0)
        printf("func=read,errno=%d,error=%s\n",errno,strerror(errno));

    return 0;
}
$ ./Test --restart # 此時會設置 SA_RESTART
sa_flags=restart
^C捕捉到信號,信號=SIGINT # 在執行信號處理函數以後自動重啓被中斷的系統調用 read(),
^C捕捉到信號,信號=SIGINT
^C捕捉到信號,信號=SIGINT
^C捕捉到信號,信號=SIGINT
^\退出 (核心已轉儲)
$ ./Test 
sa_flags=0
^C捕捉到信號,信號=SIGINT # 在執行信號處理函數以後,read() 出錯返回.以下:
func=read,errno=4,error=Interrupted system call
    • SA_NODEFER,執行信號處理程序以前,內核不自動阻塞該信號(不然,內核會將該信號添加到進程的信號屏蔽集中)

int main(int argc,char *argv[]){
    struct sigaction int_sigaction;
    sigemptyset(&int_sigaction.sa_mask);
    int_sigaction.sa_flags = argc>1 ? SA_NODEFER : 0;
    int_sigaction.sa_handler = sig_handler;
    sigaction(SIGINT,&int_sigaction,0);

    print_cur_sigprocmask("在調用 pause() 以前");
    pause();
    print_cur_sigprocmask("在調用 pause() 以後");
    return 0;
}
$ ./Test 
在調用 pause() 以前,屏蔽信號集: 空
^C在信號處理函數中,屏蔽信號集: SIGINT # 能夠看出,在執行信號處理函數以前,內核自動阻塞了 SIGINT 信號
捕捉到信號,信號=SIGINT
在調用 pause() 以後,屏蔽信號集: 空
$ ./Test --nodefer
在調用 pause() 以前,屏蔽信號集: 空
^C在信號處理函數中,屏蔽信號集: 空 # 能夠看出,在設置了 SA_NODEFER 以後,在執行信號處理函數以前,內核不會阻塞 SIGINT 信號
捕捉到信號,信號=SIGINT
在調用 pause() 以後,屏蔽信號集: 空
    • SA_RESETHAND,在執行信號處理函數,將信號的處理方式設置爲默認(SIG_DFL).

void sig_handler(int s){
    print_sigaction(SIGINT,"在信號處理函數中");
    return;
}
int main(int argc,char *argv[]){
    struct sigaction int_sigaction;
    sigemptyset(&int_sigaction.sa_mask);
    int_sigaction.sa_flags = argc>1 ? SA_RESETHAND : 0;
    int_sigaction.sa_handler = sig_handler;
    sigaction(SIGINT,&int_sigaction,0);

    print_sigaction(SIGINT,"在調用 pause() 以前");
    pause();
    print_sigaction(SIGINT,"在調用 pause() 以後");
    return 0;
}
$ ./Test 
在調用 pause() 以前;信號=SIGINT;處理方式=捕捉;函數=0x400efd
^C在信號處理函數中;信號=SIGINT;處理方式=捕捉;函數=0x400efd 
在調用 pause() 以後;信號=SIGINT;處理方式=捕捉;函數=0x400efd
$ ./Test --reset-handler
在調用 pause() 以前;信號=SIGINT;處理方式=捕捉;函數=0x400efd
^C在信號處理函數中;信號=SIGINT;處理方式=默認 # 此時,在調用信號處理函數以前,已經將對信號 SIGINT 的處理方式,設置爲默認.
在調用 pause() 以後;信號=SIGINT;處理方式=默認
    • SA_SIGINFO,參見 sa_handler,sa_sigaction.

    • SA_NOCLDWAIT,此時不會產生殭屍進程,大概是這樣的,當子進程終止時,內核會根據子進程的終止狀態生成 siginfo_t 對象,而後調用 SIGCHLD 的信號處理函數,而後內核回收子進程,不會產生殭屍進程.

    • SA_NOCLDSTOP,此時丟棄子進程由於暫停,或者從新喚醒而發出 SIGCHLD 信號,即此時不會調用信號處理函數.

    • SA_NOCLDWAIT,SA_NOCLDSTOP 僅當爲 SIGCHLD 設置信號處理方式時,纔有效.

/* 解析命令行參數,獲取 sigaction 中 sa_flags 的值 */
int get_sa_flags(int argc,char *argv[]){
    if(argc <= 1)
        return 0;
    if(strcmp(argv[1],"--no-stop") == 0)
        return SA_NOCLDSTOP;
    if(strcmp(argv[1],"--no-wait") == 0)
        return SA_NOCLDWAIT;
    return SA_NOCLDSTOP|SA_NOCLDWAIT;
}

/* 這個例子演示了 SA_NOCLDWAIT,SA_NOCLDSTOP 標誌 */
int main(int argc,char *argv[]){
    if(safe_fork() == 0){
        printf("child:pid=%d\n",getpid());
        for(int i=0;i>=0;++i) ;/* 大約會花費 3.5 s */
        for(int i=0;i>=0;++i) ;
        exit(33);
    }else{
        struct sigaction chld_sigaction;
        chld_sigaction.sa_flags = SA_SIGINFO|get_sa_flags(argc,argv);
        sigemptyset(&chld_sigaction.sa_mask);
        chld_sigaction.sa_sigaction = chld_sighandler;
        sigaction(SIGCHLD,&chld_sigaction,0);
        puts("parent:ready");
        for(;;)
            pause();
    }
    return 0;
}
$ ./Test  #首先即不設置 NO_CLDWAIT,也不設置 NO_CLDSTOP
parent:ready
child:pid=3640
捕捉到了 SIGCHLD 信號;child_pid=3640;status=暫停;信號=SIGSTOP #在另一個 shell 窗口運行 kill -SIGSTOP 3640,能夠看到此時父進程收到了子進程由於暫停而發送的 SIGCHLD 信號.
捕捉到了 SIGCHLD 信號;child_pid=3640;status=從新喚醒;信號=SIGCONT #在另一個 shell 窗口運行 kill -SIGCONT 3640,能夠看到此時父進程收到了子進程由於暫停而發送的 SIGCHLD 信號.
捕捉到了 SIGCHLD 信號;child_pid=3640;status=正常退出;exit_code=33 
# 此後在另一個窗口運行 ps -e | grep Test,能夠看到
# 3639 pts/3    00:00:00 Test
# 3640 pts/3    00:00:07 Test <defunct> 子進程變爲了殭屍進程.
^C #終止父進程

$ ./Test --no-wait # 此時只設置 SA_NOCLDWAIT
parent:ready
child:pid=3648
捕捉到了 SIGCHLD 信號;child_pid=3648;status=暫停;信號=SIGSTOP # 同上,父進程會收到子進程由於暫停,或者暫停後喚醒而發出的 SIGCHLD 信號.
捕捉到了 SIGCHLD 信號;child_pid=3648;status=從新喚醒;信號=SIGCONT
捕捉到了 SIGCHLD 信號;child_pid=3648;status=正常退出;exit_code=33
# 此後在另一個窗口運行 ps -e | grep Test,能夠看到
# 3647 pts/3    00:00:00 Test 此時只有父進程,子進程沒有變爲殭屍進程,
^C # 終止父進程

$ ./Test --no-stop
parent:ready
child:pid=3656
# 此時父進程沒法收到子進程由於暫停,或者從新喚醒發出的 SIGCHLD 信號.
捕捉到了 SIGCHLD 信號;child_pid=3656;status=正常退出;exit_code=33 
^C

進程地址空間


.rodata,.data,.bss 區別:

  • .rodata,.data,存放的是有初始值的變量,因此須要將變量的初始值存放在可執行文件中,在調用 exec() 時,會從可執行文件中將初始值加載到進程的地址空間中.

  • .bss 存放的是初始值爲0的變量,因此不須要將其初始值保存在可執行文件中,

int a = 33;/* 此時須要在可執行文件中佔用 4 個字節來保存變量 a 的初始值 33. */

環境變量


  • 環境字符串,結構爲 Name=Value;

/** 遍歷 environ 指向的環境表,查找名爲 name 的環境變量,而後獲取該環境變量的值.
若不存在名爲 name 的環境變量,則返回 0,不然返回一個指針,指向着環境字符串的 value 部分. */
char *getenv(const char *name);

/** 修改,或者新增一個環境變量,string 的格式要求爲:Name=Value,若名爲 Name 的環境變量已經存在,則刪除之.
該函數僅是將 string 放入環境表中,並不會爲環境字符串分配空間. */
int putenv(char *string);

/** 修改,或者新增環境變量,此時會爲環境字符串分配空間,而後將 name=value 複製到新分配的空間中,而後將空間的首地址存放到環境表中.
@param overwrite 指示着當名爲 name 的環境變量已經存在時,是否修改該環境變量. */
int setenv(const char *name, const char *value, int overwrite);

/** 刪除名爲 name 的環境變量.
此時就是遍歷 environ 指向的環境表,肯定 name 所在的環境表項,而後從環境表中益處該項. */
int unsetenv(const char *name);

fork()對進程對象的更改

  • pid,ppid;即進程 ID,父進程 ID.

  • 子進程的進程對象中,tms_utime,tms_stime,tms_cutime,tms_cstime 這幾個字段被清爲0.

  • 子進程的進程對象中,剩餘的鬧鐘時間被清爲0.

  • 子進程的進程對象中,未處理的信號集被清空.

  • 父進程擁有的文件鎖並不會被繼承.

exec*()對進程對象的更改

  • 關閉設置了 FD_CLOEXEC 標誌的文件描述符.

  • 可能會更改進程對象的有效ID,保存的設置ID.以下:

if(可執行文件.SUID==1)
    進程.有效用戶 ID = 可執行文件.擁有者ID.
    進程.保存的設置用戶ID = 進程.有效用戶ID
if(可執行文件.SGID == 1)
    進程.有效組 ID = 可執行文件.擁有組 ID.
    進程.保存的設置組ID = 進程.有效組ID.
  • 信號的信號處理方式,即若信號的信號處理方式爲捕捉,則更改成默認.以下:

/* ./test 程序 */
int main(int argc,char *argv[]){
    print_sigaction(SIGCHLD);
}
/* ./Test */
int main(int argc,char *argv[]){
    struct sigaction chld_sigaction;
    chld_sigaction.sa_flags = SA_SIGINFO;
    sigemptyset(&chld_sigaction.sa_mask);
    chld_sigaction.sa_sigaction = chld_sighandler;
    sigaction(SIGCHLD,&chld_sigaction,0);

    print_sigaction(SIGCHLD);
    execl("./test","./test",(char*)0);
    return 0;
}
$ ./Test 
信號=SIGCHLD;處理方式=捕捉(SA_SIGINFO);函數=0x40115d # 在執行 execl() 以前,
信號=SIGCHLD;處理方式=默認 # execl() 將對 SIGCHLD 信號的處理方式更改成默認.
相關文章
相關標籤/搜索