IPC--无名管道

1 管道

以下参考: https://www.cnblogs.com/CheeseZH/p/5264465.html

1.1 特点

1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
2) 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中

1.2 原型
1.2.1 pipe()
#include <unistd.h>
int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1
  1. 当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图
  2. 要关闭管道只需将这两个文件描述符关闭即可
    在这里插入图片描述
1.2.2 popen()

用于得到shell命令的执行结果

#include <stdio.h>
FILE * popen(const char *command , const char *type );
int pclose(FILE *stream);

command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

#include <stdio.h>
#include <string.h>
int main ()
{
    FILE *fp;
    char buff[128] = {0};

    fp = popen("ls -l", "r");  /* 执行ls -l命令, 把管道接到标准输出上, 返回值是标准I/O流 */
    fread(buff, 1, 127, fp);   /* 通过fread读取标准输出流 */
    printf("%s\r\n", buff);
    pclose(fp);
    return 0;
}

执行结果:
在这里插入图片描述

1.3 例子

单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
在这里插入图片描述

2 fork() 函数

以下参考: https://www.cnblogs.com/dongguolei/p/8098181.html

2.1 功能

fork 这个英文单词在英文里是"分叉"意思, fork() 这个函数作用也很符合这个意思. 它的作用是复制当前进程(包括进程在内存里的堆栈数据)为1个新的镜像. 然后这个新的镜像和旧的进程同时执行下去. 相当于本来1个进程, 遇到fork() 函数后就分叉成两个进程同时执行了. 而且这两个进程是互不影响

2.2 区分主进程和子进程

实际应用中, 单纯让程序分叉意义不大, 我们新增一个子程序, 很可能是为了让子进程单独执行一段代码. 实现与主进程不同的功能
实际上, fork() 有返回值, 而且在两条进程中的返回值是不同的, 在主进程里 fork()函数会返回子进程的pid, 而在子进程里会返回0!

2.3 使用exit() 函数令子进程在if 判断内结束

子进程和主进程在if判断的范围内执行了不同的代码, if 执行完成后, 他们还是会各自执行后面的代码。通常这不是我们期望的, 我们更多时会希望子进程执行一段特别的代码后就让他结束, 后面的代码让主程序执行就行了。
这个实现起来很简单, 在子程序的if 条件内最后加上exit() 函数就ok了。

2.4 使用wait() 函数主程序等子程序执行完成(退出)后再执行
2.5 exec 函数组
#include <unistd.h>  
  
int execl(const char *path, const char *arg, ...);  
  
int execlp(const char *file, const char *arg, ...);  
  
int execle(const char *path, const char *arg, ..., char *const envp[]);  
  
int execv(const char *path, char *const argv[]);  
  
int execvp(const char *file, char *const argv[]);  
  
int execve(const char *path, char *const argv[], char *const envp[]); 

可以见到这6个函数名字不同, 而且他们用于接受的参数也不同. 实际上他们的功能都是差不多的, 因为要用于接受不同的参数所以要用不同的名字区分它们, 毕竟c语言没有函数重载的功能嘛…

但是实际上它们的命名是有规律的:
exec[l or v][p][e]
exec函数里的参数可以分成3个部分, 执行文件部分, 命令参数部分, 环境变量部分.
例如我要执行1个命令 ls -l /home/gateman
执行文件部分就是 “/usr/bin/ls”
命令参赛部分就是 “ls”,"-l","/home/gateman",NULL 见到是以ls开头 每1个空格都必须分开成2个部分, 而且以NULL结尾的啊.
环境变量部分, 这是1个数组,最后的元素必须是NULL 例如 char * env[] = {“PATH=/home/gateman”, “USER=lei”, “STATUS=testing”, NULL};

好了说下命名规则:
e后续, 参数必须带环境变量部分, 环境变零部分参数会成为执行exec函数期间的环境变量, 比较少用
l 后续, 命令参数部分必须以"," 相隔, 最后1个命令参数必须是NULL
v 后续, 命令参数部分必须是1个以NULL结尾的字符串指针数组的头部指针. 例如char * pstr就是1个字符串的指针, char * pstr[] 就是数组了, 分别指向各个字符串.
p后续, 执行文件部分可以不带路径, exec函数会在$PATH中找

注意, exec函数会取代执行它的进程, 也就是说, 一旦exec函数执行成功, 它就不会返回了, 进程结束. 但是如果exec函数执行失败, 它会返回失败的信息, 而且进程继续执行后面的代码!

通常exec会放在fork() 函数的子进程部分, 来替代子进程执行啦, 执行成功后子程序就会消失, 但是执行失败的话, 必须用exit()函数来让子进程退出!

3 代码示例

#include <stdio.h>
#include <unistd.h>

void parent_execute()
{   
    int i = 0;
    wait(); /* wait until child process finished */
    while(i < 5)
    {   
        printf("[parent]execute %d time, ppid is %d\r\n", i++, getppid());
        sleep(1);
    }
}

void child_execute_shell_script()
{   
    char *execv_str[] = {"bash", "test.sh", NULL};
    char *execv_str1[] = {"echo", "executed by execv", NULL};
    
    // if (execv("/bin/echo", execv_str1) < 0)
    if (execv("/bin/bash", execv_str) < 0)
    {   
        printf("error on exec\r\n");
        exit(0);
    }
}

void child_execute()
{   
    int i = 0;
    
    while(i < 3)
    {
        printf("[child]execute %d time, ppid is %d\r\n", i++, getppid());
        sleep(1);
    }
    exit(0);
}

/* fd[0]: read, fd[1]: write */
void child_pipe_recv(int fd[2])
{
    char buff[20] = {0};
    
    printf("[child_recv]start\r\n");
    close(fd[1]);  /* child recv only*/
    
    read(fd[0], buff, 20);
    printf("[child_recv]%s\r\n", buff);
    printf("[child_recv]finish\r\n");
}

void parent_pipe_send(int fd[2])
{
    printf("[parent_send]start\r\n");
    close(fd[0]); /* parent send only */
    
    // sleep(3); /* test recv wait until send data to fifo */
    write(fd[1], "hello", 5);
    printf("[parent_send]finish\r\n");
}

int main ()
{
    pid_t fpid;
    int count = 0;
    int pipe_fd[2] = {0};
    
    printf("I am main, id is %d\r\n", getpid());
    
    if (pipe(pipe_fd) < 0)
    {
        printf("Create Pipe Error!\n");
    }

    fpid = fork();
    if (fpid < 0)
    {
        printf("error in fork\r\n");
    }
    else if (fpid == 0)
    {
        /* child process */
        printf("I am the child process, pid is %d, ppid is %d, fpid is %d\r\n", getpid(), getppid(), fpid);
        count++;

        // child_execute();
        // child_execute_shell_script();
        child_pipe_recv(pipe_fd);
    }
    else
    {
        /* parent process */
        printf("I am the parent process, pid is %d, ppid is %d, fpid is %d\r\n", getpid(), getppid(), fpid);
        count++;
        // parent_execute();
        parent_pipe_send(pipe_fd);
    }

    printf("result is %d\r\n", count);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值