《Unix环境高级编程》chapter08 进程控制(二)

chapter08 进程控制(二)

章节知识总结

如前面所述,不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

这里对子进程与父进程的结束顺序进行相关讨论:

  1. 子进程后于父进程结束:对于父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程收养。其操作过程大致是:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID),这样就保证了每个进程都有一个父进程。因为init被编写成无论何时只要有一个子进程终止,init就会调用一个wait函数取得其终止状态。所以,"一个init的子进程"可能指的是init直接产生的进程,也可能是其父进程已终止,由init收养的进程。

  2. 子进程先于父进程结束:内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用waitwaitpid时,可以得到这些信息。在UNIX术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程(zombie)

UNXI处理僵死进程
  1. 使用top指令,若zombie不为0表示有僵死进程
  2. 使用ps -A -ostat,ppid,pid,cmd | grep -e '^[zZ]'查找僵死进程
  3. 使用kill -HUP ppid清理僵死进程

当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。当进程接收到SIGCHLD信号时,我们希望调用waitwaitpid进行处理,这是waitwaitpid会立刻返回。但是如果在随机时间点调用waitwaitpid,则进程可能会阻塞。

#include<sys/wait.h>

//两个函数返回值:若成功,返回进程ID;若出错,返回0
pid_t wait(int *statloc);   //如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内
pid_t waitpid(pid_t pid,int *statloc,int options);      //可选等待哪个进程结束

waitwaitpid这两个函数有以下两个区别:

  • 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞。
  • waitpid并不等待其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。

对于waitpid函数中pid参数的作用解释如下:

  1. pid==-1:等待任一子进程,此种情况下,watpidwait等效
  2. pid>0:等待进程ID与pid相等的子进程
  3. pid==0:等待组ID等于调用进程组ID的任一子进程
  4. pid<-1:等待组ID等于pid绝对值的任一子进程

waitpid含提供了wait函数没有提供的3个功能:

  1. waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态
  2. waitpid提供了一个wait的非阻塞版本。有时希望获取一个子进程的状态,但不想阻塞
  3. waitpid通过WUNTRACKEDWCONETINUED选项支持作业控制
实例:两次fork阻止僵死进程的产生
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>


#define oops(m,x) { perror(m); exit(x); }

/*
通过两次fork,使得第二个子进程由init进程接管
避免了僵尸进程的产生
*/

int main(void)
{
    pid_t pid;
    if((pid=fork())<0){
        oops("fork error",1);
    }
    else if(pid==0){    /* first child */
        if((pid=fork())<0){
            oops("fork error",1);
        }
        else if(pid>0)
            exit(0);

        sleep(2);       //这里需要sleep(2),否则second child和first child的执行顺序不确定,无法保证second child被init接管
        printf("seoncd child,parent pid = %ld\n", (long)getppid());
        exit(0);
    }
    if(waitpid(pid,NULL,0)!=pid){
        oops("waitpid error",2);
    }
    exit(0);
}

UNIX还提供了其他更加复杂的wait系列函数,比如waitidwait3wait4等.

函数exec:当进程调用一种exec函数时,该进程执行的程序会完全替换为新程序,而新程序则从其main函数开始执行。

因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

#include<unistd.h>

/*
对exec函数名称的总结:
1. 带`l`的:传入以NULL结尾的可变形参
2. 带`v`的:传入以NULL结尾形参数组
3. 带`p`的:传入程序的名称,在PATH环境变量中查找该名称(即传入参数filename的)
4. 带`e`的:传入环境变量
*/

int execl(const char* pathname,const char* arg0,.../* (char*)0 */);
int execv(const char* pathname,char* const argv[]);
int execle(const char* pathnme,const char* arg0,.../* (char*)0,char* const envp[]*/)
int execve(const char* pathname,char* const argv[],char* const envp[]);
int execlp(const char* filename,const char* arg0,.../* (char*)0 */);
int execvp(const char* filename,char* const argv[]);
int fexecve(int fd,char* const argv[],char* const envp[]);

实例:使用exec函数
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

#define oops(m,x) { perror(m); exit(x); }

char* env_init[]={"USER=unknown","PATH=/tmp",NULL};

int main(void)
{
    pid_t pid;
    if((pid=fork())<0){
        oops("fork error",1);
    }
    else if(pid==0){
        if(execle("/home/XXX/programHome/Unix_book_code/chapter08/c8_8_17","c8_8_17","myarg1","MY ARG2",NULL,env_init)<0){
            oops("execle error",2);
        }
    }
    if(waitpid(pid,NULL,0)<0){
        oops("waitpid error",3);
    }
    if((pid=fork())<0){
        oops("fork error",1);
    }
    else if(pid==0){
        //在PATH环境变量下查找
        //所以需要先用export PATH=${PATH}:/home/XXX/programHome/Unix_book_code/chapter08 指令将当前目录添加到PATH环境变量中
        if(execlp("c8_8_17","c8_8_17","only 1 arg",NULL)<0){
            oops("execlp error",4);
        }
    }
    exit(0);
}
//c8_8_17.c
#include<stdio.h>
#include<unistd.h>

