【1】分类
早期进程间通信方式:
无名管道
有名管道
信号通信
system V
消息队列
共享内存
信号灯集
BSD
scoket(套接字)通信
【2】本质
任何一个进程在32位操作里面都会有4G的虚拟空间,包含1G内核空间和3G用户空间,进程间能够通信,就是在内核空间进行读写数据。
共享内存效率最高,它是直接获取到物理内存的地址,对物理内存直接操作。
前六种进程间通信方式只能实现在同一台主机的多个进程之间通信,而套接字通信可以实现在不同的主机的进程之间通信
【3】无名管道
(1)性质
只能用于具有亲缘关系的进程之间的通信,半双工的通信模式,具有固定的读端和写端。
管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。
管道是基于文件描述符的通信方式。
当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。
fd[0]固定用于读管道,fd[1]固定用于写管道。
(2)创建无名管道
#include
int pipe(int fd[2]);
功能:创建一个无名管道
参数:
fd:操作无名管道的数组,有两个成员,第一个负责读,第二个负责写
返回值:
成功:0
失败:-1
#include
#include
#include
int main(int argc, const char *argv[])
{
//创建一个无名管道
int fd[2];
if(pipe(fd) < 0)
{
perror("fail to pipe");
exit(1);
}
//printf("fd[0] = %d\n", fd[0]);
//printf("fd[1] = %d\n", fd[1]);
//无名管道可以使用文件io的函数直接操作
//read write
//fd[1]负责写数据
//往无名管道写数据,下一次写的数据会放在第一个写的后面,以追加的方式写数据
char s1[32] = "hello world";
char s2[32] = "nihao beijing";
write(fd[1], s1, sizeof(s1));
write(fd[1], s2, sizeof(s1));
//fd[0]负责读数据
//从无名管道读取数据,读取的数据会从无名管道里面删除
char buf[32] = {};
read(fd[0], buf, sizeof(buf));
printf("buf = %s\n", buf);
read(fd[0], buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}
(3)无名管道的读写规律
如果读写端都存在,如果只读不写,如果管道内有数据,会正常读取,但是如果没有数据,就会一直阻塞。
#include
#include
#include
int main(int argc, const char *argv[])
{
int fd[2];
if(pipe(fd) < 0)
{
perror("fail to pipe");
exit(1);
}
//如果读写端都存在,如果只读不写,如果管道内有数据,会正常读取,但是如果没有数据,就会一直阻塞
char s[32] = "hello world";
write(fd[1], s, sizeof(s));
char buf[32] = {};
read(fd[0], buf, sizeof(buf));
printf("buf = %s\n", buf);
read(fd[0], buf, sizeof(buf));
printf("buf = %s\n", buf);
return 0;
}
如果读写端都存在,如果只写不读,当管道写满是,就会阻塞,默认无名管道64k字节。
#include
#include
#include
int main(int argc, const char *argv[])
{
int fd[2];
if(pipe(fd) < 0)
{
perror("fail to pipe");
exit(1);
}
//如果读写端都存在,如果只写不读,当管道写满是,就会阻塞,默认无名管道64k字节
char s[4] = "yes";
int n = 0;
while(1)
{
write(fd[1], s, 4);
n++;
printf("n = %d\n", n);
}
return 0;
}
如果只有读端没有写端,如果管道内有数据,则正常读取,如果没有数据,则read返回0。
#include
#include
#include
int main(int argc, const char *argv[])
{
int fd[2];
if(pipe(fd) < 0)
{
perror("fail to pipe");
exit(1);
}
//如果只有读端没有写端,如果管道内有数据,则正常读取,如果没有数据,则read返回0
write(fd[1], "hello world", 32);
close(fd[1]);
char buf[32] = {};
ssize_t bytes;
bytes = read(fd[0], buf, sizeof(buf));
printf("bytes = %d\n", bytes);
printf("buf = %s\n", buf);
bytes = read(fd[0], buf, sizeof(buf));
printf("bytes = %d\n", bytes);
printf("buf = %s\n", buf);
return 0;
}
如果只有写端没有读端,当运行到write时,就会产生SIGPIPE(管道破裂),当前信号默认的处理方式是结束整个进程,所以程序直接退出。
#include
#include
#include
int main(int argc, const char *argv[])
{
int fd[2];
if(pipe(fd) < 0)
{
perror("fail to pipe");
exit(1);
}
//如果只有写端没有读端,当运行到write时,就会产生SIGPIPE(管道破裂),
//当前信号默认的处理方式是结束整个进程,所以程序直接退出
close(fd[0]);
char s[4] = "yes";
int n = 0;
ssize_t bytes;
while(1)
{
bytes = write(fd[1], s, 4);
printf("bytes = %d\n", bytes);
n++;
printf("n = %d\n", n);
}
return 0;
}
以下是父子进程,通过无名管道实现互相通信:
#include
#include
#include
#include
#define N 32
int main(int argc, const char *argv[])
{
int fd1[2], fd2[2];
if(pipe(fd1) < 0)
{
perror("fail to pipe");
exit(1);
}
if(pipe(fd2) < 0)
{
perror("fail to pipe");
exit(1);
}
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程
{
//父进程先往管道写入数据
char buf[N] = {};
while(1)
{
fgets(buf, N, stdin);
buf[strlen(buf) - 1] = '\0';
write(fd1[1], buf, N);
if(strncmp(buf, "quit", 4) == 0)
{
exit(0);
}
else
{
read(fd2[0], buf, N);
if(strncmp(buf, "quit", 4) == 0)
{
exit(0);
}
else
{
printf("from child: %s\n", buf);
}
}
}
}
else //子进程
{
//子进程先读取管道的数据
char s[N] = {};
while(1)
{
read(fd1[0], s, N);
if(strncmp(s, "quit", 4) == 0)
{
exit(0);
}
else
{
printf("from parent: %s\n", s);
fgets(s, N, stdin);
s[strlen(s) - 1] = '\0';
write(fd2[1], s, N);
if(strncmp(s, "quit", 4) == 0)
{
exit(0);
}
}
}
}
return 0;
}
【4】有名管道
(1)性质
无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围。有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见。
进程通过文件IO来操作有名管道,有名管道遵循先进先出规则,不支持如lseek() 操作 。
有名管道还是在内核空间开辟区域,然后在本地创建一个管道文件用于标识内核空间的区域,对当前管道文件的操作就是对内核空间的操作。
(2)创建有名管道
方法1:使用mkfifo命令
mkfifo fifoname
方法2:使用mkfifo函数创建有名管道
#include
#include
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道,产生一个管道文件
参数:
pathname:文件名
mode:操作权限,一般为八进制数组成,例如0664
返回值:
成功:0
失败:-1
#include
#include
#include
#include
#include
#include
#include
#define FIFONAME "myfifo"
int main(int argc, const char *argv[])
{
//创建一个有名管道
if(mkfifo(FIFONAME, 0664) < 0)
{
//printf("errno = %d\n", errno);
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//对有名管道进行操作
//使用文件io对有名管道进行操作
//使用open函数打开文件获取文件描述符
int fd;
if((fd = open(FIFONAME, O_RDWR)) < 0)
{
perror("fail to open");
exit(1);
}
//向有名管道写入数据
write(fd, "hello world", 11);
char s[32] = {};
read(fd, s, 32);
printf("s = %s\n", s);
read(fd, s, 32);
printf("s = %s\n", s);
return 0;
}
(3)有名管道的读写规律
如果读写端都存在,如果只读不写,如果管道内有数据,会正常读取,但是如果没有数据,就会一直阻塞。
#include
#include
#include
#include
#include
#include
#include
#define FIFONAME "myfifo"
int main(int argc, const char *argv[])
{
if(mkfifo(FIFONAME, 0664) < 0)
{
//printf("errno = %d\n", errno);
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd;
if((fd = open(FIFONAME, O_RDWR)) < 0)
{
perror("fail to open");
exit(1);
}
//如果读写端都存在,如果只读不写,如果管道内有数据,会正常读取,但是如果没有数据,就会一直阻塞
write(fd, "hello world", 11);
char s[32] = {};
read(fd, s, 32);
printf("s = %s\n", s);
read(fd, s, 32);
printf("s = %s\n", s);
return 0;
}
如果读写端都存在,如果只写不读,当管道写满是,就会阻塞,默认有名管道64k字节。
#include
#include
#include
#include
#include
#include
#include
#define FIFONAME "myfifo"
int main(int argc, const char *argv[])
{
if(mkfifo(FIFONAME, 0664) < 0)
{
//printf("errno = %d\n", errno);
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd;
if((fd = open(FIFONAME, O_RDWR)) < 0)
{
perror("fail to open");
exit(1);
}
//如果读写端都存在,如果只写不读,当管道写满是,就会阻塞,默认有名管道64k字节
char s[1024] = {};
int n = 0;
while(1)
{
write(fd, s, 1024);
n++;
printf("n = %d\n", n);
}
return 0;
}
如果在一个进程里面,只有读端没有写端,则会在open函数的位置阻塞
#include
#include
#include
#include
#include
#include
#include
#define FIFONAME "myfifo"
int main(int argc, const char *argv[])
{
if(mkfifo(FIFONAME, 0664) < 0)
{
//printf("errno = %d\n", errno);
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
//如果在一个进程里面,只有读端没有写端,则会在open函数的位置阻塞
int fd;
if((fd = open(FIFONAME, O_RDONLY)) < 0)
{
perror("fail to open");
exit(1);
}
printf("******************************\n");
char s[32] = {};
read(fd, s, 32);
printf("s = %s\n", s);
read(fd, s, 32);
printf("s = %s\n", s);
return 0;
}
如果在一个进程里面,只有写端没有读端,则会在open函数的位置阻塞
#include
#include
#include
#include
#include
#include
#include
#define FIFONAME "myfifo"
int main(int argc, const char *argv[])
{
if(mkfifo(FIFONAME, 0664) < 0)
{
//printf("errno = %d\n", errno);
if(errno != EEXIST)
{
perror("fail to mkfifo");
exit(1);
}
}
int fd;
if((fd = open(FIFONAME, O_WRONLY)) < 0)
{
perror("fail to open");
exit(1);
}
printf("*****************************************\n");
char s[1024] = {};
int n = 0;
while(1)
{
write(fd, s, 1024);
n++;
printf("n = %d\n", n);
}
return 0;
}
如果一个进程只负责读,另一个进程只负责写,如果关闭写端,则读端会返回0,如果关闭读端,写端只要运行到write函数,则机会产生SIGPIPE信号,使得进程结束。
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFONAME "myfifo"
#define N 128
//当前进程向管道写入数据
int main(int argc, const char *argv[])
{
if(mkfifo(FIFONAME, 0664) < 0)
{
if(errno != EEXIST)
{
perror("fail to open");
exit(1);
}
}
int fd;
if((fd = open(FIFONAME, O_WRONLY)) < 0)
{
perror("fail to open");
exit(1);
}
printf("*************************\n");
char buf[N] = {};
while(1)
{
fgets(buf, N, stdin);
buf[strlen(buf) - 1] = '\0';
write(fd, buf, N);
if(strncmp(buf, "quit", 4) == 0)
{
exit(0);
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFONAME "myfifo"
#define N 128
//当前进程从管道读取数据
int main(int argc, const char *argv[])
{
if(mkfifo(FIFONAME, 0664) < 0)
{
if(errno != EEXIST)
{
perror("fail to open");
exit(1);
}
}
int fd;
if((fd = open(FIFONAME, O_RDONLY)) < 0)
{
perror("fail to open");
exit(1);
}
printf("*************************\n");
char buf[N] = {};
while(1)
{
read(fd, buf, N);
if(strncmp(buf, "quit", 4) == 0)
{
exit(0);
}
else
{
printf("buf = %s\n", buf);
}
}
return 0;
}
【5】信号通信
(1)性质
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利
用它来通知用户空间进程发生了哪些系统事件。
如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程
恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被
延迟,直到其阻塞被取消时才被传递给进程 。
信号是由当前系统已经定义好的一些标识,每一个标识都会在特定的场合产生,并且都会对进程有一定的影响,当信号产生时,会对当前进程做出相应的操作。
(2)信号的默认处理方式
当信号产生时,对进程默认的操作方式
方式1:结束整个进程
方式2:忽略,当产生当前信号时,进程不做任何操作
方式3:停止一个进程
方式4:让停止的进程恢复执行
(3)用户对信号的处理方式
默认的方式(缺省)
忽略
用户自定义(捕捉)
注意:SIGKILL和SIGSTOP这个两个信号只能以默认的方式处理
【4】常用的信号
kill -l 查看系统中所有的信号
1 – SIGKILL 9
性质:当产生当前信号时,进程结束,不能忽略也不能自定义操作
默认方式:结束一个进程
2 – SIGSTOP 19
性质:当产生当前信号时,进程会停止,不能忽略也不能自定义操作
默认方式:停止一个进程
3 – SIGINT 2
性质:当键盘输入ctrl+c,产生当前信号
默认方式:结束一个进程
4 – SIGQUIT 3
性质:当键盘输入ctrl+\,产生当前信号
默认方式:结束一个进程
5 – SIGTSTP 20
性质:当键盘输入ctrl+z,产生当前信号
默认方式:停止一个进程
6 – SIGCONT 18
性质:可以使得一个停止的进程恢复执行
默认方式:停止的进程回复执行
7 – SIGPIPE 13
性质:管道在操作的时候,如果关闭读端,当执行到写操作时,就会产生当前信号
默认方式:结束一个进程
8 – SIGALRM 14
性质:当使用alarm函数设定的时间到达时,会产生当前信号
默认方式:结束一个进程
9 – SIGCHLD 17
性质:当创建子进程后,如果子进程的状态改变(退出)时,就会产生当前信号
默认方式:忽略
10 – SIGUSR1 10 、 SIGUSR2 12
性质:用户自定义的信号,有当前进程自己主动产生当前信号
默认方式:忽略
(5)信号处理
1 – signal( )
#include
void (*signal(int sig, void (*func)(int)))(int);
-->
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:处理一个信号
参数:
sig:指定的信号
func:信号的处理方式
SIG_DFL 以默认的方式处理
SIG_IGN 以忽略的方式处理
func:信号处理函数,用户自定义的方式处理
返回值:
成功:当前信号默认的处理方式
失败:SIG_ERR
#include
#include
#include
void handler(int sig)
{
if(sig == SIGINT)
{
printf("SIGINT handler is running\n");
}
else if(sig == SIGQUIT)
{
printf("SIGQUIT handler is running\n");
}
else
{
printf("SIGTSTP handler is running\n");
}
}
int main(int argc, const char *argv[])
{
//使用signal函数改变进程对信号的响应方式
//以默认的方式处理
//signal(SIGINT, SIG_DFL);
//signal(SIGTSTP, SIG_DFL);
//以忽略的方式处理
//signal(SIGINT, SIG_IGN);
//signal(SIGTSTP, SIG_IGN);
#if 0
//SIGKILL和SIGSTOP这两个信号不能被忽略,也不能自定义处理
if(signal(SIGKILL, SIG_IGN) == SIG_ERR)
{
perror("fail to signal");
exit(1);
}
#endif
//以自定义方式处理
signal(SIGINT, handler);
signal(SIGQUIT, handler);
signal(SIGTSTP, handler);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
#include
#include
#include
void *ret;
void handler(int sig)
{
printf("SIGINT handler is running\n");
signal(SIGINT, ret);
}
int main(int argc, const char *argv[])
{
ret = signal(SIGINT, handler);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
(6)进程间发生信号
#include
int kill(pid_t pid, int sig);
功能:向一个进程发送信号
参数:
pid:绝对接收信号的进程
>0 指定进程号等于当前参数的进程接收信号
0 将信号发送给发送者所在进程组里面所有的进程
-1 发送给所有的进程
<-1 发送给当前参数的绝对值的进程组内的所有的进程
sig:指定的信号
返回值:
成功:0
失败:-1
#include
#include
#include
void handler(int sig)
{
printf("handler...\n");
}
int main(int argc, const char *argv[])
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程
{
signal(SIGINT, handler);
while(1)
{
printf("This is parent process\n");
sleep(1);
}
}
else //子进程
{
//子进程给父进程发送信号
printf("This is child process\n");
sleep(5);
kill(getppid(), SIGINT);
}
return 0;
}
#include
int raise(int sig);
功能:给自己发送信号
raise(sig) <==> kill(getpid(), sig)
(7)alarm( )
#include
unsigned int alarm(unsigned int seconds);
功能:定时器,当时间到达时会产生SIGALRM信号,使得进程结束
参数:
seconds:设定的秒数
返回值:
成功:上一个alarm函数剩余的时间,如果没有则返回0
#include
#include
#include
void handler(int sig)
{
printf("闹钟响了!!!\n");
}
int main(int argc, const char *argv[])
{
signal(SIGALRM, handler);
//alarm设定的时间,如果一个程序里面有多个alarm函数,则以最后一个时间为基准
int n = 0;
n = alarm(8);
printf("n = %d\n", n);
sleep(2);
n = alarm(3);
printf("n = %d\n", n);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
(8)pause()
#include
int pause(void);
功能:阻塞等待一个信号的产生
参数:
无
返回值:
成功:具体的信号
失败:-1
#include
#include
#include
void handler1(int sig)
{
pause();
}
void handler2(int sig)
{
}
int main(int argc, const char *argv[])
{
signal(SIGINT, handler1);
signal(SIGQUIT, handler2);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
【6】消息队列
(1)性质
消息队列是IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
消息队列可以按照类型来发送/接收消息
(2)函数
1 – msgget( )
#include
#include
#include
int msgget(key_t key, int msgflg);
功能:创建或者打开一个消息队列
参数:
key:键值,相同的键值确定唯一的消息队列
键值的设置方式:
第一种:任意赋值
第二种:ftok函数获取
msgflg:访问权限,
一般为 IPC_CREAT | IPC_EXCL | 0777
返回值:
成功:消息队列id
失败:-1
2 – ftok( )
#include
#include
key_t ftok(const char *pathname, int proj_id);
功能:获取键值,当前获取的键值是由文件信息和第二个参数一起决定
参数:
pathname:路径名
proj_id:任意一个值,一般为0 - 127
返回值:
成功:键值
失败:-1
3 – msgctl( )
#include
#include
#include
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:控制操作一个消息队列
参数:
msqid:消息队列id
cmd:具体的操作
IPC_STAT 获取消息队列的信息
IPC_SET 设置消息队列的信息
IPC_RMID 删除一个消息队列
buf:消息队列信息结构体
返回值:
成功:0
失败:-1
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
key_t key;
int msgid;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//printf("key = %#x\n", key);
//打开或者创建一个消息队列
if((msgid = msgget(key, IPC_CREAT | 0777)) < 0)
{
perror("fail to msgget");
exit(1);
}
//printf("msgid = %d\n", msgid);
system("ipcs -q");
#if 0
//删除一个消息队列
if(msgctl(msgid, IPC_RMID, NULL) < 0)
{
perror("fail to msgctl");
exit(1);
}
system("ipcs -q");
#endif
return 0;
}
4 – msgsnd( )
#include
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:给消息队列发送数据
参数:
msqid:消息队列id
msgp:保存要发送的数据结构体(自己定义)
struct mymsg {
long mtype; /* Message type. / 消息类型,必须执行
char mtext[1]; / Message text. */ 消息正文
}
msgsz:消息正文所占的空间大小
msgflg:标志位
0 阻塞
IPC_NOWAIT 非阻塞
返回值:
成功:0
失败:-1
5 – msgrcv( )
#include
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:从消息队列里面读取数据
参数:
msqid:消息队列id
msgp:保存接收到的数据的结构体(自己定义)
struct mymsg {
long mtype; /* Message type. / 消息类型,必须执行
char mtext[1]; / Message text. */ 消息正文
}
msgsz:消息正文所占的空间大小
msgtyp:根据设置的消息的类型接收
0 按照写入消息的顺序获取第一个消息
>0 获取第一个消息类型为当前参数的信息
<0 获取当前参数的绝对值以内消息类型最小的消息
msgflg:标志位
0 阻塞
IPC_NOWAIT 非阻塞
返回值:
成功:接收的字节数
失败:-1
#include
#include
#include
#include
#include
#define N 128
typedef struct{
long type;
char text[N];
}MSG;
#define TEXT_SIZE (sizeof(MSG) - sizeof(long))
int main(int argc, const char *argv[])
{
key_t key;
int msgid;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//打开或者创建一个消息队列
if((msgid = msgget(key, IPC_CREAT | 0777)) < 0)
{
perror("fail to msgget");
exit(1);
}
//向消息队列发送消息
MSG msg1 = {3, "3: hello world"};
MSG msg2 = {1, "1: hello kitty"};
MSG msg3 = {2, "2: nihai beijing"};
MSG msg4 = {5, "5: welcome to hqyj"};
MSG msg5 = {4, "4: bye bye"};
msgsnd(msgid, &msg1, TEXT_SIZE, 0);
msgsnd(msgid, &msg2, TEXT_SIZE, 0);
msgsnd(msgid, &msg3, TEXT_SIZE, 0);
msgsnd(msgid, &msg4, TEXT_SIZE, 0);
msgsnd(msgid, &msg5, TEXT_SIZE, 0);
system("ipcs -q");
#if 0
//删除一个消息队列
if(msgctl(msgid, IPC_RMID, NULL) < 0)
{
perror("fail to msgctl");
exit(1);
}
system("ipcs -q");
#endif
return 0;
}
#include
#include
#include
#include
#include
#define N 128
typedef struct{
long type;
char text[N];
}MSG;
#define TEXT_SIZE (sizeof(MSG) - sizeof(long))
int main(int argc, const char *argv[])
{
key_t key;
int msgid;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//打开或者创建一个消息队列
if((msgid = msgget(key, IPC_CREAT | 0777)) < 0)
{
perror("fail to msgget");
exit(1);
}
//从消息队列接收消息
//如果消息队列里面没有相应的数据,则会阻塞
MSG msg;
msgrcv(msgid, &msg, TEXT_SIZE, -2, 0);
printf("--> %s\n", msg.text);
system("ipcs -q");
#if 0
//删除一个消息队列
if(msgctl(msgid, IPC_RMID, NULL) < 0)
{
perror("fail to msgctl");
exit(1);
}
system("ipcs -q");
#endif
return 0;
}
【7】共享内存
(1)性质
共享内存是一种最为高效的进程间通信方式,进程可以直接读写物理内存,而不需要任何数据的拷贝。
为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。
进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
(2)函数
1 – shmget( )
#include
#include
int shmget(key_t key, size_t size, int shmflg);
功能:创建或者打开一个共享内存
参数:
key:键值
size:共享内存的大小
shmflg:访问权限
一般为IPC_CREAT | IPC_EXCL | 0777
返回值:
成功:共享内存的id
失败:-1
2 – shmctl( )
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制操作一个共享内存
参数:
msqid:共享内存列id
cmd:具体的操作
IPC_STAT 获取共享内存的信息
IPC_SET 设置共享内存的信息
IPC_RMID 删除一个共享内存
buf:共享内存信息结构体
返回值:
成功:0
失败:-1
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
key_t key;
int shmid;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//打开或者创建一个共享内存
if((shmid = shmget(key, 500, IPC_CREAT | 0777)) < 0)
{
perror("fail to shmget");
exit(1);
}
system("ipcs -m");
#if 0
//删除一个共享内存
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("fail to shmctl");
exit(1);
}
system("ipcs -m");
#endif
return 0;
}
3 – shmat( )
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存
参数:
shmid:共享内存id
shmaddr:获取地址的方式
NULL:系统随机分配
shmflg:权限
0 可读可写
SHM_RDONLY 只读
返回值:
成功:获取映射的首地址
失败:-1
4 – shmdt( )
#include
int shmdt(const void *shmaddr);
功能:解除共享内存的映射
参数:
shmaddr:shmat的返回值
返回值:
成功:0
失败:-1
#include
#include
#include
#include
#include
#include
typedef struct{
int a;
int b;
char c;
}MSG;
int main(int argc, const char *argv[])
{
key_t key;
int shmid;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//打开或者创建一个共享内存
if((shmid = shmget(key, 500, IPC_CREAT | 0777)) < 0)
{
perror("fail to shmget");
exit(1);
}
//映射共享内存
//char *s;
//MSG *s;
double *s;
s = shmat(shmid, NULL, 0);
//strcpy(s, "hello wrold");
//s->a = 100;
//s->b = 200;
//s->c = 'w';
while(1)
{
*s = drand48();
printf("%lf\n", *s);
}
//解除映射
shmdt(s);
system("ipcs -m");
#if 0
//删除一个共享内存
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("fail to shmctl");
exit(1);
}
system("ipcs -m");
#endif
return 0;
}
#include
#include
#include
#include
#include
#include
typedef struct{
int a;
int b;
char c;
}MSG;
int main(int argc, const char *argv[])
{
key_t key;
int shmid;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//打开或者创建一个共享内存
if((shmid = shmget(key, 500, IPC_CREAT | 0777)) < 0)
{
perror("fail to shmget");
exit(1);
}
//映射共享内存
//char *s;
//MSG *s;
double *s;
s = shmat(shmid, NULL, 0);
//printf("s = %s\n", s);
//printf("%d -- %d -- %c\n", s->a, s->b, s->c);
while(1)
{
printf("%lf\n", *s);
sleep(3);
}
//解除映射
shmdt(s);
system("ipcs -m");
#if 0
//删除一个共享内存
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("fail to shmctl");
exit(1);
}
system("ipcs -m");
#endif
return 0;
}
【8】信号灯集
(1)性质
信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程
内部不同线程间同步的机制
(2)本质
PV操作,P为减操作,V为加操作,当信号灯的值为0时,P操作会阻塞
(3)函数
1 – semget( )
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
功能:创建或者打开一个信号灯集
参数:
key:键值
nsems:信号灯的个数,至少为1
semflg:权限
一般为IPC_CREAT | IPC_EXCL | 0777
返回值:
成功:信号灯集的id
失败:-1
2 – semctl( )
#include
#include
#include
int semctl(int semid, int semnum, int cmd, ...);
功能:操作一个信号灯
参数:
semid:信号灯集的id
semnum:要操作的信号灯的编号,从0开始
cmd:具体的操作
IPC_RMID 删除一个信号灯集
SETVAL 设置信号灯的初始值(自己定义共用体)
union semun{
int val;
}
返回值:
成功:0
失败:-1
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
key_t key;
int semid;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//创建或者打开一个信号灯集
if((semid = semget(key, 3, IPC_CREAT | 0777)) < 0)
{
perror("fail to semget");
exit(1);
}
system("ipcs -s");
#if 0
//删除信号灯集
if(semctl(semid, 1, IPC_RMID) < 0)
{
perror("fail to semctl");
exit(1);
}
system("ipcs -s");
#endif
return 0;
}
3 – semop( )
#include
#include
#include
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:执行PV操作
参数:
semid:信号灯集的id
sops:要操作的信号灯的结构体数组
struct sembuf{
unsigned short sem_num; 信号灯的编号,从0开始
short sem_op; 具体操作
>0 V操作
<0 P操作
short sem_flg; 标志位
0 阻塞
IPC_NOWAIT 非阻塞
}
nsops:要操作的信号灯的个数
返回值:
成功:0
失败:-1
#include
#include
#include
#include
#include
#include
#include
union semun{
int val;
};
int main(int argc, const char *argv[])
{
key_t key;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//**************************共享内存******************************
//打开或者创建一个共享内存
int shmid;
if((shmid = shmget(key, 500, IPC_CREAT | 0777)) < 0)
{
perror("fail to shmget");
exit(1);
}
//映射共享内存
char *s;
s = shmat(shmid, NULL, 0);
//**************************信号灯集******************************
//创建或者打开一个信号灯集
int semid;
if((semid = semget(key, 1, IPC_CREAT | 0777)) < 0)
{
perror("fail to semget");
exit(1);
}
//设置信号灯的初始值
union semun myun;
myun.val = 0;
semctl(semid, 0, SETVAL, myun);
//V操作
struct sembuf buf = {0, 1, 0};
while(1)
{
fgets(s, 128, stdin);
s[strlen(s) - 1] = '\0';
//先执行的进程执行完毕后执行V操作
semop(semid, &buf, 1);
if(strncmp(s, "quit", 4) == 0)
{
shmdt(s);
exit(0);
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
union semun{
int val;
};
int main(int argc, const char *argv[])
{
key_t key;
//获取键值
if((key = ftok(".", 100)) < 0)
{
perror("fail to ftok");
exit(1);
}
//**************************共享内存******************************
//打开或者创建一个共享内存
int shmid;
if((shmid = shmget(key, 500, IPC_CREAT | 0777)) < 0)
{
perror("fail to shmget");
exit(1);
}
//映射共享内存
char *s;
s = shmat(shmid, NULL, 0);
//**************************信号灯集******************************
//创建或者打开一个信号灯集
int semid;
if((semid = semget(key, 1, IPC_CREAT | 0777)) < 0)
{
perror("fail to semget");
exit(1);
}
//设置信号灯的初始值
union semun myun;
myun.val = 0;
semctl(semid, 0, SETVAL, myun);
//P操作
struct sembuf buf = {0, -1, 0};
while(1)
{
//后执行的进程执行P操作
semop(semid, &buf, 1);
if(strncmp(s, "quit", 4) == 0)
{
shmdt(s);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
exit(0);
}
else
{
printf("--> %s\n", s);
}
}
return 0;
}