进程间通讯_原始

进程间通信_原始

由于多进程间内存空间是独立的,就不能够想线程一样使用全局变量完成通信。可以利用文件实现进程和进程间的数据传输,但是使用文件通信需要写和读的先后顺序,但是有的时候你没有办法保证这两个进程执行的先后顺序,所以使用文件进行进程间通讯不可取。如何实现进程间通信?通过内核3-4G的空间完成。Linux内核提供的专用用于进程间通信的方式有7中。通讯方式如下:

1.Linux内核中原始的进程间通信方式

无名管道

有名管道

信号

2.在system V中有引入三种IPC的进程间通信方式

消息队列

共享内存

信号灯

3.BSD(伯克利分校)

套接字通信(通过网络实现进程间通信)

4.在Android系统中通过binder实现进程间通信

binder机制 (Android内核就是完整的Linux,binder也是Linux内核中实现的)

1、无名管道

在这里插入图片描述

1.1无名管道的API

int pipe(int pipefd[2]);
功能:创建一个管道,返回管道两端的文件描述符,无名管道只能用户亲缘关系的进程间通信
参数:
    @pipefd:返回的管道的两端的文件描述符
     pipefd[0];读端
     pipefd[1]:写端
返回值:成功返回0,失败返回-1置位错误码
/*
Pipe()创建一个管道,这是一个单向的数据通道,可用于进程间通信。
pipefd数组用于返回指向管道末端的两个文件描述符。Pipefd[0]指
向管道的读端。Pipefd[1]指的是管道的写入端。写入到写端的数据
的内核对管道进行缓冲,直到从管道的读端读取它为止。要了解更
多细节,请参见管道(7)。
*/

1.2无名管道的实例

#include <head.h>

int main(int argc,const char * argv[])
{
    int pipefd[2];
    char buf[128];
    pid_t pid;
    //1.创建无名管道
    if(pipe(pipefd)==-1)
        PRINT_ERR("create pipe error\n");
    
    pid = fork();
    if(pid<0){
        PRINT_ERR("fork error");
    }else if (pid == 0){
        //子进程写
        close(pipefd[0]); //关闭读端
        while(1){
            //从终端上将数据输入到buf数组中
            fgets(buf,sizeof(buf),stdin);
            buf[strlen(buf)-1]='\0';
            //将终端输入到buf的数据写入到管道中
            write(pipefd[1],buf,strlen(buf));
            if(strcmp(buf,"quit")==0){
                break;
            }
        }
        close(pipefd[1]);
        exit(EXIT_SUCCESS);

    }else{
        //父进程读
        close(pipefd[1]); //关闭写端
        while(1){
            memset(buf,0,sizeof(buf));
            read(pipefd[0],buf,sizeof(buf));
            if(strcmp(buf,"quit")==0)break;
            printf("父进程读到的数据=%s\n",buf);
        }
        close(pipefd[0]);
        wait(NULL);
    }
        
    return 0;
}

1.3无名管道的特点

1.无名管道只能用于亲缘间的进程的通信

2.无名管道的大小是64K

3.无名管道中不能使用lseek

4.无名管道属于半双工的通信方式

单工:只能一边发另外一边收

	 A---->B

半双工:在同一时刻能只能一边发另外一边收

	 A--->B   A<---B

全双工:在同一时刻内容可以同时收发

	 A<---->B

5.读写的特点如下

如果读端存在,写管道,有多少写多少,直到写满64k为止

如果写端存在,读管道,有多少读多少,没有数据读的时候读阻塞

如果读端不存在,写管道,管道破裂SIGPIPE

如果写到不存在,读管道,有多少读多少,没有数据的时候立即返回

2、有名管道

