C++学习笔记day28-----UC

进程管理
1、创建一个子进程
pstree
该指令用于查看当前的进程树。
在运行的进程中,创建一个新的进程。
这个原本正在运行的进程被称为,父进程。
而这个新的进程被称为,子进程。
系统提供了以下函数,用于创建新的进程:

#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程
参数:
返回值:
-1  errno被设置
0   父进程中返回子进程的pid,在子进程中返回0

在子进程的创建过程中发生如下:
子进程
当父进程(A)运行到fork函数的时候,系统开始创建一个新的进程(子进程B)。并将父进程的PCB也作为子进程的PCB。所以,当fork函数还没有结束的时候,就已经存在两个进程了。那么fork结束接收后,会分别在两个进程里返回。在父进程中,返回子进程的pid;在子进程中返回0;所以在编写程序的时候,需要在创建完子进程之后,要区分父、子进程所执行的代码,通过fork的返回值来指定两个进程各自需要执行的内容。
进程的结束
一个进程正常结束有以下两种:
1、mian函数中执行到return语句,那么整个进程就结束了。
2、程序执行期间,执行到exit函数,那么整个进程就结束了。
进程结束的时候,会把进程的状态码(例如,return后面跟着的数字,或者exit的实参)传递给父进程。
exit会把实参和0377进行按位与操作,然后再将状态码返回。例如,exit(-1)返回给父进程的码就是255
echo $?
echo ” ” 将字符串输出到显示器。
$? 是最近执行的指令的返回值
遗言函数
进程在结束的过程中,可以调用遗言函数。遗言函数的执行时间,是在进程结束但还没有结束的时候。
遗言函数需要在进程结束前注册到进程中,下面提供两个注册遗言函数的函数:

#include <stdlib.h>
int atexit(void (*function)(void));
功能:注册一个函数,在进程中止的时候被调用
参数:
function:指针类型的变量,用来存储遗言函数的入口地址。
返回值:
成功:0
错误:非0

注册遗言函数也可以通过on_exit(3)
#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);
功能:
参数:
function:指向遗言函数的入口地址
arg:传递给遗憾函数的第二个参数
返回值:
success:0
error:not 0

1、遗言函数注册的顺序和调用的顺序相反
2、一个遗言函数,注册多少次,就会被调用多少次
3、子进程继承父进程的所有遗言函数
在注册遗言函数的函数中,规定了遗言函数的参数列表和返回值。
on_exit要求的遗言函数的参数列表的第一个值,是exit/return的参数。
遗言注册函数会将子进程exit/return的参数传入到遗言函数中。
下面通过一个例子来演示上述函数:

#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void doit(int num,void *arg){
        printf("the exit num is:%d\nresean is:%s\n",num,(char *)arg);
}
void doit1(int num,void *arg){
        printf("the exit num is:%d\nresean is:%s\n",num,(char *)arg);
}
void addd(void){
        printf("exit\n");
}
int main(void){
        atexit(addd);
        on_exit(doit,"first regist");
        on_exit(doit1,"secend regist");
        getchar();
        pid_t pid = fork();
        if(pid == -1){
                perror("fork");
                return -1;
        }
        if(pid == 0){
                printf("son's pid:%d\n",getpid());
                exit(2);
        }
        else{
                getchar();
                printf("father's pid:%d\n",getpid());
        }
        return 3;
}
---------------------------------------------------------------
linxin@ubuntu:~/UC/day08$ on_exit

son's pid:7535
the exit num is:2
resean is:secend regist
the exit num is:2
resean is:first regist
exit

father's pid:7534
the exit num is:3
resean is:secend regist
the exit num is:3
resean is:first regist
exit

2、回收一个子进程使用过的资源
当进程结束的时候,如果不对进程的资源进行回收,那么这部分资源会被一直占用,不能再被使用。
下面介绍两个回收进程资源函数的函数:

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:等待子进程的中止,并回收子进程的资源
参数:
status:存储子进程退出状态码
返回值:
error:-1
success:返回中止的子进程的PID

