进程对象[TODO]

进程对象

进程的特权

  • 进程的特权基于用户ID,所以更改进程对象中用户ID就像更改进程的特权,如下是若干个可以更改进程对象用户ID的接口:

  • setuid(uid)

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;

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.

  • 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

进程地址空间

211542_AID2_1383479.jpg

.rodata,.data,.bss 区别:

  • .rodata,.data,存放的是有初始值的变量,所以需要将变量的初始值存放在可执行文件中,在调用 exec() 时,会从可执行文件中将初始值加载到进程的地址空间中.

  • .bss 存放的是初始值为0的变量,所以不需要将其初始值保存在可执行文件中,

int a = 33;/* 此时需要在可执行文件中占用 4 个字节来保存变量 a 的初始值 33. */

环境变量

210135_Oapn_1383479.png

  • 环境字符串,结构为 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 信号的处理方式更改为默认.

































































































































转载于:https://my.oschina.net/u/1383479/blog/339185

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值