2.1有名管道API

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件(特殊文件在内存上存放)
参数:
    @pathname:文件的路径和文件的名字
    @mode:管道文件的权限
 返回值:成功返回0,失败返回-1置位错误码
 /*
 mkfifo()创建一个名为pathname的FIFO特殊文件。mode指定
 FIFO的权限。它被进程的umask以通常的方式修改:创建文件
 的权限是(mode & ~umask)。
 FIFO特殊文件类似于管道,只是它是以不同的方式创建的。一
 个FIFO特殊文件不是匿名通信通道,而是通过调用mkfifo()进
 入文件系统。一旦您以这种方式创建了一个FIFO特殊文件,任
 何进程都可以以与普通文件相同的方式打开它进行读写。但是,
 在您可以对它进行任何输入或输出操作之前,它必须在两端同
 时打开。打开一个FIFO来读取通常会阻塞,直到其他进程打开
 同一个FIFO来写入,反之亦然。参见fifo(7)的fifo特殊文件
 的非阻塞处理。
 */

2.2有名管道实例

01mkfifo.c:创建管道

#include <head.h>

int main(int argc,const char * argv[])
{
    //1.创建管道
    if(mkfifo("./myfifo",0664)==-1)
        PRINT_ERR("create mkfifo error");

    //2.等待读写的操作
    getchar();

    //3.销毁管道文件
    system("rm ./myfifo");
        
    return 0;
}

02write.c:向管道中写数据

#include <head.h>

int main(int argc,const char * argv[])
{
    int fd;
    char buf[128] = {0};
    if((fd = open("./myfifo",O_WRONLY))==-1)
        PRINT_ERR("write open error");

    while(1){
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1]='\0';
        //向有名管道中写数据
        write(fd,buf,strlen(buf));
        if(strcmp(buf,"quit")==0)break;
    }
    close(fd);
    return 0;
}

03read.c:从管道中读数据

#include <head.h>

int main(int argc,const char * argv[])
{
    int fd;
    char buf[128] = {0};
    if((fd = open("./myfifo",O_RDONLY))==-1)
        PRINT_ERR("read open error");

    while(1){
        memset(buf,0,sizeof(buf));
        //从管道中读数据
        read(fd,buf,sizeof(buf));
        if(strcmp(buf,"quit")==0)break;
        printf("read = %s\n",buf);
    }
    close(fd);
    return 0;
}

2.3有名管道的特点

1.有名管道可以用于亲缘或非亲缘间的进程通讯

2.有名管道创建后会出现管道文件,管道文件在内存上存放,没有在磁盘上存放

3.有名管道的大小也是64K

4.有名管道也不支持lseek函数

5.有名管道的通信方式(单工,半双工)取决于open时候的打开方式

6.有名管道的读写特点

如果读端存在,写管道,有多少写多少,直到写满64k为止

如果写端存在,读管道,有多少读多少,没有数据的时候阻塞等待

如果读端不能存在,写管道:

	如果读端没有打开,写端在open的位置阻塞

	如果读端打开后又关闭了,写管道,管道破裂SIGPIPE

如果写端不存在,读管道:

	如果写端没有打开,读端在open的位置阻塞

	如果写端打开后有关闭了,读端立即返回

3.信号

3.1什么是信号

信号是软件模拟的中断的功能,在信号是软件实现的,中断是硬件实现的。信号是Linux内核实现的,如果没有Linux内核就没有信号的概念。用户可以给进程发信号,进程可以给进程发信号,内核也可以给进程发信号。信号属于异步通知。进程对信号的处理方式有三种默认,忽略,捕捉

3.2信号的种类及功能

3.2.1.查看信号

在这里插入图片描述

3.2.2常用的信号及含义

在这里插入图片描述
在这里插入图片描述

注:只有SIGKILL和SIGSTOP不能被捕捉,也不能被忽略

3.3信号处理函数API(signal)

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:这是为信号绑定信号处理函数
参数:
     @signum:信号号
     @handler:信号处理函数
         SIG_DFL :默认
         SIG_IGN :忽略
         handler:捕捉
         void signal_handle(int signo)
         {
         	//信号处理函数	
     	  }
返回值:成功返回handler的指针,失败返回SIG_ERR,置位错误码

3.4信号的实例

3.4.1练习处理函数的时候(默认,忽略,捕捉)

#include <head.h>
#include <signal.h>

