[C++系列] UNIX高级编程 day10,管道,文件输入重定向,信号管理
in C/C++ with 0 comment

[C++系列] UNIX高级编程 day10,管道,文件输入重定向,信号管理

in C/C++ with 0 comment

复习

一,回收子进程的资源

* wait(2)    waited(2)
* 还可以检查子进程的退出状态。
* 僵尸进程

二,环境变量

* Char **environ        getenv(3)        putenv(3)        setenv(3)

三,加载新的映像

* execve(2)        execl(3)

四,system(3)的使用

* 调用/bin/sh -c command

一,管道

管道分为两种

* 有名管道
* 无名管道

无名管道

系统提供了pipe(2)用于创建无名管道
#include <unistd.h>
int     pipe(int fildes[2]);

功能:创建一个管道
参数:
Filed[2]:指定了两个文件描述符。这两个文件描述符指向了管道的两端,pipefd[0]指向了管道的读端。pipefd[1]指向了管道的写端。
返回值:
成功 0
错误 -1 errno被设置

使用管道进行进程间的通信步骤

(1)父进程负责的任务

关闭读端,写消息到管道,等待子进程的结束。

(2)子进程负责的任务

关闭写端,从管道读取数据到指定的buf中
将读取到的内容读取到显示器。

readwrite都是阻塞的。
如果管道中没有数据可读,从管道读取数据就被阻塞。一直阻塞到管道中有数据为止。

如果管道中已满,这时候再向管道中写数据,写操作会被阻塞,一直等到管道的数据被读取走,有空间的时候,才将数据写入到管道。

使用无名管道进行进程间的通信,只能在有亲源关系的进程间实现通讯。父子关系 兄弟 否则无法通信

有名管道

其实就是一个文件,这个文件用于两个进程间的通讯。这个文件只是桥梁的功能。
#include <sys/types.h>
     #include <sys/stat.h>
int     mkfifo(const char *path, mode_t mode);

功能:创建一个有名管道。
参数
Path:要创建的管道文件的名字
Mode:指定了管道文件的权限,同open(2)一样
返回值:
成功 0
错误 -1 errno被设置

使用有名管道可以实现没有任何关系的进程通信

举例

1,创建一个有名管道的文件
2, 使用这个管道名实现进程间通信 mkfifo.c
3,改程序使用有名管道文件,向管道写数据。write_p.c
4, 改程序从管道里读取数据,存放到buf中,最终输出到显示器。read_p.c

Mkfifo.c
 1 #include<stdio.h>
 2 #include <sys/types.h>
 3 #include <sys/stat.h>
 4 int main(int argc,char* argv[]){
 5         //创建一个有名管道
 6         int m = mkfifo(argv[1],0664);
 7         if(m == -1){
 8                 perror("mkfifo");
 9                 return -1;
10         }
11         printf(" fipo file success!\n");
12         return 0;
13 }

Write_p.c

1 #include<stdio.h>
  2 #include<string.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include<sys/types.h>
  6 int main(int argc,char* argv[]){
  7         int fd;
  8         char* msg = "this is a msg!\n";
  9         //打开管道文件
10         fd=open(argv[1],O_WRONLY);
11         if(fd == -1){
12                 perror("open");
13                 return -1;
14         }
15         //向管道写数据
16         write(fd,msg,strlen(msg));
17         return 0;
18 }

Read_c.c

1 #include<stdio.h>
  2 #include<string.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include<sys/types.h>
  6 int main(int argc,char* argv[]){
  7         int fd;
  8         char buf[128];
  9         //打开管道文件
10         fd=open(argv[1],O_RDONLY);
11         if(fd == -1){
12                 perror("open");
13                 return -1;
14         }
15         //从管道读数据到buf中
16         int r = read(fd,buf,128);
17         write(1,buf,r);
18         close(fd);
19         return 0;
20 }
~

举例

