进程和线程(2)

1、进程间通信方式

在这里插入图片描述
  进程间通信的本质是读写操作。创建进程后会自动分配4G虚拟内存,其中1G内核空间和3G用户空间。
  每个进程的用户空间时私有的,但是内核空间时当前主机中所有进程公有的,所以如果进程间要共享资源,需要对同一块内核空间进行操作。
  共享内存:是所有进程通信方式中效率最高的,共享内存是直接对物理内存进行操作。
  套接字通信方式可以实现不同主机之间进行通信。

1、管道

1、无名管道

创建无名管道

#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个无名管道
参数:
    pipefd:数组,用户保存对无名管道读写的文件描述符
        pipefd[0] 用于从管道读取数据
        pipefd[1] 用于向管道写入数据
返回值:
    成功:0
    失败:-1

  管道可以看做是一种特殊的文件,对于它的读写是使用的文件IO如read和write。
  无名管道只能用于具有亲缘关系的进程之间的通信,为半双工的通信模式。
  具有固定的读端和写端。当一个管道建立时,他会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。是在进程的内核空间里开辟的区域并得到两个文件描述符。
例如:

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

int main(int argc, char const *argv[])
{
    //使用pipe函数创建无名管道
    //无名管道是在内核空间开辟的区域,通过两个文件描述符对当前区域进行读写操作
    int pipefd[2];
    if(pipe(pipefd) == -1)
    {
        perror("fail to pipe");
        exit(1);
    }

    //printf("pipefd[0] = %d\n", pipefd[0]);
    //printf("pipefd[1] = %d\n", pipefd[1]);

    //对管道的操作使用read和write函数
    //无名管道写入数据时,第二次写入的数据不会覆盖之前的,会在之前的数据后面追加写入
    //无名管道读取数据时,如果管道中有数据,会读取成功,如果管道中没有数据,会阻塞等待
    //从无名管道中读取数据后,读取的数据会从管道中清除

    //向管道写入数据,使用pipefd[1]
    if(write(pipefd[1], "hello world", 11) == -1)
    {
        perror("fail to write");
        exit(1);
    }

    if(write(pipefd[1], "666666", 6) == -1)
    {
        perror("fail to write");
        exit(1);
    }

    //注意:无名管道无法修改偏移量
    // if(lseek(pipefd[0], 8, SEEK_SET) == -1)
    // {
    //     perror("fail to lseek");
    //     exit(1);
    // }

    //从管道中读取数据,使用pipefd[0]
    char buf[128] = "";
    if(read(pipefd[0], buf, sizeof(buf)) == -1)
    {
        perror("fail to read");
        exit(1);
    }

    printf("buf = %s\n", buf);

    if(read(pipefd[0], buf, sizeof(buf)) == -1)
    {
        perror("fail to read");
        exit(1);
    }

    printf("buf = %s\n", buf);

    return 0;
}

如果管道内没有数据,读端read函数会一直阻塞直到读到数据。
无名管道默认为64K空间,如果只写不读,管道写满write函数会阻塞。

使用无名管道进行进程间通信

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

int main(int argc, char const *argv[])
{
    //由于无名管道的标识是两个文件描述符
    //所以只能在同一个程序中的多个进程间通信
    //也就是具有亲缘关系的进程间实现通信
    pid_t pid;
    int pipefd[2];
    if(pipe(pipefd) == -1)
    {
        perror("fail to pipe");
        exit(1);
    }

    if((pid = fork()) < 0)
    {
        perror("fail to fork");
        exit(1);
    }
    else if(pid > 0) // 父进程
    {
        //父进程负责给子进程发送数据 
        char buf[128] = {};
        while(1)
        {
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0';

            if(write(pipefd[1], buf, sizeof(buf)) == -1)
            {
                perror("fail to write");
                exit(1);
            }
        } 
    }
    else //子进程
    {
        //子进程接收父进程的数据
        char buf[128] = "";
        while(1)
        {
            if(read(pipefd[0], buf, sizeof(buf)) == -1)
            {
                perror("fail to read");
                exit(1);
            }
        }
    }

    return 0;
}

2、有名管道

创建有名管道

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道
参数:
    pathname:有名管道的文件名
    mode:有名管道的权限,一般0664
返回值:
    成功:0
    失败:-1

  有名管道可以使互不相关的两个进程通信,可以通过路径表示,并且在文件系统中可见。
  有名管道遵循先进先出规则,类似队列。有名管道也是在进程的内核空间区开辟区域,在本地创建一个文件来标识内核空间的区域。
