目录
3、实例:创建一个共享内存,进程1写数据进去,进程2读数据出来
7、sigaction函数-信号量携带信息(检查或修改与指定信号相关联的处理动作)
一、进程间通信
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
二、管道通信原理
无名管道
1、特点:
- 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
- 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
- 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。不存在磁盘中。
2、函数原型
int pipe(int fd[2]);
参数说明:数组
返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开
关闭管道时,close(fd[0]);close(fd[1]);
3、实例:父进程写数据,子进程读数据
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2];//两个文件标识符
int pid;//进程标识符
char buf[128];//数据存储区
//创建管道失败,返回-1
if(pipe(fd)==-1){
printf("create pipe failed\n");
}
//创建子进程
pid = fork();
//创建子进程失败
if(pid<0){
printf("create child failed\n");
}
//父进程
else if(pid>0){
printf("this is father\n");
close(fd[0]);//关闭读端
write(fd[1],"hello from dad",strlen("hello from dad"));//写数据到管道
wait();//等待子进程关闭
}
//子进程
else{
printf("this is child\n");
close(fd[1]);//关闭写端
read(fd[0],buf,128);//读数据从管道
printf("read from dad:%s\n",buf);
exit(0);//子进程退出
}
return 0;
}
命名管道
1、简介
FIFO,是一种文件类型
2、特点
- FIFO可以在无关的进程间进行通信,而不是像无名管道只能在父子或者兄弟进程间;
- FIFO有路径名,以一种特殊设备文件形式存在于文件系统中。
3、函数原型
int mkfifo(const char* pathname,mode_t mode);
参数说明:
pathname:路径名
mode:与open函数的mode相同
FIFO可以使用write、open等一般的文件I/O函数来操作。
返回值:
成功返回0,失败返回-1
4、实例:创建命名管道
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
int main()
{
if((mkfifo("./file",0600))==-1 && errno!=EEXIST){
printf("mkfifo failed\n");
perror("why");
}
return 0;
}
5、open
当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK):
①默认不设置,当只读open时,会阻塞,直到有一个进程写打开这个FIFO;同样当只写open时,会阻塞,直到有一个进程读打开这个FIFO;
②设置非阻塞后,只读open立即返回,只写open则出错立即返回-1
6、实例:一个进程写数据,另一个读数据
write.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char *str ="message from fifo";
int fd = open("./file",O_WRONLY);//只写
printf("write success\n");
write(fd,str,strlen(str));
close(fd);
return 0;
}
read.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
char buf[30] = {0};
//创建管道
if((mkfifo("./file",0600))==-1 && errno!=EEXIST){
printf("mkfifo failed\n");
perror("why");
}
int fd = open("./file",O_RDONLY);//只读
printf("open success\n");
int readsize = read(fd,buf,30);
printf("read %d byte from fifo,context:%s\n",readsize,buf);
close(fd);
return 0;
}
打开两个终端,先运行read和write其中一个,会阻塞,此时运行另一个后,才会成功
运行结果:
写端:write success
读端:open success
read 17 byte from fifo,context:message from fifo
三、消息队列
1、简介
消息队列就是存放在内核中的消息链表。一个消息队列由一个标识符(即队列ID来标识)。
2、特点
- 消息具有特定格式
- 消息队列存在于内核中,不会随着进程的结束而删除
- 消息队列可以实现消息的随机查询,因为是链表
3、函数原型
#include <sys/msg.h>
①创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
②添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
③读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
④控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
4、实例:一个进程发送,另一个接收
发送端——send.c
#include <stdio.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
//结构体{数据类型,数据}
struct msgbuf{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf sendbuf={888,"this is message from que\n"};//创建数据结构体
//创建消息队列(key,flag|权限)
int msgID = msgget(0x1234,IPC_CREAT|0777);
if(msgID == -1){
printf("get que failed\n");
}
//发送数据向消息队列(队列标识符,结构体指针,数据长度,flag)
msgsnd(msgID,&sendbuf,strlen(sendbuf.mtext),0);
return 0;
}
接收端——get.c
#include <stdio.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
struct msgbuf{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf readbuf;
int msgID = msgget(0x1234,IPC_CREAT|0777);
if(msgID == -1){
printf("get que failed\n");
}
//读取数据(消息队列标识符,结构体指针,数据长度,数据类型,flag)
msgrcv(msgID,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que:%s\n",readbuf.mtext);
return 0;
}
打开两个终端,先运行get和send其中一个,会阻塞,此时运行另一个后,才会成功
运行结果:
发送端:
接收端:read from que:this is message from que
5、ftok函数
作用: 将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
查询文件索引节点号的方法是: ls -i
函数原型:
key_t ftok( const char * fname, int id )
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
key = ftok(".", 1); 这样就是将fname设为当前目录。
#include <sys/types.h>
#include <sys/ipc.h>
key_t key;
key = ftok(".",'s');
printf("key=%x\n",key);
int msgID = msgget(key,IPC_CREAT|0777) ;
运行结果:key=7305631a
6、删除队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数说明:
msqid:消息队列标识符
cmd:命令(IPC_RMID-删除)
实例:
msgctl(msgID,IPC_RMID,NULL);
四、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个指定的存储区。
1、函数原型
包含头文件
#include <sys/ipc.h>
#include <sys/shm.h>
① 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
②连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
③断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
④控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
2、共享内存相关指令
查看所有共享内存:ipcs -m
删除某一个共享内存:ipcrm -m (shmid)
3、实例:创建一个共享内存,进程1写数据进去,进程2读数据出来
写端:shmw.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main()
{
int shmid;//共享内存ID
char *shmaddr;//映射的地址
key_t key;
key = ftok(".",1);
shmid = shmget(key,1024*4,IPC_CREAT|0666);//shmget创建共享内存,返回ID
if(shmid == -1){
printf("shmget failed\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间
printf("shmat ok\n");
strcpy(shmaddr,"wydhh");//使用strcpy写入数据
sleep(4);//休眠等待另一个进程读取
shmdt(shmaddr);//断开与共享内存的连接
shmctl(shmid,IPC_RMID,0);//删除共享内存
printf("quit\n");
return 0;
}
读端:shmr.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".",1);
shmid = shmget(key,1024*4,0);//读数据,flag置0
if(shmid == -1){
printf("shmget failed\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);
printf("shmat ok\n");
printf("data:%s\n",shmaddr);//读数据
shmdt(shmaddr);//断开连接
printf("quit\n");
return 0;
}
运行结果:
写端:shmat ok
quit读端:shmat ok
data:wydhh
quit
五、信号
在Linux中,信号就是软件中断。信号为Linux提供了一种处理异步事件的方法。比如终端输入ctrl+c来中断程序。
1、简介
每个信号都有名字和编号,以"SIG"开头,例如“SIGKILL”。可以使用kill -l来查看信号的序号和名称。
2、信号的处理
①忽略,有两种不可忽略(SIGKILL和SIGSTOP)
②捕捉,收到某个信号需要做什么
③系统默认,一般来说,系统直接杀死进程
3、常见指令
使用kill 9 PID杀死进程
4、信号忽略
signal(信号名,SIG_IGN);
5、信号处理函数
//本程序修改ctrl+c退出为打印两行说明
#include <signal.h>
#include <stdio.h>
void handler(int signum)//处理函数(信号序号)
{
printf("get signum=%d\n",signum);
printf("don't quit\n");
}
int main()
{
signal(SIGINT,handler);//信号处理函数,(信号名,处理函数)
while(1);
return 0;
}
6、利用程序发送kill指令
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc,char **argv)//信号序号,进程ID
{
int signum;
int pid;
signum = atoi(argv[1]);//ascii转int,信号序号
pid = atoi(argv[2]);//进程id
printf("num=%d,pid=%d\n",signum,pid);
kill(pid,signum);//发送指令
printf("send kill ok\n");
return 0;
}
//-----------另一种-------//不用kill函数
char cmd[128] = {0};
sprintf(cmd,"kill -%d %d",signum,pid);//将这一串命令封装成cmd
system(cmd);//使用system函数发送出去
7、sigaction函数-信号量携带信息(检查或修改与指定信号相关联的处理动作)
函数原型:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数说明:
signum:指出要捕获的信号类型
act:指定新的信号处理方式
oldact:指向当前的处理器,若无意获取此信息,可以指定为NULL。
struct aigaction结构体:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }
- sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
- sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
- sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
直接整实例
发送端:
#include <stdio.h>
#include <signal.h>
int main(int argc,char **argv)
{
int signum;//信号序号
int pid;//进程ID
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;//指定数据
sigqueue(pid,signum,value);//发送数据
printf("done\n");
return 0;
}
接收端:
#include <signal.h>
#include <stdio.h>
void handler(int signum,siginfo_t *info,void *context)//处理函数(信号量序号,内容,内容有无)
{
printf("get signum:%d\n",signum);//信号量序号
if(context != NULL){
printf("get data = %d\n",info->si_int);
printf("get data = %d\n",info->si_value.sival_int);
printf("send's pid=%d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;//结构体
printf("pid=%d\n",getpid());//打印当前进程的ID
act.sa_sigaction = handler;//结构体的sigaction指向信号量处理handler函数
act.sa_flags = SA_SIGINFO;//结构体的flag
sigaction(SIGUSR1,&act,NULL);//接收函数,(信号量序号,结构体,空)
while(1);
return 0;
}
六、信号量(不涉及数据)
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
临界资源:一次仅允许一个进程使用的资源被称为临界资源
现在有一个房子(临界资源),许多人(进程访问)要进去,门上有锁,门口有钥匙(信号量),每次只能进一个人(一个进程访问);P操作(拿锁);V操作(放锁)
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
1、函数原型
头文件:#include <sys/sem.h>
①创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);参数说明:
key:使用ftok获取
num:信号量集中信号量的个数
flag:如果没有就创建,有就调用
②对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
③控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);参数说明:
semid:信号量集id
num:操作第几个数
cmd:很多自行查阅,SETVAL设置信号量初值
联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
}
2、实例:子进程放信号量,父进程取信号量之后再放信号量
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//联合体
union semun{
int val;//给setval赋值
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
//P操作,取钥匙
void pGetKey(int id){
struct sembuf set;//实例化联合体命名为set
set.sem_num = 0;
set.sem_op = -1;//信号量(钥匙)数量的操作,取就要-1
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("getkey\n");
}
void vPutKey(int id){
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;//信号量(钥匙)数量的操作,放就要+1
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("putkey\n");
}
int main(int argc,char const *argv[])
{
key_t key;
int semid;
key = ftok(".",2);
semid = semget(key,1,IPC_CREAT|0666);
//1:信号量集合中有一个信号量
//获取/创建信号量
//-----初始化信号量集-------//
union semun initsem;
initsem.val = 0;//信号量集中信号量个数
semctl(semid,0,SETVAL,initsem);
//0:操作第0个信号量
//SETVAL设置信号量值,设置为inisem
//-----初始化信号量集-------//
int pid = fork();
if(pid >0){
pGetKey(semid);//拿锁
printf("this is father\n");
vPutKey(semid);//放锁
}
else if(pid == 0){
printf("this is child\n");
vPutKey(semid);//放锁
}
else{
printf("fork error\n");
}
return 0;
}