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

补充
以bash为例:
bash中有内部命令和外部命令。指令 type commond 能知道一个指令是外部命令还是内部命令。
内部命令是和bash存在于同一个进程,而外部命令是通过fork创建的子进程。
一、替换进程映像和环境变量的联合使用
在前一篇的笔记中,已经讲明怎样创建一个子进程,环境变量的概念。
如果父进程没有给子进程显式的传递环境变量的参数,子进程默认的继承父进程的环境变量。
如果父进程指定了传给子进程的环境变量的参数,子进程会用传递来的环境变量,替换原先从父进程那里继承来的环境变量。
这里通过一个例子来演示,在创建子进程之后,父进程通过参数 envp 将环境变量传递给子进程。

#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(int argc,char *argv,char *envp[]){
    int s;
    char *ps_argv[] = {NULL};
    char *ps_envp[] = {"caption=beijing",NULL};
    on_exit(addd,"resean is:");
    setenv("myname","linxin",0);
    //创建一个子进程
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }
    if(pid == 0){//子进程执行的代码
        //使用新的映像替换旧的映像
        //execve("tenv",ps_argv,ps_envp);
        //从父进程那里继承环境变量
        execl("./tenv","tenv",NULL);
        perror("execvpe");//失败的时候执行
        exit(-1);
    }
    else{//父进程的代码
        //wait(NULL);//阻塞等待子进程的中止
        wait(&s);//s用于接受退出状态码 
        printf("parent pid:%d\n",getpid());
        printf("envp's addr:%p\n",envp);
        getchar();
        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 1;
}

上述程序中 tenv 是一个可执行程序,它用于打印当前进程的环境变量。

二、进程间的通讯(管道)
介绍进程间通讯的方式—–管道
在32位机上,可以看作每个进程都拥有自己独立的4G虚拟地址空间(具体是什么样的,不清楚,但是这样的想法并没有错),其中3G是分配给进程的(用户态),剩下的1G分配给内核(内核态)。两个进程在用户态是平行的,没有任何的交集。在如果通过用户态映射的物理内存来通讯是不行的。而内核部分的映射是不变的,所以通过内核部分的内存地址在两个进程间通讯时可行的。
管道分为两种:无名管道和有名管道
无名管道,如图:
无名管道
系统提供了系统调用,可以允许进程通过系统调用在内核的物理内存上申请空间,用于进程间的通讯。
函数介绍如下:

#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建管道  单向的
参数:
pipefd:数组的名字  pipefd[0],指向了管道的读端  pipefd[1],指向了管道的写端
返回值:
success:0
error-1,errno被设置

使用无名管道实现的进程间通讯,两个进程必须具有亲缘关系。
有亲缘关系的进程,才可能拥有同样的文件描述符。文件描述符是管道的入口。
使用无名管道实现进程间的通讯:
1、创建一个管道,父进程有两个文件描述符,分别指向了管道的两端
2、创建一个子进程,子进程继承了父进程的文件描述符
3、 父进程的任务:
从管道里读取数据,将读取到的数据输出到显示器
close(ptpefd[1]);关闭写端
read(ptpef[0],buf,128);//如果管道里没有数据,阻塞
write(1,buf,r);
wait(NULL);
子进程的任务:
向管道写入数据。
close(pipefd[0]);
write(pipefd[1],buf,128);
exit(0);
注意!当管道中没有数据存在的时候,read会进入阻塞状态,直到另一个进程向管道写入数据之后,才会退出阻塞状态。
用以下两个例子来演示,父进程,从管道中读取数据;子进程,从管道中写入数据。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void){
    //创建一个管道
    int pipefd[2] = {0};
    int flag_pipe = pipe(pipefd);
    if(flag_pipe == -1){
        perror("pipe is fail");
        return -1;
    }
    printf("pipe is success\n");
    //创建一个子进程
    char *msg = "hello beijing";
    char *buf = (char *)malloc(128);
    pid_t pid = fork();
    if(pid == -1){
        perror("fork is fail");
        return -1;
    }
    printf("fork is success\n");
    if(pid == 0){//子进程要执行的内容
        //关闭读端
        close(pipefd[0]);
        //写入
        write(pipefd[1],msg,strlen(msg));
        free(buf);
        buf = NULL;
        exit(0);
    }
    else{//父进程要执行的内容
        //关闭写端
        close(pipefd[1]);
        //从管道中读取
        int num = read(pipefd[0],buf,128);
        write(1,buf,num);
        wait(NULL);
        free(buf);
        buf = NULL;
    }
    return 0;
}