例如:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //创建一个有名管道,使用mkfifo函数
    if(mkfifo("myfifo", 0664) == -1)
    {
        //printf("errno = %d\n", errno);
        //如果有名管道文件已经存在,没有必要打印错误信息,后期直接用就可以
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    //有名管道的特点
    //有名管道中写入数据,后写入的不会覆盖之前的,会以追加的方式写数据
    //有名管道中读取数据,有数据可以正常读取,如果没有数据,read函数会阻塞
    //读取到的数据会从管道中清除
    //有名管道创建的文件只是起到标识作用,本质还是在内核空间李阿敏开辟区域,
    //不同的进程可以通过这个文件标识找到相同的内核空间,这样就可以实现相互通信,
    //所以有名管道创建的文件永远都是0个字节

    //对有名管道进行操作
    //使用文件io的函数对有名管道进行操作
    int fd;
    if((fd = open("myfifo", O_RDWR)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    //使用write向有名管道写入数据
    write(fd, "hello world", 11);
    write(fd, "666666", 6);

    //使用read从有名管道中读取数据
    char buf[128] = "";
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);

    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);

    close(fd);
    return 0;
}

  有名管道实际操作类似文件的读写,多进程操作是类似多个进程对同一个文件进行读写,只不过需要加锁。
有名管道进程通信:
send.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    if(mkfifo("myfifo1", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    if(mkfifo("myfifo2", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    int fd_w, fd_r;
    if((fd_w = open("myfifo1", O_WRONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    if((fd_r = open("myfifo2", O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    char buf[128] = "";
    ssize_t bytes;
    while(1)
    {
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';

        if((bytes = write(fd_w, buf, sizeof(buf))) == -1)
        {
            perror("fail to write");
            exit(1);
        }

        if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
        {
            perror("fail to read");
            exit(1);
        }

        printf("from recv: %s\n", buf);
        
    }


    return 0;
}

recv.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    if(mkfifo("myfifo1", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    if(mkfifo("myfifo2", 0664) == -1)
    {
        if(errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    int fd_w, fd_r;
    
    if((fd_r = open("myfifo1", O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }
    
    if((fd_w = open("myfifo2", O_WRONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    char buf[128] = "";
    ssize_t bytes;
    while(1)
    {
        if((bytes = read(fd_r, buf, sizeof(buf))) == -1)
        {
            perror("fail to read");
            exit(1);
        }

        printf("from send: %s\n", buf);

        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';

        write(fd_w, buf, sizeof(buf));
    }

    return 0;
}

2、信号

  在Linux系统中信号是由系统已经定义好的一些宏,所以不能自己定义,直接使用系统中的信号即可。使用kill -l可以查看系统定义的信号。
  信号本质是软件中断,它是在软件层次上对中断机制的一种模拟。信号是一种异步通信方式。
  信号是有当前系统已经定义好的一些表示,每一个标识都会在特定的场合使用,并且都会对进程有一定的影响,当信号产生时,会对当前进程作出相应的操作。
信号的相关操作:
Linux中信号内容博大精深,此处略微总结一种使用。

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
    -->
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
功能:当进程中产生某一个信号时,对当前信号进行处理
参数:
    sig:指定要处理的信号
    handler:处理方式
        SIG_IGN 当信号产生时,以缺省(忽略)的方式处理
        SIG_DFL 当信号产生时,以当前信号默认的方式处理
        void handler(int sig):当信号产生时,通过信号处
                理函数自定义方式处理,函数名可以随便写,
                参数表示当前的信号
返回值:
    成功:当前进程默认的处理方式
    失败:SIG_ERR

例如:

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

void handler(int sig)
{
    if(sig == SIGINT)
    {
        printf("SIGINT正在处理\n");
    }

    if(sig == SIGQUIT)
    {
        printf("SIGQUIT正在处理\n");
    }

    if(sig == SIGTSTP)
    {
        printf("SIGTSTP正在处理\n");
    }
}

int main(int argc, char const *argv[])
{
    //使用signal函数对产生的信号做出处理
    //使用signal函数,如果信号不产生,则永远也不会执行对应的处理方式
    //但是一旦信号产生了,就会立即执行对应的处理方式

    //当信号产生后,会立即执行signal函数设置的处理方式,当处理方式执行完毕,又会回到之前的代码位置继续执行
    //如果信号使用signal设置了处理方式,则当前程序中都有效

    //一般signal函数要放在程序的最前面,保证信号产生后是按照自己设置的处理方式进行处理的
    
    //以默认的方式处理信号
#if 0
    if(signal(SIGINT, SIG_DFL) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    if(signal(SIGQUIT, SIG_DFL) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    if(signal(SIGTSTP, SIG_DFL) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
#endif

    //以忽略的方式来处理信号
#if 0
    if(signal(SIGINT, SIG_IGN) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    if(signal(SIGQUIT, SIG_IGN) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    if(signal(SIGTSTP, SIG_IGN) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
#endif
    //SIGKILL和SIGSTOP信号只能以默认的方式处理,不能被忽略或者自定义
    // if(signal(SIGKILL, SIG_IGN) == SIG_ERR)
    // {
    //     perror("fail to signal");
    //     exit(1);
    // }

    //以用户自定义方式处理信号
    if(signal(SIGINT, handler) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    if(signal(SIGQUIT, handler) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }
    if(signal(SIGTSTP, handler) == SIG_ERR)
    {
        perror("fail to signal");
        exit(1);
    }

    while(1)
    {
        printf("hello world\n");
        sleep(1);
    }

    return 0;
}

ps:剩余三种通信方式下期在做总结☺

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值