//信号处理函数
void signal_handle(int signo)
{
    printf("用户按下了ctrl+c\n");
}
int main(int argc, const char *argv[])
{
    // 1.忽略信号
    if (signal(SIGINT, SIG_IGN) == SIG_ERR)
        PRINT_ERR("signal error");
        
    // 2.信号默认处理
    // if (signal(SIGINT, SIG_DFL) == SIG_ERR)
    //     PRINT_ERR("signal error");

    // 3.信号的捕捉 ctrl+c ===>SIGINT
    //  if(signal(SIGINT,signal_handle)==SIG_ERR)
    //      PRINT_ERR("signal error");

    while (1)
        ;
    return 0;
}
#include <head.h>
#include <signal.h>

//信号处理函数
void signal_handle(int signo)
{
    printf("尝试捕捉SIGKILL\n");
}
int main(int argc, const char *argv[])
{
    // 1.捕捉SIGKILL SIGSTOP
    // if (signal(SIGKILL, signal_handle) == SIG_ERR)
    //     PRINT_ERR("signal error");
    //如果捕捉就会出现:signal error: Invalid argument

    // 2.忽略SIGKILL SIGSTOP
    if (signal(SIGKILL, SIG_IGN) == SIG_ERR)
        PRINT_ERR("signal error");
    //如果捕捉就会出现:signal error: Invalid argument

    while (1)
        ;
    return 0;
}

3.4.2使用信号以非阻塞方式回收子进程资源

注:当子进程结束的时候,父进程会收到一个SIGCHLD的信号,在信号处理函数中就可以以非阻塞方式回收资源了

#include <head.h>
#include <signal.h>

//信号处理函数
void signal_handle(int signo)
{
    waitpid(-1,NULL,WNOHANG);
    printf("父进程以非阻塞的方式回收了子进程的资源\n");
}
int main(int argc, const char *argv[])
{
    pid_t pid;

    pid = fork();
    if(pid < 0){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        printf("子进程开始执行了...\n");
        sleep(2);
        printf("子进程执行结束了...\n");
        exit(EXIT_SUCCESS);
    }else{

        if(signal(SIGCHLD,signal_handle)==SIG_ERR)
            PRINT_ERR("signal error");

        while(1);
    }

    return 0;
}

3.5发信号的相关API(kill/raise)

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
功能:给指定的进程发信号
参数:
   @pid:进程号
    pid>0:给指定pid进程发信号
   	pid=0: 给当前进程组内的进程发信号
    pid=-1 :给所有的进程发信号(必须有权限)
    pid<-1 :将pid取反,将信号发给取反之后对应的进程组
   @sig:信号号
返回值:成功返回0,失败返回-1置位错误码    
         
int raise(int sig);
功能:给当前进程发信号
参数
    @sig:信号号
返回值:成功返回0,失败返回非0 
#include <head.h>
#include <signal.h>

//信号处理函数
void signal_handle(int signo)
{
    printf("收到了子进程发来的信号\n");
    kill(getpid(),SIGKILL);
}
int main(int argc, const char *argv[])
{
    pid_t pid;

    pid = fork();
    if(pid < 0){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        printf("子进程开始执行了...\n");
        sleep(2);
        kill(getppid(),SIGUSR1);
        printf("子进程执行结束了...\n");
        exit(EXIT_SUCCESS);
    }else{
        if(signal(SIGUSR1,signal_handle)==SIG_ERR)
            PRINT_ERR("signal error");

        while(1){
            sleep(1);
            printf("%s:%s:%d\n",__FILE__,__func__,__LINE__);
            //__FILE__:文件名
            //__func__:函数名
            //__LINE__:行号
        };
    }

    return 0;
}
#include <head.h>
#include <signal.h>

//信号处理函数
void signal_handle(int signo)
{
    printf("收到了子进程发来的信号\n");
    raise(SIGKILL);
}
int main(int argc, const char *argv[])
{
    pid_t pid;

    pid = fork();
    if(pid < 0){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        printf("子进程开始执行了...\n");
        sleep(2);
        kill(getppid(),SIGUSR1);
        printf("子进程执行结束了...\n");
        exit(EXIT_SUCCESS);
    }else{
        if(signal(SIGUSR1,signal_handle)==SIG_ERR)
            PRINT_ERR("signal error");

        while(1){
            sleep(1);
            printf("%s:%s:%d\n",__FILE__,__func__,__LINE__);
            //__FILE__:文件名
            //__func__:函数名
            //__LINE__:行号
        };
    }

    return 0;
}