使用管道实现进程间的通信。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void){
    pid_t pid;
    int fd[2];
    char buf[128];
    char *msg="this is a test\n";
    //创建管道
    int p=pipe(fd);
    if(p==-1){
        perror("pipe");
        return -1;
    }
    //创建子进程
    pid=fork();
    if(pid==-1){
        perror("fork");
        return -1;
    }
    if(pid==0){//子进程执行的代码
        //关闭写端
        close(fd[1]);
        //从管道的读端读取数据
        int r=read(fd[0],buf,128);
        //将读取到的数据输出到显示器
        write(1,buf,r);
        //子进程退出
        exit(0);
    }else{//父进程的执行代码
        //关闭读端
        close(fd[0]);
        //向管道写数据
        write(fd[1],msg,strlen(msg));
        //等待子进程的结束
        wait(NULL);
    }
    return 0;
}

注意:这个数据对象是单向的。单工

二,文件输入重定向的实现

举例

编写代码实现从标准输入文件读取数据,并将读取到的数据转换为大写。代码参见 upper.c

Toupper()
#include <ctype.h>
Int toupper(int c);
Int to lower(int c);

功能:将字符转换为大写格式
C:指定要转换为大写格式的字符的ASCII值
返回值:

  1 #include<stdio.h>
  2 #include<ctype.h>
  3 int main(){
  4         int ch;
  5         while((ch = getchar())!=EOF){
  6                 putchar(toupper(ch));
  7         }
  8         return 0;
  9 }
