IPC是各种进程通信方式的统称。IPC 单机形式有管道(无名管道 和 命名管道(FIFO))、消息队列、共享内存、信号、信号量。多机形式有socket、streams。其中Socket和Streams支持不同主机上的两个进程IPC。
一、管道
管道,通常指无名管道,是UNIX系统IPC最古老的形式。
1.无名管道
1.1特点
1、工作方式一般使用半双工(即数据只能在一个方向上流动),保留一组读写端(只能读或写,读的时候要把写关上,反之一样)。
2、使用只限于具有亲缘关系(也是父子进程或兄弟进程之间)的进程之间。
3、使用管道可以理解为一种特殊的文件,可以使用read、write等函数进行读写(lseek不可用), 但它不是普通文件,存在于内存中
4、数据被读取后就消失(类似于水管),不可进行二次读取
1.2管道工作原理:
1.3函数原型
int pipe(int pipefd[2]);
参数说明:
pipe创建一个管道,会生成两个管道描述符:
pipefd[0]:从管道进行读取数据。
pipefd[1]:向管道进行写入数据。
返回值:成功返回0,失败返回-1。
1.4编程实现
读取数据时要先关闭写,写入数据时要先关闭读,只能单向。
int main()
{
// int pipe(int pipefd[2]);
int fd[2];
if(pipe(fd)==-1)
{
printf("create pipe failed\n");
}
int pid;
char readbuff[128]={0};
char readbuff2[128]={0};
pid=fork();
if(pid<0)
{
printf("create child process\n");
}
else if(pid==0)
{
printf("this is child\n");
close(fd[1]);
read(fd[0],readbuff,sizeof(readbuff));
read(fd[0],readbuff2,sizeof(readbuff2));
close(fd[0]);
printf("from father process:%s,2:%s\n",readbuff,readbuff2);
exit(0);
}
else{
sleep(3);
printf("this is father\n");
close(fd[0]);
write(fd[1],"hello this is father",strlen("hello this is father"));
close(fd[1]);
wait(NULL);
exit(0);
}
return 0;
}
运行结果:
从运行结果看,数据被读取后就消失(类似于水管),不可进行二次读取。
2.命名管道(有名管道)
命名管道也被称为FIFO文件,是一种特殊的文件。由于linux所有的事物都可以被视为文件,所以对命名管道的使用也就变得与文件操作非常统一,可以用open、write、read等函数操作。
2.1特点:
1、FIFO可以在无关进程之间交换数据,与无名管道不同;
2、FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2.2函数原型:
查看man手册:
man 3 mkfifo
int mkfifo(const char *pathname, mode_t mode);
参数说明:
pathname:创建管道的路径文件名;
mode:管道创建的模式,与open函数中的mode一样。
返回值:执行成功时,返回0,失败则返回-1。
当open打开一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
若没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开此FIFO文件。类似的,只写open要阻塞到某个其他进程为读而打开它。
若指定了O_NONBLOCK,则只读open立即返回。而只写open将出错返回-1,如果没有进程已经为读而打开该FIFO文件,其中errno置ENXIO
2.3编程实现
先测试没有指定O_NONBLOCK,代码如下:
mkfifo.c文件
int main()
{
if(mkfifo("./fifo.txt",0600)==-1 && errno!=EEXIST)
{
printf("mkfifo failed\n");
perror("why");
}
int fd=open("./fifo.txt",O_RDONLY); //只读
printf("open success\n");
close(fd);
return 0;
}
运行该文件会出现阻塞,因为用只读open打开此fifo文件,需其他进程用写来打开此fifo文件才不会阻塞。
fifowrite.c
int main()
{
int fd=open("./fifo.txt",O_WRONLY);
if(fd==-1)
{
printf("open failed\n");
exit(0);
}
printf("open success\n");
close(fd);
return 0;
}
先运行mkfifo.c文件,再打开一个终端去运行fifowrite.c文件。
运行结果:
fifowrite.c文件是没有发生阻塞的,因为先运行mkfifo.c,open是指定只读的。
2.4实现两个进程通信
在上面代码的基础上进行改进。
read.c
int main()
{
char readbuff[128]={0};
if(mkfifo("./fifo.txt",0600)==-1 && errno!=EEXIST)
{
printf("mkfifo failed\n");
perror("why");
}
int fd=open("./fifo.txt",O_RDONLY);
if(fd==-1)
{
printf("open failed\n");
exit(0);
}
int nread=read(fd,readbuff,sizeof(readbuff));
printf("read %d byte,content:%s\n",nread,readbuff);
close(fd);
return 0;
}
write.c
int main()
{
int fd=open("./fifo.txt",O_WRONLY);
if(fd==-1)
{
printf("open failed\n");
exit(0);
}
write(fd,"hello,my from write",strlen("hello,my from write"));
printf("write success\n");
close(fd);
return 0;
}
运行结果:
不能使用lseek函数,可以试一下。
指定O_NONBLOCK,以O_RDONLY为例,在O_RDONLY | O_NONBLOCK,可以自己进行测试。
如果open用可读可写的方式打开此FIFO文件,则不会阻塞。
以write.c为例
int main()
{
int fd=open("./fifo.txt",O_RDWR);
if(fd==-1)
{
printf("open failed\n");
exit(0);
}
//write(fd,"hello,my from write",strlen("hello,my from write"));
printf("write success\n");
close(fd);
return 0;
}
运行结果:
最后说明一下,无论是无名管道还是命名管道,数据读取后都会消失。
二、消息队列
消息队列:是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
2.1、特点
1、消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级;
2、消息队列独立于发送与接受进程,进程终止时,消息队列不会被删除,但消息内容读取完之后,内容(消息)会被删除;
3、消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2.2工作原理
2.3函数原型
//创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int msgflg);
//添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//读取消息:成功返回消息数据的长度,失败返回-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
//控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数说明:
在以下两种情况下,msgget将创建一个新的消息队列:
(1).如果没有与键值key相对应的消息对列,并且flag中包含了IPC_CREAT标志位。
(2).key参数为IPC_PRIVATE。
补充一下:键值不一样,msgget函数返回值ID号不一样。ID号从0开始。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
(1).type==0,返回队列中的第一个消息;
(2).type>0,返回队列中消息类型为type的第一个消息;
(3).type<0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,则取类型值最小的消息。
从这可以看出,type值非0时用于以非先进先出次序读消息。也可以把type看作优先级的权值。
msgid:msgget函数返回值;
msgflg:默认值为0。
2.4编程实现收发数据
msgread.c
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
int msgid;
struct msgbuf readbuff;
struct msgbuf buff={98,"my rcv!"};
msgid=msgget(0x1234,IPC_CREAT|0777);
printf("msgid=%d\n",msgid);
if(msgid==-1)
{
printf("get queue fail\n");
}
msgrcv(msgid,&readbuff,sizeof(readbuff.mtext),888,0);
msgsnd(msgid,&buff,strlen(buff.mtext),0);
msgctl(msgid,IPC_RMID,NULL);//删除消息队列
printf("read from you:%s\n",readbuff.mtext);
return 0;
}
msgSend.c
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
int msgid;
struct msgbuf buff={888,"hello my from msgSend"};
struct msgbuf readbuff;
msgid=msgget(0x1234,0);
printf("msgid=%d\n",msgid);
if(msgid==-1)
{
printf("get queue fail\n");
}
msgsnd(msgid,&buff,strlen(buff.mtext),0);
printf("send success\n");
msgrcv(msgid,&readbuff,sizeof(readbuff.mtext),98,0);
printf("read from you:%s\n",readbuff.mtext);
return 0;
}
运行结果:
键值key是直接给定的,key键值也可以用ftok函数进行获取。
2.5 ftok函数
系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
函数原型:
key_t ftok( const char * fname, int id )
参数说明:
//key通过ftok函数获得
key_t key;
key=ftok(".",2);
printf("key=%x\n",key);
msgid=msgget(key,IPC_CREAT|0777);
临界区:每个进程中访问临界资源的代码称为临界区。
3.2函数原型
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int shmflg);
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
//断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(const void *shmaddr);
//控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数说明:
shmget函数中参数:
key:通过ftok函数获得;
size:创建共享内存的大小,必须写;
shmflg:和消息对列msgflag用法一样;
shmat函数中参数:
shmid:shmget函数返回的内存id;
shmaddr:设置值为NULL,系统会选择适合的页地址去绑定这段地址;
shmflg:如果shmaddr不设置NULL,需设置shmflag,否则设置值为0;
shmdt函数中参数:
shmaddr:shmat函数返回的内存地址。
3.3编程实现共享内存数据的收发
shmw.c
int main()
{
int shmid;
char *shmaddr;
key_t key;
key=ftok(".",2);
shmid=shmget(key,1024*4,IPC_CREAT|0666);
if(shmid==-1)
{
printf("shmget fail\n");
exit(0);
}
shmaddr=shmat(shmid,NULL,0);
strcpy(shmaddr,"my de name!");
shmdt(shmaddr);
printf("quit\n");
return 0;
}
shmr.c
int main()
{
int shmid;
char *shmaddr;
key_t key;
key=ftok(".",2);
shmid=shmget(key,1024*4,0);
if(shmid==-1)
{
printf("shmget fail\n");
exit(0);
}
shmaddr=shmat(shmid,NULL,0);
printf("shmat ok\n");
printf("datas:%s\n",shmaddr);
shmdt(shmaddr);
shmctl(shmid,IPC_RMID,0); //读取数据后进行删除共享内存
printf("quit\n");
return 0;
}
运行结果:
读取数据:
写入:
可以通过ipcs -m查看共享内存,ipcrm -m shmid可以删除共享内存。
每次向共享内存写入新的数据时,原先的数据会被清除掉,但共享内存中数据在读取之后仍然存在除非共享内存删除。
四、信号
对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。
4.1概述
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGQUIT ”、“SIGKILL”、“SIGINT”等等。
signal.h
头文件中,信号名都定义为正整数。具体的信号名称可以使用kill -l
来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0y有特殊的应用。2.信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作。
忽略信号:大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是
SIGKILL
和SIGSTOP
)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。捕捉信号:需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作:对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。具体的信号默认动作可以使用
man 7 signal
来查看系统的具体定义。
常用在终端使用kill命令进行终止进程,如kill -9 进程ID 或kill -SIGKILL 进程ID。
入门版:函数 signal
高级版:函数 sigaction
4.信号处理函数的发送
信号发送函数也不止一个,同样分为入门版和高级版
1.入门版:kill
2.高级版:sigqueue
4.2入门版 :signal函数:
函数原型:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("get signum:%d\n",signum);
switch(signum)
{
case 2:
printf("SIGINT\n");break; //就是ctrl+c 产生的
case 9:
printf("SIGKILL");break;
case 10:
printf("SIGUSR1\n");break;
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);
return 0;
}
发送信号有两种:一种是在终端输入kill命令,另一种通过发送函数kill进行发送命令。
运行结果
方式-:
方式二,kill函数:
函数原型:
//成功执行返回0,失败返回-1
int kill(pid_t pid, int sig);
参数说明:
pid:接受信号进程的pid号。
sig:发送接受信号值。
sigkill.c
int main(int argc,char **argv)
{
int signum;
int pid;
if(argc!=3)
{
printf("param error\n");
exit(0);
}
signum=atoi(argv[1]);
pid=atoi(argv[2]);
printf("signum=%d,pid=%d\n",signum,pid);
kill(pid,signum);
return 0;
}
对于已注册的信号,使用 kill 发送都可以正常接收到,但是如果发送了未注册的信号,则会使得应用程序终止进程。
4.4高级版:sigaction函数
我们已经实现信号的收发,总感觉缺少点什么。所以要使用高级版的,在发送信号的同时携带一些数据。
函数原型:
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
参数说明:
signum:等待信号产生的信号编号。
act:是一个结构体类型,如果不为空说明需要对该信号有新的配置。
oldact:如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
struct sigaction结构体内容如下:
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_flags设置为SA_SIGINFO,说明了信号处理程序带有附加信息,也就是会调用 void (*sa_sigaction)(int, siginfo_t *, void *);。否则,系统会默认使用 void (*sa_handler)(int);。在此,还要特别说明一下,sa_sigaction 和 sa_handler 使用的是同一块内存空间,相当于 union,所以只能设置其中的一个,不能两个都同时设置。
siginfo_t 是一个结构体,说明一下:
//信号处理函数
void handler(int sig, siginfo_t *info, void *ucontext)
{
...
}
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
union sigval si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count;
POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_lower; /* Lower bound when address violation
occurred (since Linux 3.19) */
void *si_upper; /* Upper bound when address violation
occurred (since Linux 3.19) */
int si_pkey; /* Protection key on PTE that caused
fault (since Linux 4.6) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
}
可以看到结构体成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
关于发送来的数据,保存在si_int和*si_ptr中,和si_value成员中sival_int和*sival_ptr保存的数据是一样。
在接收数据时,ucontext要保证是非空的,然后打印数据。
union sigval结构体
union sigval {
int sival_int;
void *sival_ptr;
};
4.5信号发送函数高级版:sigqueue函数
函数原型:
//成功执行返回0,失败返回-1
int sigqueue(pid_t pid, int sig, const union sigval value);
参数说明:
pid:注册信号进程的pid号。
sig:所要发送的信号编号。
value:发送信号时所要携带的数据。
4.6编程实现高级版函数信号的发送与接受
接受信号sigaction.c
//int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
void handler(int signum,siginfo_t *info,void *context)
{
printf("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 process pid;%d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("my pid=%d\n",getpid());
act.sa_sigaction=handler;
act.sa_flags=SA_SIGINFO;//sa_sigaction should be set instead of sa_handler
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
发送信号sigqueue.c
int main(int argc,char **argv)
{
int signum;
int pid;
union sigval value;
if(argc!=3)
{
printf("param error\n");
exit(0);
}
signum=atoi(argv[1]); //字符转整型
pid=atoi(argv[2]);
printf("signum=%d,pid=%d\n",signum,pid);
value.sival_int=100;
sigqueue(pid,signum,value);
return 0;
}
运行结果:
通过结果可以看到,si_int中的数据和si_value成员中sival_int数据保存是一样的。
五、信号量
5.1概述
信号量(Semaphore)是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据。
信号量是由操作系统来维护的,用户进程只能通过初始化和两个标准原语(P、V原语)来访问。信号量初始化可指定一个非负整数,及空闲资源总数。
P原语:P是荷兰语Proberen(测试)的首字母。为阻塞原语,负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞。
V原语:V是荷兰语Verhogen(增加)的首字母。为唤醒原语,负责把一个被阻塞的进程唤醒。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒。
5.2特点
1.信号量用于进程间同步,若要在进程间传递数据需要结合共享内存;
2.信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作;
3.每次对信号量的PV操作不仅限于对信号量值加1或减1,而且可以加减任意正整数;
4.支持信号量组。
5.3函数原型
//创建或获取一个信号量集,成功执行返回对应ID号,失败则返回-1
int semget(key_t key, int nsems, int semflg);
//控制信号量的相关信息,成功执行返回一个非负整数值,失败返回-1
int semctl(int semid, int semnum, int cmd, ...);
//改变信号量的值操作函数,成功执行返回0,失败返回-1
int semop(int semid, struct sembuf *sops, size_t nsops);
参数说明:
semget函数:
key:通过ftok函数获取的键值;
nsems:一个信号量集中信号量的个数;
semflg:和消息队列中msgget函数中msgflg参数用法一样。
semctl函数:
semid:semget函数返回的ID号;
semnum:信号量集中对信号量进行编号,一般信号量从0开始编号;
这个函数可以有3个或4个参数,取决于cmd。当有4个参数时,这第4个参数必须指定union semun这个类型,而且必须在程序中定义这个结构体。结构体如下:
union semun {
int val; /* Value for SETVAL */ //设置初始信号量值
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
在本次编程实验中只使用了结构体第一个参数val来设置初始信号量值,使用第一个参数,cmd指定为SETVAL。
semop函数:
semid:semget函数返回的ID号;
sops:是一个struct sembuf结构体类型指针变量,一般发送多个数据写为数组;
nsops:需要发送的sops数组中元素个数。
struct sembuf结构体包含的成员如下:
unsigned short sem_num; /* semaphore number */ //信号量编号
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
sem_num:信号量编号。
在信号量集中,第一个信号量编号为0,sem_op可以有三种值:
sem_op==0:进程必须对信号量集具有读取权限。这是一个“等待零”操作:如果semval为零,则操作可以立即继续(不发生阻塞等待)。否则,如果在sem_flg中指定了IPC_NOWAIT,semop()失败。否则,进程就会出现阻塞等待,直到semval变为0。
sem_op>0:该操作将此值添加到信号量值(semval)中。此外,如果为此操作指定了SEM_UNDO,系统将从该信号量的信号调整(semadj)值中减去值sem_op。
sem_op<0:如果semval值大于或等于这个sem_op绝对值,这个操作能立即继续:从semval中减去semop的绝对值。如果为此操作指定了SEM_UNDO,则系统会将sem_op的绝对值添加到此信号量的信号量调整(semadj)值中。否则如果在sem_flg中指定了IPC_NOWAIT,semop()失败。否则就阻塞等待,直到semval值大于或等于这个sem_op绝对值。
简单的记一下:
sem_op==0:如果semval为零,当前进程不再等待,也就是申请到了一个空间资源;
sem_op>0:释放空间资源;
sem_op<0:申请空间资源。
sem_flg可以指定两个宏:IPC_NOWAIT和SEM_UNDO。如果设置为SEM_UNDO,当进程退出时将会立即取消操作。
5.4编程实现信号量PV操作
// int semget(key_t key, int nsems, int semflg);
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void PGetKey(int id)
{
struct sembuf set;
set.sem_num=0;
set.sem_op=-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;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("VPut back the key\n");
}
int main()
{
key_t key;
int semid;
key=ftok(".",1);
semid=semget(key,1,IPC_CREAT|0666);
union semun initsem;
initsem.val=0;
semctl(semid,0,SETVAL,initsem);
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 fail\n");
}
return 0;
}
运行结果:
可通过ipcs命令查看消息队列、共享内存、信号量。ipcs -s可以直接查看信号量。
5.5进一步实现PV操作一直运行,并且保证子进程优先运行
比上面代码多了PInitKey函数,main有所改变。
void PInitKey(int id)
{
struct sembuf set1;
int sd;
set1.sem_num=0;
set1.sem_op=0;
set1.sem_flg=SEM_UNDO;
sd=semop(id,&set1,1);
if(sd==-1)
{
printf("semop error\n");
exit(0);
}
printf("getInitkey\n");
}
int main()
{
key_t key;
int semid;
key=ftok(".",1);
semid=semget(key,1,IPC_CREAT|0666);
union semun initsem;
initsem.val=0;
semctl(semid,0,SETVAL,initsem);
int pid=fork();
if(pid>0)
{
while(1)
{
PGetKey(semid);
printf("this is father\n");
VPutKey(semid);
semctl(semid,0,SETVAL,initsem);
sleep(2);
}
}
else if(pid==0)
{
while(1)
{
PInitKey(semid);
printf("initsem.val=%d\n",initsem.val);
printf("this is child\n");
VPutKey(semid);
sleep(2);
}
}
else
{
printf(" fork fail\n");
}
return 0;
}
运行结果:
六、生产者——消费者例子
生产者在缓冲区生产一个件商品,通知消费者从缓冲区消耗一件商品,消费者消耗完,通知生产者生产下一个商品。
使用共享内存充当缓冲区,放入数据。
通过消息队列来通知生产者和消费者。
利用信号量控制着进程同步。
代码如下:
producer.c
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
//P 申请一个空间资源
void PGetSem(int id)
{
struct sembuf set1;
int sd;
set1.sem_num=0;
set1.sem_op=-1;
set1.sem_flg=SEM_UNDO;
sd=semop(id,&set1,1);
if(sd==-1)
{
printf("semop error\n");
exit(0);
}
printf("PGet\n");
}
//V 释放一个空间资源
void VPutSem(int id)
{
struct sembuf set;
set.sem_num=0;
set.sem_op=1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("VPut\n");
}
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key;
int semid,shmid,msgid;
union semun initsem;
struct msgbuf rbuff;
struct msgbuf buff;
char *shmaddr;
char cmd;
char flag=1;
key=ftok(".",3);
initsem.val=1; //设置信号量初始值
buff.mtype=10;
//创建消息队列
msgid=msgget(key,IPC_CREAT|0666);
if(msgid==-1)
{
printf("create msgQueue error\n");
exit(0);
}
//创建共享内存
shmid=shmget(key,1024*2,IPC_CREAT|0666);
if(shmid==-1)
{
printf("create shm error\n");
exit(0);
}
//创建信号量集
semid=semget(key,1,IPC_CREAT|0666);
if(semid==-1)
{
printf("create sem error\n");
exit(0);
}
//控制信号量
semctl(semid,0,SETVAL,initsem);
while(flag)
{
printf("Please input a cmd:");
scanf("%c",&cmd);
getchar();
printf("\n");
buff.mtext[0]=cmd;
switch(cmd)
{
case 'r':
PGetSem(semid); //P操作
shmaddr=shmat(shmid,NULL,0); //往缓冲区生产一个商品
strcpy(shmaddr,"I produce one thing!");
printf("Write OK\n");
VPutSem(semid); //V操作
//通知消费者消耗该商品
msgsnd(msgid,&buff,sizeof(struct msgbuf),0);
printf("msg send OK\n");
//等待消费者消耗该商品
msgrcv(msgid,&rbuff,sizeof(struct msgbuf),20,0);
printf("msg from consumer:%ld,%s\n",rbuff.mtype,rbuff.mtext);
printf("msg read OK\n");
memset(rbuff.mtext,0,128);
printf("\n\n");break;
case 'q':
msgsnd(msgid,&buff,sizeof(struct msgbuf),0);
printf("quit\n");
flag=0;break;
default:
printf("input error\n");
}
}
//断开与共享内存连接
shmdt(shmaddr);
//删除消息队列、共享内存、信号量
msgctl(msgid,IPC_RMID,NULL);
shmctl(shmid,IPC_RMID,NULL);
semctl(semid,0,IPC_RMID,NULL);
return 0;
}
consumer.c
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
//P 申请一个空间资源
void PGetSem(int id)
{
struct sembuf set1;
int sd;
set1.sem_num=0;
set1.sem_op=-1;
set1.sem_flg=SEM_UNDO;
sd=semop(id,&set1,1);
if(sd==-1)
{
printf("semop error\n");
exit(0);
}
printf("PGet\n");
}
//V 释放一个空间资源
void VPutSem(int id)
{
struct sembuf set;
set.sem_num=0;
set.sem_op=1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("VPut\n");
}
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key;
int semid,shmid,msgid;
struct msgbuf rbuff;
struct msgbuf wbuff={20,"I have taked"};
char *shmaddr;
key=ftok(".",3);
//获取消息队列
msgid=msgget(key,0);
if(msgid==-1)
{
printf("create msgQueue error\n");
exit(0);
}
//获取共享内存
shmid=shmget(key,1024*2,0);
if(shmid==-1)
{
printf("create shm error\n");
exit(0);
}
//获取信号量集
semid=semget(key,1,0);
if(semid==-1)
{
printf("create sem error\n");
exit(0);
}
while(1)
{
//接收生产者发来的通知消息
msgrcv(msgid,&rbuff,sizeof(struct msgbuf),10,0);
if(rbuff.mtext[0]=='r') //消耗商品
{
PGetSem(semid); //P操作
shmaddr=shmat(shmid,NULL,0);//拿走商品
printf("data from producer:%s\n",shmaddr);
printf("data read OK\n");
VPutSem(semid); //V操作
//通知生产者缓冲区商品已拿走,生产下一个商品
msgsnd(msgid,&wbuff,sizeof(struct msgbuf),0);
printf("msg send OK\n");
printf("\n\n");
}
if(rbuff.mtext[0]=='q') //退出
{
printf("quit\n");
break;
}
memset(rbuff.mtext,0,128);
}
//断开与共享内存的连接
shmdt(shmaddr);
return 0;
}