通信方式
1.无名管道
2.有名管道
3.信号
4.共享内存
5.消息队列
6.信号灯集
7.套接字
无名管道
特点
1.只能在具有亲缘关系的进程间通信
2.半双工的通信方式,具有固定的读端fd[0]和写端fd[1]
3.管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数.
4.管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符,fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。
函数
pipe()
定义:
int pipe(int pipefd[2]);
功能:
创建管道,返回两个文件描述符,fd[0]用于读,fd[1]用于写。
参数:
pipefd[2],一个整数数组,用于存放两个文件描述符。
返回值:
成功返回0,出错返回-1,并设置errno。
读写特性
1.读端:
1)当管道中无数据时,读操作会阻塞
2)管道中无数据时,将写端关闭,读操作会立即返回
2.写端:
1)管道中装满(64k)数据时,写阻塞,一旦有4k空间,写继续
2)当读端不存在时,写操作会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号:Broken pipe。
示例代码
创建子进程,父进程循环从终端输入字符串,子进程循环打印输入的字符串,当输入quit时程序结束。
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char buf[32] = "";
int fd[2] = {0};
if (pipe(fd) < 0)
{
perror("pipe create error");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork error");
return -1;
}
// 子进程
else if (pid == 0)
{
while (1)
{
read(fd[0], buf, 32);
if (strcmp(buf,"quit")==0)
break;
printf("%s\n", buf);
}
}
// 父进程
else if (pid > 0)
{
while (1)
{
scanf("%s", buf);
write(fd[1], buf, 32);
if (strcmp(buf,"quit")==0)
break;
}
wait(NULL);
}
return 0;
}
有名管道、
特点
1.可以用于两个互不相关的进程间通信
2.有名管道是实际存在的一个文件,可以用文件路径指出,但其内容存放在内存中
3.通过文件IO来操作管道
4.有名管道遵循先进先出原则
5.有名管道不支持iseek之类的操作
函数
mkfifo()
定义:
int mkfifo(const char *pathname, mode_t mode);
功能:
创建一个命名管道。
参数:
pathname,管道的路径名。
mode,管道权限。
返回值:
成功返回0,出错返回-1,并设置errno。
读写特性
1.用只写模式打开有名管道时,写端会阻塞,直到有读端打开。
2.用只读模式打开有名管道时,读端会阻塞,直到有写端打开。
3.用可读可写模式打开有名管道时,如果管道中没有数据,读端会阻塞。
示例代码
创建两个独立的进程,实现一个进程循环输入字符串,另一个进程打印输入的字符串,当输入quit时结束。
// 输入端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
printf("FILE EXIST\n");
else
{
perror("mkfifo error");
return -1;
}
}
printf("mkfifo ready\n");
char buf[32] = "";
int fd = open("./fifo", O_WRONLY);
if (fd < 0)
{
perror("open error");
return -1;
}
while (1)
{
scanf("%s",buf);
write(fd, buf, 32);
if (strcmp(buf,"quit")==0)
break;
}
return 0;
}
// 输出端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{ /* 此段用于创建管道,已在输入端创建,可写可不写
if (mkfifo("./fifo", 0666) < 0)
{
if (errno == EEXIST)
printf("FILE EXIST\n");
else
{
perror("mkfifo error");
return -1;
}
}
printf("mkfifo ready\n");
*/
char buf[32] = "";
int fd = open("./fifo", O_RDONLY);
if (fd < 0)
{
perror("open error");
return -1;
}
while (1)
{
read(fd, buf, 32);
if (strcmp(buf,"quit")==0)
break;
printf("%s\n", buf);
}
return 0;
}
使用以下命令为两个程序生成独立的可执行文件,然后在终端分别执行两个进程,方便观察结果:
# 编译
gcc in.c -o in
gcc out.c -o out
# 运行输入端
./in
# 运行输出端
./out
运行结果
信号
信号的概念
1.信号是一种软件中断,它是由操作系统发送给进程的一种通知消息,这是一种异步通信方式。
2.信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3.如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
信号的分类
在Linux操作系统中,信号分为可靠信号和不可靠信号,一共64种,可用kill -l命令查看。
1.不可靠信号:也称为非实时信号,不支持排队,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,信号值取值区间为1~31。
2.可靠信号:也称为实时信号,支持排队,信号不会丢失,发多少次,就可以收到多少次,信号值取值区间为34~64。
信号的产生方式
1.对于前台进程,用户可以输入特殊终端字符来发送:比如:输入ctrl c
2.在终端运行kill命令或者在程序中调用 kill 函数
3.系统状态变化,比如alarm定时器到期时将引起SIGALRM信号
信号的处理方式
1.忽略信号:不做任何处理
2.捕获信号:执行自定义的信号处理函数
3.执行默认操作:Linux系统中对每种信号规定了默认操作,即执行信号默认的功能
函数
kill()
定义:
int kill(pid_t pid, int sig);
功能:
向进程发送信号。
参数:
pid,进程ID。
sig,信号值。
返回值:
成功返回0,出错返回-1,并设置errno。
raise()
定义:
int raise(int sig);
功能:
向调用该函数的进程发送信号。
参数:
sig,信号值。
返回值:
成功返回0,出错返回-1,并设置errno。
alarm()
定义:
unsigned int alarm(unsigned int seconds);
功能:
设置一个定时器,在指定的时间后向进程发送SIGALRM信号结束进程。
如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替
参数:
seconds,定时器时间,单位为秒。
返回值:
成如果调用此alarm()前,进程中已经设置了闹钟时间,
则返回上一个闹钟时间的剩余时间,否则返回0
pause()
定义:
int pause(void);
功能:
挂起进程,直到收到信号。
参数:
无
返回值:
无
signal()
定义:
void (*signal(int signum, void (*func)(int)))(int);
或:
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:
signum:要处理的信号
handler:信号处理方式
SIG_IGN:忽略信号
SIG_DFL:执行默认操作
handler:捕捉信号
返回值:
成功:设置之前的信号处理方式
失败:-1
示例代码
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <stdlib.h>
pid_t pid;
void conductor(int sig)
{
switch (sig)
{
case SIGINT:
kill(getppid(), SIGUSR1);
break;
case SIGQUIT:
kill(getppid(), SIGUSR2);
break;
case SIGUSR1:
printf("Conductor:please get off the bus\n");
exit(0);
break;
}
}
void driver(int sig)
{
switch (sig)
{
case SIGUSR1:
printf("Driver:let's gogogo\n");
break;
case SIGUSR2:
printf("Driver:stop the bus\n");
break;
case SIGTSTP:
kill(pid, SIGUSR1);
wait(NULL);
exit(0);
break;
}
}
int main(int argc, char const *argv[])
{
pid = fork();
if (pid < 0)
{
perror("fork error");
return -1;
}
// 子进程(售票员)
else if (0 == pid)
{
while(1)
{
signal(SIGINT, conductor);
signal(SIGQUIT, conductor);
signal(SIGUSR1, conductor);
signal(SIGTSTP, SIG_IGN);
}
}
// 父进程(司机)
else
{
while(1)
{
signal(SIGUSR1, driver);
signal(SIGUSR2, driver);
signal(SIGTSTP, driver);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
}
}
return 0;
}
通过题目的描述,可以得出以下结果:
司机:
忽略的信号:SIGINT、SIGQUIT
捕捉的信号:SIGUSR1、SIGUSR2、SIGTSTP
售票员:
忽略的信号:SIGTSTP
捕捉的信号:SIGINT、SIGQUIT、SIGUSR1
分别在父、子进程用signal函数捕捉相应的函数,然后再将信号传入对应的函数用switch语句进行处理。
运行结果
共享内存
概念
共享内存指的是操作系统在物理内存中申请一块空间,应用程序可以映射到这块空间,进行直接读写操作。
特点
1.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2.为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
3.进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率
4.由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
操作步骤
1.创建唯一key值
2.创建或打开共享内存
3.映射到进程地址空间
4.读写共享内存
5.解除映射
6.删除共享内存
函数
创建key值:ftok()
定义:
key_t ftok(const char *pathname, int proj_id);
功能:
创建一个唯一的key值。
参数:
pathname,共享内存的路径名。
proj_id,项目ID。
返回值:
成功返回key值,出错返回-1,并设置errno。
创建共享内存:shmget()
定义:
int shmget(key_t key, size_t size, int flag);
功能:
创建一个共享内存,并返回一个唯一的key值。
参数:
key,共享内存的key值。
size,共享内存的大小。
flag,共享内存的访问权限。
返回值:
成功返回共享内存的ID,出错返回-1,并设置errno。
映射共享内存:shmat()
定义:
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
映射共享内存到进程地址空间。
参数:
shmid,共享内存的ID。
shmaddr,映射到进程地址空间的起始地址,一般为NULL,表示由系统自动完成映射。
shmflg,映射标志,SHM_RDONLY就是对该共享内存只进行读操作。
返回值:
成功返回映射到进程地址空间的起始地址,出错返回(void *)-1,并设置errno。
取消映射:shmdt()
定义:
int shmdt(const void *shmaddr);
功能:
取消映射共享内存。
参数:
shmaddr,映射到进程地址空间的起始地址。
返回值:
成功返回0,出错返回-1,并设置errno。
删除共享内存:shmctl()
定义:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
删除共享内存。
参数:
shmid,共享内存的ID。
cmd,操作命令
IPC_STAT 获得shmid属性信息,存放在第三参数
IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
IPC_RMID:删除共享内存,此时第三个参数为NULL即可
buf,共享内存的相关信息。
返回值:
成功返回0,出错返回-1,并设置errno。
相关命令
ipcs -m
查看系统中创建的共享内存
ipcrm -m [shmid]
删除创建的共享内存
示例代码
通过共享内存实现进程间通信,一个进程从终端输入数据,另一个进程打印数据,循环执行,当输入quit时循环结束
// 输入端
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
typedef struct Data
{
int flag;
char buf[32];
} data;
int main(int argc, char const *argv[])
{
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok error");
return -1;
}
int shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0)
{
if (errno == EEXIST)
{
printf("file exist\n");
shmid = shmget(key, 64, 0666);
}
else
{
perror("shmget error");
return -1;
}
}
data *p = shmat(shmid, NULL, 0);
if (p == (void *)-1)
{
perror("shmat error");
return -1;
}
while (1)
{
if (p->flag == 0)
{
scanf("%s", p->buf);
p->flag = 1;
if (strcmp(p->buf, "quit") == 0)
break;
}
}
return 0;
}
// 输出端
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
typedef struct Data
{
int flag;
char buf[32];
} data;
int main(int argc, char const *argv[])
{
key_t key = ftok("./app", 'a');
if (key < 0)
{
perror("ftok error");
return -1;
}
int shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0)
{
if (errno == EEXIST)
{
printf("file exist\n");
shmid = shmget(key, 64, 0666);
}
else
{
perror("shmget error");
return -1;
}
}
data *p = shmat(shmid, NULL, 0);
if (p == (void *)-1)
{
perror("shmat error");
return -1;
}
while (1)
{
if (p->flag == 1)
{
if (strcmp(p->buf, "quit") == 0)
break;
printf("%s\n", p->buf);
p->flag = 0;
}
}
shmdt(p);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
信号灯集
概念
信号灯(semaphore),也就是信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;
System V信号灯集是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作。
操作步骤
1.创建key值
2.创建或打开信号灯集
3.初始化信号灯集
4.P、V操作
5.删除信号灯集
函数
创建信号灯集:semget()
定义:
int semget(key_t key, int nsems, int semflg);
功能:
创建一个信号灯集,并返回一个唯一的key值。
参数:
key,信号灯集的key值。
nsems,信号灯集中信号灯的个数。
semflg,信号灯集的访问权限,通常为IPC_CREAT |0666
返回值:
成功返回信号灯集的ID,出错返回-1,并设置errno。
在创建信号灯集后,如果进行了容错判断,即判断semget函数的返回值,并且设置了perror错误提示,运行后如果出现以下提示:
错误提示:Success
这说明此时的信号灯集ID等于0,可以用以下命令删除信号灯集重新创建:
ipcs -s
查看信号灯集
ipcrm -m [semid]
删除信号灯集
初始化或删除信号灯集:semctl()
定义:
int semctl(int semid, int semnum, int cmd, ... /* arg */ );
功能:
初始化或删除信号灯集。
参数:
semid,信号灯集的ID。
semnum,信号灯的编号,从0开始。
cmd,操作命令
GETVAL:获取信号灯的值,返回值是获得值
SETVAL:设置信号灯的值
IPC_RMID:删除信号灯集,此时第三个参数为NULL即可
arg,信号灯属性,具体取决于cmd的值,当cmd为SETVAL时,需要传递共用体
返回值:
成功返回0,出错返回-1,并设置errno。
P、V操作:semop()
定义:
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:
P、V操作。
参数:
semid,信号灯集的ID。
sops,操作方式
nsops,要操作的信号灯的个数。
返回值:
成功返回0,出错返回-1,并设置errno。
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 0 : 等待,直到信号灯的值变成0
// 1 : 释放资源,V操作
// -1 : 分配资源,P操作
short sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};
消息队列
概念和特点
1.消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
2.消息队列可以按照类型来发送/接收消息
3.在linux下消息队列的大小有限制:
1)消息队列个数最多为16个;
2)每个消息队列总容量最多为16384字节;
3)每个消息内容最多为8192字节
操作步骤
1.创建key值
2.创建或打开消息队列
3.添加消息
4.读取消息
5.删除消息队列
函数
创建消息队列:msgget()
定义:
int msgget(key_t key, int msgflg);
功能:
创建一个消息队列,并返回一个唯一的key值。
参数:
key,消息队列的key值。
msgflg,消息队列的访问权限,通常为IPC_CREAT |0666
返回值:
成功返回消息队列的ID,出错返回-1,并设置errno。
添加消息:msgsnd()
定义:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:
向消息队列中添加消息。
参数:
msqid,消息队列的ID。
msgp,消息内容的起始地址。
msgsz,消息内容的大小。
msgflg,消息发送标志,通常为0。
返回值:
成功返回0,出错返回-1,并设置errno。
读取消息:msgrcv()
定义:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:
从消息队列中读取消息。
参数:
msqid,消息队列的ID。
msgp,消息内容的起始地址。
msgsz,消息内容的大小。
msgtyp,消息类型,0表示接收所有类型消息。
msgflg,消息接收标志,通常为0。
返回值:
成功返回消息的大小,出错返回-1,并设置errno。
删除消息队列:msgctl()
定义:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:
删除消息队列。
参数:
msqid,消息队列的ID。
cmd,操作命令
IPC_STAT 获得msqid属性信息,存放在第三参数
IPC_SET 设置msqid属性信息,要设置的属性放在第三参数
IPC_RMID:删除消息队列,此时第三个参数为NULL即可
buf,消息队列的相关信息。
返回值:
成功返回0,出错返回-1,并设置errno。
相关命令
ipcs -q
查看系统中创建的消息队列
ipcrm -q [msqid]
删除创建的消息队列