~```

## 例子

编写代码实现,将标准输入重定向到一个置顶文件,然后调用upper可执行文件,从文件里读取数据,将文件显示到显示器。wrap.c

1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<fcntl.h>
5 int main(int argc,char* argv[]){
6 int fd;
7 //先关闭标准输入文件描述符
8 close(0);
9 fd = open(argv[1],O_RDONLY);
10 if(fd == -1){
11 perror("open");
12 return -1;
13 }
14 //从fd指定的文件描述符中读取数据,目前是标准输入。标准输入已经定位到指定的文件了。
15 execl("./upper","upper",NULL);
16
17 return 0;
18 }


## 三,信号管理

什么是信号:

    * 信号就是软中断。

什么是软中断:

    * 软件实现的中断机制。

什么是**中断**:
中断产生后需要有一个中断的处理程序。

中断处理程序和进程是异步的。

中断什么时候到达不确定,所以中断处理程序和进程是异步的。

信号的处理程序和进程是异步的。
进程接受到信号以后调用信号处理程序。

系统为我们提供了哪些信号:

`Ctl +c` 发送` 2 `信号

信号的编号        信号的名字

在程序中信号的编号和名字的效果是一样的。

信号的生命周期:

信号的产生,未决信号,阻塞,信号的抵达,信号的捕获(信号的处理)

信号未决就是信号的一种状态。信号产生了,但是信号还没有递达给目标进程,这时候的信号,处于未决状态。

## 信号的捕获(信号处理)
当信号到达进程的时候,进程会对信号进行处理。
对信号的处理分为三种:
* 1,默认处理:    进程对大部分信号的默认处理动作是中止进程。
* 2,忽略 : 进程对信号视而不见。不做任何响应处理。
* 3,用户自定义处理 : 用户自己定义对信号的处理。

信号的处理程序可以被子进程继承。

一般情况下,在`bash`下启动的进程都默认从`bash`继承信号的处理。
进程对所有的信号采用的是默认处理动作。中止当前进程。

如何去改变进程对信号的处理:

系统提供了`signal(2)`函数,用于向进程注册信号的处理程序。

include <signal.h>

void (signal(int sig, void (func)(int)))(int);
typedef void (*sig_t) (int);
sig_t signal(int sig, sig_t func,);


**功能**:为信号设置相应的处理程序。
**参数**:
`Signum`:指定信号的编号
`Func`:指定了信号的处理程序的入口地址
`SIG_IGN`
`SIG_DFL`
用户自定义的处理函数
**返回值**:

**成功**: 返回原来的信号处理函数的首地址
**失败**:`SIG_ERR`


## 举例说明
使用`signal`给进程注册信号处理函数
对2号信号注册忽略信号。

1 #include<stdio.h>
2 #include<signal.h>
3 int main(){
4 signal(2,SIG_IGN);
5 while(1);
6 return 0;
7 }

忽略2号信号,注册3号信号

1 #include<stdio.h>
2 #include<signal.h>
3 #include<stdlib.h>
4 //信号的处理程序,参数n传递的是参数的编号
5 void doit(int n){
6 printf("recive signal..%dn",n);
7 exit(0);
8 return;
9 }
10 int main(){
11 //该进程对二号信号产生忽略
12 signal(2,SIG_IGN);
13 //该进程对3号信号采用自定义处理函数
14 signal(SIGQUIT,doit);
15 while(1);
16 return 0;
17 }

严重信号的处理可以被子进程继承

1 #include<stdio.h>
2 #include<signal.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5 #include<sys/wait.h>
6 //信号的处理程序,参数n传递的是参数的编号
7 void doit(int n){
8 printf("recive signal..%dn",n);
9 //exit(0);
10 return;
11 }
12 int main(){
13 //该进程对二号信号产生忽略
14 signal(2,SIG_IGN);
15 //该进程对3号信号采用自定义处理函数
16 signal(SIGQUIT,doit);
17 pid_t pid;
18 pid = fork();
19 if(pid == 0){
20 printf("child pid %dn",getpid());
21 while(1);
22 }else{
23 printf("parent pid %dn",getpid());
24 wait(NULL);
25 }
26
27 return 0;
28 }



### 信号的产生
1,硬件产生       ` ctl+c  `      `ctl+\`
2,    使用命令给进程发送信号
`Kill     -信号编号    pid`
3,使用系统调用活着库函数产生信号。

include <signal.h>

include<sys/types.h>

int kill(pid_t pid, int sig);

**功能**:给一个进程发送信号
**参数**:
pid   :指定了目标进程,将信号发送给这个进程
Sig    :指定了要发送的信号。
**返回值**:
0        成功
-1        失败        errno被设置


## 举例

使用`kill(2) `   完成`kill(1)`的功能。编译生成可执行文件rkill

1 #include<signal.h>
2 #include<sys/types.h>
3 #include<stdlib.h>
4 int main(int argc,char* argv[]){
5 pid_t pid=atoi(argv[2]);
6 int signum = atoi(argv[1]);
7 kill(pid,signum);
8 return 0;
9 }



Raise(2)

include <signal.h>

int raise(int sig);

**功能**:发送信号给当前进程
**参数**:
sig:指定信号的编号
**返回值**:
0        成功
非0        错误

## 举例

`raise`函数的使用    

1 #include<signal.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<stdio.h>
5 void doit(int n){
6 printf("recive signal...%dn",n);
7 return;
8 }
9 int main(){
10 signal(SIGINT,doit);
11 while(1){
12 usleep(500000);
13 raise(2);
14 }
15 return 0;
16 }
~`

Alarm(2)

#include <unistd.h>
unsigned     alarm(unsigned seconds);

功能:在一定的时间内,将SIGALRM信号递送给当前进程。
参数
seconds:指定了时间,在这段时间内,将SIGALRM信号递送给当前进程。
日过seconds指定为0,代表取消闹钟。
返回值
0 前期设置的闹钟执行完毕
前期设置的闹钟,剩余没有执行的时间

举例

使用alarm产生设置闹钟为 1s 在闹钟执行的时候停止程序的运行。

1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main(){
  4         int i;
  5         //设置闹钟为1s
  6         alarm(15);
  7         for(i=1;i<=360000;i++){
  8                 printf("%d\n",i);
  9         }
10         //取消闹钟am保存上次alarm剩余时间
11         int am=alarm(0);
12         printf("am=%d\n",am);
13         //for(i=1;i<100000;i++){
14         //      printf("%d\n",i);
15 //      }
16
17         return 0;
18 }

总结

一,管道
有名管道 无名管道
二,文件输入重定向的实现
三,信号的基础
1,给进程注册信号处理函数
2,信号的产生
kill raise
alarm

Responses