进程间通信_原始
由于多进程间内存空间是独立的,就不能够想线程一样使用全局变量完成通信。可以利用文件实现进程和进程间的数据传输,但是使用文件通信需要写和读的先后顺序,但是有的时候你没有办法保证这两个进程执行的先后顺序,所以使用文件进行进程间通讯不可取。如何实现进程间通信?通过内核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;
}