3.6闹钟函数API(alarm)

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
功能:在seconds后发送闹钟信号
参数:
    @seconds:倒计时的秒钟
 	如果在倒计时到0前又重新给seconds赋值,重新计数seconds秒
	 如果第一次就是seconds赋值为0,就不会发SIGALRM信号了
返回值:返回上一次倒计时为0前的剩余的秒钟,如果是0表示之前没有调闹钟的函数
#include <head.h>
#include <signal.h>

//信号处理函数
void signal_handle(int signo)
{
    if(signo == SIGALRM){
        printf("我收到了5s的闹钟的信号\n");
    }
}
int main(int argc, const char *argv[])
{
    //注册信号处理函数,捕捉SIGALRM的信号
    if(signal(SIGALRM,signal_handle)==SIG_ERR)
        PRINT_ERR("signal error");

    //5秒后发送闹钟的信号,但是之前没有调用过alarm所以这里
    //alarm返回的是0
    printf("ret = %d\n",alarm(5));
    sleep(1);
    //这里第二个alarm函数的时候会把上一次alarm的值从新刷新
    //这里返回的是上一次alarm剩余的秒钟数,5秒之后执行信号
    //处理函数
    printf("ret = %d\n",alarm(5));
    
    while(1);
    return 0;
}

使用alarm模拟自动出牌实例

#include <head.h>
#include <signal.h>

//信号处理函数
void signal_handle(int signo)
{
    printf("时间到,自动出牌\n");
    alarm(4);
}
int main(int argc, const char *argv[])
{
    char ch;
    //注册信号处理函数,捕捉SIGALRM的信号
    if (signal(SIGALRM, signal_handle) == SIG_ERR)
        PRINT_ERR("signal error");

    //如果不出牌,4s后自动出牌
    alarm(4);
    
    while(1){
        ch=getchar();
        getchar();
        printf("手动出的牌是%c\n",ch);
        alarm(4);
    }
    return 0;
}

作业

1.使用条件变量实现生产者和消费者线程循环执行的效果。

2.练习课上的代码

使用条件变量实现生产者和消费者线程循环执行的效果。

#include <head.h>
#include <semaphore.h>

int flags = 0;
pthread_cond_t cond;
pthread_mutex_t mutex;
//生产者
void *task1(void *arg)
{
    while (1)
    {
        //flags:定义flags的目的就是为了确定生产者和消费者的执行的顺序
        //原因是如果不定义这个flags,生产者和消费者都要抢占这个锁,不一定
        //谁抢占成功,加上flags之后就能够确定生产者先执行
        
        //为什么要 while (flags != 0)而不使用 if (flags != 0)
        //如果生产者第一次抢占到锁,第二次还是生产者抢占到锁
        //第二次执行pthread_cond_wait会立即退出,原因上一次它执行了pthread_cond_signal
        //但是由于while是个循环flags的值没有改,锁它会第二次执行pthread_cond_wait休眠
        //直到消费者线程执行一次
        pthread_mutex_lock(&mutex);
        while (flags != 0)
            pthread_cond_wait(&cond, &mutex);
        flags = 1;
        sleep(1);
        printf("生产了一辆超级跑车\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}

//消费者
void *task2(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        while (flags == 0)
            pthread_cond_wait(&cond, &mutex);
        flags = 0;
        sleep(1);
        printf("我购买了一辆超级跑车\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    if (pthread_create(&tid1, NULL, task1, NULL))
        PRINT_ERR("create tid1 error");
    if (pthread_create(&tid2, NULL, task2, NULL))
        PRINT_ERR("create tid1 error");

    printf("tid1=%#lx,tid2=%#lx\n", tid1, tid2);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}

n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2;

pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);

if (pthread_create(&tid1, NULL, task1, NULL))
    PRINT_ERR("create tid1 error");
if (pthread_create(&tid2, NULL, task2, NULL))
    PRINT_ERR("create tid1 error");

printf("tid1=%#lx,tid2=%#lx\n", tid1, tid2);

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);

pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);

return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值