进程的特权基于用户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",按照这个理论也就是说,数据结构
生成一个程序,全部者为root;设置 SUID 标志,而后以普通用户(如 ww)启动,此时进程的三个ID应该是:ruid=ww;euid=root;保存的设置用户ID=root函数
此时调用 setreuid(getuid(),getuid());此时:ruid=ww;euid=ww;保存的设置用户ID=root;[由于此时 euid 的新值与原来的 ruid 一致,因此不更改保存的设置用户ID]测试
再调用 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(),
文件建立模式屏蔽字,在建立文件(包括任何类型的文件)时起做用.如:在调用 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,是一个 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);设置屏蔽信号集,或者获取当前屏蔽信号集,流程以下:
若是 oldset 不为0,则将当前屏蔽信号集复制到 oldset 指向的信号集中.
若是 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);用来获取当前的信号处理方式,或者设置新的信号处理方式,流程以下:
若是 oldact 不为0,则将当前对信号 signum 的处理方式复制到 oldact 指向的缓冲区中.
若是 act 不为0,则将 act 设置为信号 signum 新的处理方式.
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,存放的是有初始值的变量,因此须要将变量的初始值存放在可执行文件中,在调用 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);
pid,ppid;即进程 ID,父进程 ID.
子进程的进程对象中,tms_utime,tms_stime,tms_cutime,tms_cstime 这几个字段被清为0.
子进程的进程对象中,剩余的闹钟时间被清为0.
子进程的进程对象中,未处理的信号集被清空.
父进程拥有的文件锁并不会被继承.
关闭设置了 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 信号的处理方式更改成默认.