有名管道:
是一种文件,这个文件只是起到通讯的桥梁,没有任何其他意义,这个文件的大小总是为0;
p类型的文件。
系统提供了系统调用,用于创建管道文件。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件
参数:
pathname:指定了文件的路径,要创建的管道文件
mode:指定了管道文件的权限  mode & ~umask
返回值:
success:0
error:-1 errno被设置

使用有名管道实现进程间的通讯,并不要求进程具有亲缘关系。有名管道的管道文件就像是一个连接器将两个进程连接。
有名管道一端写,一端读,任意一端没有连接上进程,另一端就会被阻塞。
例如,进程A打开了管道文件,开始写入,但是由于管道文件的另一端没有进程连接上来读取文件,这个时候进程A就被阻塞。
注意!如果管道两边分别接了读/写进程,但是写进程并没有向管道写入,这个时候读进程就会被阻塞。
以下用一个例子来演示上述内容,pa进程向管道写入数据,pb进程从管道读取数据

/*pa.c*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char *argv[]){
    char *msg = "hello kitty!\n";
    int fd = open(argv[1],O_WRONLY);
    if(fd == -1){
        perror("open is fail");
        return -1;
    }
    write(fd,msg,strlen(msg));
    close(fd);
}
-----------------------------------------------------
/*pb.c*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc,char *argv[]){
    char buf[128] = {0};
    int fd = open(argv[1],O_RDONLY);
    if(fd == -1){
        perror("open is fail");
        return -1;
    }
    int num = read(fd,buf,128);
    write(1,buf,num);
    close(fd);
}

三、信号
信号是由程序实现的中断。
当CPU在执行一个进程的时候,CPU收到一条信号,CPU就要对该信号做出相应的动作。信号无法打断CPU的原子操作。必须等待CPU处理好当前的原子操作,然后再处理信号。
操作系统提供了62种信号,可以通过命令 kill -l ,看到所有信号编号和信号名称。
未决信号:指一个信号已经产生,但是信号还没有到达,这个状态称为未决状态。处于这种状态的信号称为未决信号。

处理信号:
系统为每个进程都提供了默认的处理信号的函数 SIG_DFL,当进程收到信号之后,就会调用这个函数去处理信号。SIG_DFL的处理方法就是退出进程。除此之外,系统还提供了一个系统调用,允许用户向进程注册自定义的信号处理函数。该系统调用规定了信号处理函数的返回值和参数列表。如下:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
参数:
signum:信号的编号
handler:信号的处理函数
返回值:
error:SIG_ERR  errno被设置
success:返回原来的信号处理程序的入口地址

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

#include <stdio.h>
#include <signal.h>
void sig(int num){
    printf("\n num of %d signal,atemp to kill me!\n",num);
    return;
}
int main(void){
    signal(2,sig);//注册用户自定义的信号处理函数,用于处理2号消息
    while(1);
    return 0;        
}

信号处理函数的形式参数,用于接受信号编号。

信号发生:
信号的产生:
1、通过硬件 Ctrl+c Ctrl+\
2、使用命令发送的信号 kill -信号编号 进程的pid
3、可以使用系统调用或库函数给进程发送信号
系统提供了以下函数用于发生信号:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:给一个进程发送信号
参数:
pid_t:接受信号的进程的pid   只讨论 pid>0的情况
sig:要发送的信号编号
返回值:
success:0
error:-1 errno被设置

#include <signal.h>
int raise(int sig);

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:设置一个闹钟,到时发送一个SIGALRM信号给当前进程
参数:
secondes:0取消以前设定的,还没执行的信号
返回值:
返回以前设定的闹钟还没有执行的剩余时间。

对于raise函数不做过多的介绍,它是给当前进程发送信号,可以等价于 kill(getpid(),sig_num);
下面通过两个例子分别演示,alarm和kill函数:

/*alarm*/
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void shijian(int num){
    printf("who is you dad!\n");
    alarm(5);
}
int main(void){
    signal(14,shijian);
    int before = alarm(5);
    while(1);

    return 0;
}
--------------------------------------------------
/*kill*/
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc,char *argv[]){
    int sig = atoi(argv[1]);
    int pid = atoi(argv[2]);
    int flag_kill = kill(pid,sig);
    if(flag_kill == -1){
        perror("kill is fail");
        return -1;
    }
    return 0;
}

猜你喜欢

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