WIFEXITED(status)   如果子进程正常中止,返回真
WEXITSTATUS(status) 返回子进程的推出状态码,只有上个宏为真的时候,才使用这个宏。
WIFSIGNALED(status) 如果子进程被一个信号中止,返回真
WSTOPSIG(status)    获取中止子进程的信号的编号,只有上个宏为真的时候,才被使用。


pid_t waitpid(pid_t pid, int *status, int options);
功能:
参数:
pid:
status:存储子进程退出的状态码
options:
<-1:等待任意子进程,子进程的进程组ID必须等于pid参数的绝对值
-1:等待任意的子进程中止
0:等待和当前进程同组的子进程中止
>0:等待pid参数指定的子进程中止
WNOHANG:如果没有子进程中止,立即返回。非阻塞

返回值:
success:中止的子进程的pid,如果指定为非阻塞,没有子进程退出的时候,返回0
error:-1

group ID
进程组:

wait(&s);和waitpid(-1,&s,0);这两种调用是一样的。
下面通过两端代码来演示上述函数:

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
void addd(int num,void *arg){
    printf("%s%d\n",(char *)arg,num);
}
int main(void){
    int s;
    on_exit(addd,"resean is:");
    //创建一个子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    if(pid == 0){//子进程执行的代码
        printf("child's pid:%d\n",getpid());
        getchar();
        exit(-1);
    }
    else{//父进程的代码
        //wait(NULL);//阻塞等待子进程的中止
        wait(&s);//s用于接受退出状态码
        printf("parent pid:%d\n",getpid());
        if(WIFEXITED(s))//子进程的正常结束
            printf("child kill by nomu:%d\n",WEXITSTATUS(s));//打印退出状态码
        if(WIFSIGNALED(s))
            printf("child kill by daduan :%d\n",WTERMSIG(s));//打印子进程被打断时推出
    }
    return 0;
}
--------------------------------------------------------------------------------
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
void addd(int num,void *arg){
    printf("%s%d\n",(char *)arg,num);
}
int main(void){
    int s;
    on_exit(addd,"resean is:");
    //创建一个子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    if(pid == 0){//子进程执行的代码
        printf("child's pid:%d\n",getpid());
        getchar();
        exit(-1);
    }
    else{//父进程的代码
        //waitpid(-1,NULL,WNOHANG);//阻塞等待子进程的中止wait(NULL);
        waitpid(-1,&s,0);//s用于接受退出状态码
        printf("parent pid:%d\n",getpid());

        if(WIFEXITED(s))//子进程的正常结束
            printf("child kill by nomu:%d\n",WEXITSTATUS(s));//打印退出状态码
        if(WIFSIGNALED(s))
            printf("child kill by daduan :%d\n",WTERMSIG(s));//打印子进程被打断时推出

    }
    return 0;
}

如何使用信号打断一个进程
信号编号:2 3 9

子进程中止,父进程没有回收子进程的资源,这时候,子进程处于僵尸状态,处于僵尸状态的进程,称为僵尸进程。
每个子进程都有自己的父进程,子进程中止,父进程收尸。
如果父进程先死,这个时候咋办?
如果一个子进程的父进程先于子进程中止,子进程过继给init(进程树根)。这个子进程就是孤儿进程。在一些套件中,对进程树根进行了封装,所以很有可能看到的不是init进程。

3、替换进程的映像
上述的子进程演示中,子进程拥有和父进程一致的代码。在这里,着重介绍如何替换子进程执行的内容。替换进程的映像。
映像替换
当子进程刚创建的时候,和父进程共用同一个PCB。虽然每个进程都拥有独立的虚拟地址空间,但是它们还是指向同一个物理内存。如果对子进程的映像(代码段,栈段,数据段,堆。。。)进行替换,那么操作系统会为子进程重新准备一个PCB用于存放子进程的状态。
系统提供了以下系列函数用于替换映像:

#include <unistd.h>
int execve(const char *filename, char *const argv[],char *const envp[]);
功能:执行程序
参数:
fiename:指定了要执行的文件的名字,二进制的可执行文件
argv[]:传递给可执行文件的参数列表,第一个可执行文件的文件名
envp[]:传递给可执行程序的环境变量列表
注意argv和envp最后一个成员必须是NULL。
返回值:
success:无返回值
error:-1 错误 errno被设置

exec家族函数封装了execve(2).
#include <unistd.h>
extern char **environ;//全局变量    二级指针    字符串列表
int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

共同点:
exec开头,
l:列表
v:向量表
p:如果有这个字符,可执行程序的名字只需要提供可执行文件名,会自动到PATH指定的路径下寻找可执行程序的名字。如果不带这个字符,必须指定可执行文件的路径。
e:如果有这个字符,可以指定环境变量,给可执行程序。如果没有,就继承父进程的环境变量


pid -o pid,ppid,pgrp,session,comm

#!/bin/bash     用bash写脚本
#!/bin/sh       用sh写脚本
#!/usr/bin/python   用python写脚本
lua

下面通过一个例子来演示上述函数:

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
void addd(int num,void *arg){
    printf("%s%d\n",(char *)arg,num);
}
int main(void){
    int s;
    char *ps_argv[] = {"ps","-o","pid,ppid,pgrp,session,comm",NULL};
    on_exit(addd,"resean is:");
    //创建一个子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    if(pid == 0){//子进程执行的代码
        //使用新的映像替换旧的映像
        execvp("ps",ps_argv);
        perror("execvp");//失败的时候执行
        exit(-1);
    }
    else{//父进程的代码
        //wait(NULL);//阻塞等待子进程的中止
        wait(&s);//s用于接受退出状态码
        getchar();
        printf("parent pid:%d\n",getpid());
        if(WIFEXITED(s))//子进程的正常结束
            printf("child kill by nomu:%d\n",WEXITSTATUS(s));//打印退出状态码
        if(WIFSIGNALED(s))
            printf("child kill by daduan :%d\n",WTERMSIG(s));//打印子进程被打断时推出
    }
    return 0;
}

注意,在子进程要执行的代码部分,如果替换映像成功,则替换映像的函数不会有返回值,在替换映像之后的所有代码也不再执行。因为子进程的映像替换成功之后,原先所有的代码块全部没有了。但是,子进程还是父进程的子进程,如果父进程是阻塞等待,则会等待子进程执行完毕之后,回收子进程的资源。这个时候父进程收到的子进程的状态码是子进程新映像返回的。

4、环境变量
每一个进程都有自己的环境变量。
子进程继承父进程的环境变量。
extern char **environ;//全局变量,指向了环境变量列表的首地址
使用 environ 访问进程的环境变量。
系统提供了以下函数用于操作进程的环境变量:

#include <stdlib.h>
char *getenv(const char *name);
功能:在环境变量列表中查找环境变量,并返回该变量的值的首地址
参数:
name:指定要找的环境变量的名字
返回值:
NULL    没有找到
返回。值的首地址

putenv(3)
setenv(3)

#include <stdlib.h>
int setenv(const char *name, const char *value, int overwrite);
功能:改变或增加一个环境变量
参数:
name:指定了环境变量的名字
value:指定了环境变量的值
overwrite:0 如果环境变量已经存在,不改变环境变量的值
        非 0 如果环境变量存在,改变环境变量的值
返回值:
success 0
error -1 errno被设置


int unsetenv(const char *name);
功能:删除环境变量
参数:
name:指定了要删除的环境变量
返回值:
success:
error:

clearenv(3)

在进程中对进程自己的环境变量操作是比较简单的。但是当环境变量涉及到父子进程之间的继承时一定要注意以下几点。
1、子进程虽然继承父进程的环境变量,这种继承,是将父进程的环境变量复制一份给子进程的!而且环境变量是放在进程的栈段!所以进程对自己环境变量的修改是不会影响其他进程的。
2、父进程的私有变量是不会继承给子继承的!什么叫做私有变量,就是没有使用函数创建为环境变量的所有变量都是不可以被继承的。

猜你喜欢

转载自blog.csdn.net/displaymessage/article/details/80271864