int main(int argc,char* argv[])
{
    int i;
    char** ptr;
    extern char** environ;

    for(i=0;i<argc;++i)
        printf("argv[%d]:%s\n",i,argv[i]);

    for(ptr=environ;*ptr!=0;++ptr)
        printf("%s\n",*ptr);

    exit(0);
}

UNIX组关于用户ID:

  1. 实际用户ID(RUID):用于标识一个系统用户是谁,一般在登录之后,就被唯一确定
  2. 有效用户ID(EUID):用于系统决定用户对系统资源的权限,用户做任何操作时,都是在判断有效用户ID是否有权限
  3. 保存设置用户ID(SUID):是有效用户ID的副本,它的作用是为了以后恢复有效用户ID用的

在UNIX中可以通过setuid来设置用户ID或有效用户ID

#include<unistd.h>

int setuid(uid_t uid);

(***)使用setuid更改用户ID的规则如下:

  1. 若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID以及保存的设置用户ID(saved set-user-ID)设置为uid
  2. 若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid。不更改实际用户ID和保存的设置用户ID

这里将更改用户ID的不同方法总结如下图:
在这里插入图片描述

UNIX中还包含两个函数seteuidsetegid用于更改有效用户ID和有效组ID

#include<unistd.h>

int seteuid(uid_t uid);
int setegid(git_t gid);

一个非特权用户可将其有效用户ID设置为其实际用户ID或其保存的设置用户ID。对于一个特权用户则可将有效用户ID设置为uid(这区别于setuid函数,它更改所有3个用户ID)。

所有现今的UNIX系统都支持解释器文件。这种文件是文本文件,其起始行的形式是:

#! pathname[optional-argument]

比如常见的解释器以下列行开始:

#! /bin/sh
执行一个解释器文件的程序
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

#define oops(m,x) { perror(m); exit(x); }

/*
部分输出:
argv[0]:/home/wushukun/programHome/Unix_book_code/chapter08/c8_8_17
argv[1]:foo
argv[2]:/home/wushukun/programHome/Unix_book_code/chapter08/testingterp
argv[3]:myarg1
argv[4]:MY ARG2

可以看出当运行解释器文件时,execl传入的参数后移了
*/

int main(void)
{
    pid_t pid;

    if((pid=fork())<0){
        oops("fork error",1);
    }
    else if(pid==0){
        if(execl("/home/XXX/programHome/Unix_book_code/chapter08/testingterp","testringterp","myarg1","MY ARG2",NULL)<0){
            oops("execl error",2);
        }
    }
    if(waitpid(pid,NULL,0)<0){
        oops("waitpid error",3);
    }
    exit(0);
}

testingterp解释器文件如下:

#! /home/wushukun/programHome/Unix_book_code/chapter08/c8_8_17 foo

UNIX系统定义了system函数使得用户可以直接使用系统指令:

#include<stdlib.h>

int system(const char* cmdstring);  //运行一条指令

因为system在其实现中调用了forkexecwaitpid,因此有3种返回值

system函数的简单实现(没有对信号进行处理)
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<errno.h>

#define oops(m,x) { perror(m); exit(x); }

/*
输出
2021年 03月 20日 星期六 12:34:58 CST
exit status:0
sh: 1: nosuchcommand: not found
exit status:127
maokaige pts/2        2021-03-09 21:57 (tmux(9073).%0)
exit status:44
*/

int system(const char* cmdstring)
{
    pid_t pid;
    int status;

    if(cmdstring==NULL)
        return 1;

    if((pid=fork())<0){
        oops("fork error",1);
    }
    else if(pid==0){
        execl("/bin/sh","sh","-c",cmdstring,NULL);      //调用sh解释器执行指令
        _exit(127);
    }
    else{
        while(waitpid(pid,&status,0)<0){
            if(errno!=EINTR){   //子进程遇到信号会将errno置于EINTR
                status=-1;
                break;
            }
        }
    }
    return status;
}

int main(void)
{
    int status;

    if((status= system("date"))<0){
        oops("system() error",1);
    }

    printf("exit status:%d\n",status>>=8);

    if((status=system("nosuchcommand"))<0){
        oops("system() error",1);
    }

    printf("exit status:%d\n",status>>=8);

    if((status=system("who; exit 44"))<0){
        oops("system() error",1);
    }

    printf("exit status:%d\n",status>>=8);

    exit(0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值