IPC 进程间的通信
无名管道 有名管道 两个人在一张桌子上
男的放入纸条 女的拿走纸条
或者两者相反
消息队列 男的放入纸条 女的读取纸条 不拿走纸条
或者女的放入纸条,男的读取纸条 不拿走纸条
共享内存 桌子上面有一个纸条 男的写了纸条的信息以后女生直接可以看
见
面向同一个内存空间两者
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。
一、管道
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
1、特点:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
原型
1 #include <unistd.h>
2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
if(pipe(fd)<0){
printf("管道创建失败\n");
}
半双工
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。
**
一、 无名管道
读取完成后管道就会消失不见
**
要关闭管道只需将这两个文件描述符关闭即可。
fd[0]读 fd[1]写
使用读的时候就要把写关闭掉
close(fd[0]); //关闭了读 表示你要读取信息
read(fd[1],);
使用写就要把读关掉
close(fd[1]); //表示关闭了写端 使用读端
1、例子
int main()
{
int fd[2]; //定义数组 fd[0]表示读打开 fd[1]表示写打开
char buf[20]={0}; //创建读取的空间
pid_t pid; //定义一个进程描述符
if(pipe(fd)<0){ //加入pipe创建失败 返回值是-1
printf("creat pipe defeat\n");
}
pid=fork(); //创建父子进程
if(pid<0){ //pid<0 表示父子进程创建失败
printf("defeat\n");
}else if(pid>0){ //表示父进程
sleep(3);//先让父进程睡上三秒
close(fd[0]); //关闭读文件描述符
write(fd[1],"hello world",20); //写入hello world 给管道
wait(NULL); //防止子进程变成僵尸进程
}else{
close(fd[1]); //关闭写文件描述符
read(fd[0],buf,20); //read 如果没有读取到buf里面的东西就会堵塞
//读取fd[0]传过来的信息 存放在buf里面
printf("pipe %s\n",buf); //显示buf里面传递过来的信息
exit(0);
}
return 0;
}
二、FIFO
FIFO,也称为命名管道,它是一种文件类型。
1、特点
FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2、函数原型
1 #include <sys/stat.h>
2 // 返回值:成功返回0,出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
若没有指定O_NONBLOCK(默认),只读 open 要阻塞,等到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
3、errno函数
errno函数
系统调用的错误都会存放在errno中
EEXIST 是个宏定义,是Error EXIST 的缩写,EXIST 是“存在”的意思。
errno是个全局变量,在errno.h头文件中定义,用于保存错误码,方便根据错误码来查询出错原因。
这个mkfifo函数创建管道时,如果有已存在的同名管道,就会将errno赋值为EEXIST。
erron != EEXIST,也就是判断mkfifo函数是否出现“已存在同名管道”的错误。
4、创建管道例子
使用if(mkfifo("./file",0600) ==-1 && errno !=EEXIST)
表示创建失败 两者都非零 才能创建成功
出现错误提示显示EEXIST 不管 其他错误显示打印失败
int main()
{
// int mkfifo(const char *pathname, mode_t mode);
if(mkfifo("./file",0600)==-1 &&errno!=EEXIST){
//返回出错表示返回-1 并且错误提示不是存在EEXIST
//创建file这个管道
printf("mkfifo defeat\n");
perror("why");
}
return 0;
}
使用mkfifo 进行读写
读的函数
int main()
{
// int mkfifo(const char *pathname, mode_t mode);
int nread;
char buf[30]={0};
if(mkfifo("./file",0600)==-1&&errno!=EEXIST){
printf("mkfifo defeat\n");
perror("why");
}
int fd=open("./file",O_RDONLY); //设置成只读打开
while(1){
nread=read(fd,buf,30);
printf("read %d buf=%s\n",nread,buf);
}
close(fd);
return 0;
}
写函数
int main()
{
int fd;
char *buf="hello world";
fd=open("./file",O_WRONLY); //需要打开文件时只写文件打开
while(1){
write(fd,buf,strlen(buf));
sleep(1);
}
close(fd);
return ;
}
三、消息队列 msg
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
1、特点
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
而是通过liunx内核统一管理
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2、原型
函数原型
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
msgget(key,IPC_CREAT|0777)
4 // 添加消息:成功返回0,失败返回-1
其中size表示的发送的内容的大小
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
其中size表示的读取的内容的大小
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
msgrcv(msgid,&msg, 1,888,0); 消息队列号888
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl(msgid, IPC_RMID,NULL);
在以下两种情况下,msgget将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,
并且flag中包含了IPC_CREAT标志位。
3、例子
消息队列进行接收和发送的情况下,
需要创建两个结构体分别用于存储接受到的数据和发送的数据
发送接收函数的代码块
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf //结构体发送和接受函数使用
{
long mtype; //表示创建的队列号
char mtext[128]; //表示要传输的信息
};
int main()
{
// int msgget(key_t key, int msgflg);
//定义发送的结构体为:
struct msgbuf sendbuf={888,"你好帅,哈哈哈"};
//定义接受的结构体 进行清零操作
struct msgbuf rcvbuf={
.mtext[128]={'\0'}
};
int msgId=msgget(0x1234,IPC_CREAT|0777);//0777可参照权限
if(msgId==-1){
printf("消息队列创建失败\n");
}
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//第三个参数使用字符串统计长度函数
msgsnd(msgid,&sendbuf,strlen(sendbuf.mtext),0);//默认写0flog
//使用sizeof函数
msgrcv(msgId,&rcvbuf,sizeof(rcvbuf.mtext),889,0); //读取消息队列号889
//要和接受的消息队列号一致,才可以接受到
printf("你收到的是 %s",rcvbuf.mtext);
return 0;
}
接受发送函数
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
// int msgget(key_t key, int msgflg);
struct msgbuf sendbuf={889,"谢谢,你也很帅"};
struct msgbuf rcvbuf;
int msgId=msgget(0x1234,IPC_CREAT|0777);//0777可参照权限
if(msgId==-1){
printf("消息队列创建失败\n");
}
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
// int msgflg); msgtyp 是接受的人的消息队列号
msgrcv(msgId,&rcvbuf,sizeof(rcvbuf.mtext),888,0);
printf("你接收到的是 %s",rcvbuf.mtext);
msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);//默认写0flog
return 0;
}
使用消息队列的时候通常使用key,而不是直接确定
4、ftok函数
ftok函数
系统建立IPC通讯(消息队列、信号量和共享内存)时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
如下:
key_t ftok( char * fname, int id )
ftok(".",1);
// .在当前目录
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
这样就是将fname设为当前目录。
id是子序号。
key_t key;
key = ftok(".", 1); 这样就是将fname设为 当前目录。
id是子序号。虽然是int类型,但是只使用8bits(1-255)。
**成功**后,将返回生成的key_t值。**失败**
返回 -1,其中errno指示与stat(2)系统有关的错误
呼叫。
在一般的UNIX实现中,是将文件的 索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的 索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002
key_t key;
key=ftok(".",1);
if(key==-1){
perror("why");
printf("创建key失败\n");
}
5、msgctl函数
使用完成消息队列后移除消息队列的链表
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl(msgid,IPC_RMID,NULL);
第一个参数msgqid 是消息队列对象的标识符。
第二个参数是函数要对消息队列进行的操作,它可以是:
IPC_STAT:取出系统保存的消息队列的msqid_ds 数据,并将其存入参数buf 指向的msqid_ds 结构中。
IPC_SET:设定消息队列的msqid_ds 数据中的msg_perm 成员。设定的值由buf 指向的msqid_ds结构给出。
**IPC_RMID:**将队列从系统内核中删除。 使用的多 一般为NULL
这三个命令的功能都是明显的,所以就不多解释了。唯一需要强调的是在IPC_STAT命令中队列的msqid_ds 数据中唯一能被设定的只msg_perm 成员,其是ipc_perm 类型的数据。而ipc_perm 中能被修改的只有mode,pid 和uid 成员。其他的都是只能由系统来设定的。`
四、共享内存 shm
创建共享内存的过程
1.生成子序列号key=ftok(".",1);
2.创建生成共享内存
shmid=shmget(key,10244,IPC_CREAT|0666);//写函数
shmid=shmget(key,10244,0); //读函数
3.连接到共享内存空间上面进行映射
char *pshmat=shmat(shmid,0,0);
4.断开和共享内存的连接
shmdt(pshmat);
5.关闭共性内存
shmctl(shmid);
1、函数原型
size_t size 创建的空间是以 m 为最小单位的
1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
写shmget(key,1024*4,IPC_CREAT|0666);
读shmget(key,1024*4,0);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
pshmat=shmat(shmid,0,0);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr);
shmdt(pshmat);
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmctl(shmid,IPC_RMID,NULL);
2、写函数 的代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
key_t key;
int shmid;
char *shmatp;
key=ftok(".",2);
if(key==-1){
perror("why");
printf("key chuangjianshibai\n");
}
shmid=shmget(key,1024*4,IPC_CREAT|0666);
if(shmid==-1){
printf("chuangjianshibai\n");
exit(-1);
}
shmatp=shmat(shmid,0,0);
printf("映射创建完成\n");
strcpy(shmatp,"纸片上写的是一个帅哥");//往映射中写入
sleep(5);//延时5秒后,关闭映射内存
shmdt(shmatp);
shmctl(shmid,IPC_RMID,NULL);
printf("关闭完成\n");
return 0;
}
读函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
key_t key;
int shmid;
char *shmatp;
key=ftok(".",2);//创建id号
if(key==-1){
perror("why");
printf("key chuangjianshibai\n");
}
shmid=shmget(key,1024*4,0);//这里的0表示进行读取
if(shmid==-1){
printf("chuangjianshibai\n");
exit(-1);
}
shmatp=shmat(shmid,0,0);
printf("映射完成\n");
printf("看到的纸条上面的信息是%s\n",shmatp);
shmdt(shmatp);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
使用共享内存如果不对信号加以控制,两方都读内存的内容和写内存里面的内容,会造成,不知道内存里面是谁写入的需要进行人为的控制,当一个人在写的时候,只能读,当那个人发送完成之后,停止。
五、Linux信号
信号其实我们也见过,当我们在shell上写出一个死循环退不出来的时候,只需要一个组合键,ctrl+c,就可以解决了,这就是一个信号,但是真正的过程并不是那么简单的
信号是从1开始 的 0不被征用
kill对信号0有特殊的应用
在bash上执行命令kill -l便可看到系统定义的所有信号
忽略信号
捕捉信号处理,即用户自定义的信号处理函数来处理。
采用系统默认处理SIG_DFL,执行缺省操作
忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。
ps -aux|grep a.out
使用 kill -9 PID 杀死进程
pid 表示的是进程
1、signal函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
sianal(SIGINT,handler);
例子
使用信号 让ctrl C 无法杀死该进程
过程:1.使用signal(信号名,handler);
2.创建函数handler
void handler(int signum)
3. 结尾使用while(1);死循环
#include <stdio.h>
#include <signal.h>
/*
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
第一个参数signum:指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号
*/
void handler(int signum) //判断信号的函数
{
printf("get signum=%d\n",signum); //得到信号的信号类型
switch(signum){
case 2: //判断信号类型是否为2
printf("SIGINT\n");
break;
case 10:
printf("SIGUSR1\n");
break;
}
printf("butui chu \n");
}
int main()
{
signal(SIGINT,handler);//信号的输出
signal(SIGUSR1,handler);//
while(1);
return 0;
}
signal是一个函数:
1.它返回一个函数指针void (*p) (int)
2.它的两个参数是一个是int和一个函数指针void (*p) (int)
中间部分 :
signal( int sig, void (*func)(int))
signal函数有2个参数,第一个是int,第二个是无返回值,带一个int参数的函数指针
外围:
void (*signal(xxx)) (int)
signal函数返回的是一个函数指针,无返回值,有一个int参数
下面用typedef进行简化:
typedef void(*ptr_to_func)(int);
ptr_to_func signal(int,ptr_to_func);
杀死进程 使用软件编码
函数原型 函数头文件
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
kill(pid,signum); //进程号 和消息号
编程过程:
1.使用main函数传参 把传递进来的信号和pid号通过函数atoi把字符串转换成int型
2.使用kill发送进程号 信息号
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
int main(int argr,char*argv[])
{
int signum;
int pid;
signum=atoi(argv[1]);ascII码转换 把字符串变成int型
pid=atoi(argv[2]);
printf("num=%d, pid=%d\n",signum,pid);
kill(pid,signum);//进程号,信息号
return 0;
}
使用system 调用脚本文件
sprintf的作用是将一个格式化的字符串输出到一个目的字符串中,而printf是将一个格式化的字符串输出到屏幕。
sprintf的第一个参数是目的字符串,如果不指定这个参数,执行过程中出现 "该程序产生非法操作,即将被关闭…"的提示。
char cmd[128]={0};
//输出连接 把kill -%d %d 放到cmd中
sprintf(cmd,"kill -%d %d",signum,pid);
system(cmd);
高级传递信号编程
信号安装函数 sigaction
包含头文件<signal.h>
功能:sigaction函数用于改变进程接收到特定信号后的行为。
原型: int sigaction(int signum,const struct sigaction *act,const struct sigaction *old);
1、该函数的第一个参数为信号的值,可以为除sigkill及sigstop外的任何一 个特定有效的信号
2、第二个参数是指向结构sigaction的一个实例的指针
3、第三个参数oldact指向的对象用来保存原来对相应信号的处理,一般为NULL
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。
结构体
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一样 表示的是没有参数传递的函数,如果不使用可以不写
结构体的第二个参数很重要
第一个参数为信号值,第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,第三个参数表示有无数据 如果有数据,不为NULL,否则为NULL
2. sa_mask存放需要手动屏蔽的信号
默认情况下为阻塞进程 可以不写
3.sa_flags指定一组修改信号行为的标志
flag在man文档中有详细列出,常用的有:
SA_SIGINFO:实际上,可以把该标志位看成信号是否传递参数的开关,如果设置该位,则传递参数;否则,不传递参数。
下面为siginfo的结构体
The siginfo_t argument to sa_sigaction is a struct with the following fields:
siginfo_t {
int si_signo; /* Signal number 信号编号 */
int si_errno; /* An errno value 如果为非零值则错误代码与之关联 */
int si_code; /* Signal code 说明进程如何接收信号以及从何处收到*/
int si_trapno;
pid_t si_pid; /* Sending process ID适用于SIGCHLD,代表被终止进程的PID */
uid_t si_uid; /* Real user ID of sending process适用于SIGCHLD,代表被终止进程所拥有进程的UID */
int si_status; /* Exit value or signal 适用于SIGCHLD,代表被终止进程的状态 */
clock_t si_utime; /* User time consumed 适用于SIGCHLD,代表被终止进程所消耗的用户时间 */
clock_t si_stime; /* System time consumed 适用于SIGCHLD,代表被终止进程所消耗系统的时间 */
==================
sigval_t si_value; /* Signal value */
==================
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
}
我们要通过siginfo的结构体传递参数
通过 si_pid 传递pid进程号
通过 si_int 传递数据 发送过来的数据
通过联合体si_value 可以传递 int char*
信号发送函数 sigqueue函数
功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
原型: int sigqueue(pid_t pid, int sig, const union sigval value);
参数:
第一个参数是指定接收信号的进程id,
第二个参数确定即将发送的信号signum
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
联合体:
union sigval {
int sival_int;
void *sival_ptr;
};
编程实现sigaction
#include <stdio.h>
#include <signal.h>
// int sigaction(int signum, const struct sigaction *act,
// struct sigaction *oldact);
void sa_sigactionhendler (int signum, siginfo_t* info, void *context)
{
printf("signum=%d\n",signum);
if(context!=NULL){ //如果内容不为空的话
printf("pid=%d\n",info->si_pid); //打印传递过来的pid进程号
printf("int =%d\n",info->si_int);// 打印传递过来的数据
printf("sival_int=%d\n",info->si_value.sival_int);
}
printf("no context \n");
}
int main()
{
// 在主函数里面创建一个结构体变量 act
struct sigaction act;
printf("my pid =%d\n",getpid()); //输出自己的pid号
act.sa_sigaction=sa_sigactionhendler;//这是结构体的第二个参数 可以携带信息的函数参数 把结构体 的第二个参数指向该函数
act.sa_flags=SA_SIGINFO; //设置成可以传递参数
sigaction(SIGUSR1,&act,NULL); //把结构体的地址给sigaction函数
while(1);
return 0;
}
编程实现sigqueue
#include <stdio.h>
#include <signal.h>
int main(int argr,char *argv[])
{
pid_t pid;
int signum;
union sigval value;
value.sival_int=100;
pid=atoi(argv[1]); //进行ascII码转换
signum=atoi(argv[2]);
//int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,signum,value);
printf("jieshu\n");
printf("发送端的pid= %d\n",getpid());
}
实验结果
通过结果可以看出发送端的pid号和携带信息发送到sigaction的siginfo结构体里面的pid号相同 而和自己本身的pid号不同 传递的int值100 也在siginfo结构体里面的int型变量输出的结果相同
六、 信号量
信号量不涉及信号的传递 信号量起到管理信号的操作
在一个房间外面有一把钥匙 第一个人拿了这个钥匙进入了这个房间里面,这时候外面没有钥匙了,第二个人再进去的时候就要再外面原地等待,第一个人出来后,第二个人才能拿着钥匙进入到房间里面去
钥匙 表示的是信号量
房间表示的是 临界资源
房间外面有很多的钥匙 表示信号量集
拿锁 p操作
放回锁 v操作
多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。
1、函数原型
1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
semget(key,1,IPC_CREAT|0666);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);
配合结构体进行p v 操作
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);
使用信号 union semun cmd;
cmd.val=0;
semctl(semid,0,SETVAL,cmd);
释放信号量semctl(semid,0,IPC_RMID);
2、semget
创建新的信号量集合时,必须指定集合中信号量的个数 num_sems 通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。
semop函数中,sembuf结构的定义如下
3、semop函数
int semop(int semid, struct sembuf semoparray[], size_t numops);
第二个参数指向结构体 sembuf
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的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
_semflg SEM_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
在semctl函数中
第三个参数
SEM_INFO //返回和IPC_INFO相同的信息,不同点有:semusz字段包含有当前系统存在的信号集总量。semaem字段包含有系统内所有信号集的信号总量。
SEM_STAT //返回和IPC_STAT相同的信息。不过参数semid不是一个信号集标识,而是内核内部维持所有信号集信息的数组索引。
GETALL //将所有信号的值存入semun.array中。
GETNCNT //等待信号值增加的进程的总数。
GETPID //前一个对此信号进行操作的进程的识别码。
GETVAL //根据semnun返回信号的值。
GETZCNT //等待信号值变为0的进程的总数。
SETALL //将所有semun.array的值设定到信号集中。
SETVAL //根据semun设定信号的值。
stmctl的结构体
根据 cmd 不同,这个函数有三个或四个参数。当有四个参数时,第四个参数的类型是 unionsemun。调用程序 必须按照下面方式定义这个联合体:
union semun {
int val; // SETVAL使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; // GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};
semop操作的结构体
struct sembuf {
short sem_num; //信号量的标识码,也就是semget返回值
short sem_op; //PV操作
short sem_flg; //两个取值IPC_NOWAIT或SEM_UNDO
};
编程过程
1.使用ftok创建文件索引值
2.使用semget创建信号量
semget(key,1,IPC_CREAT|0666);
3.初始化联合体 val 创建当前信号量的个数
4.创建父子进程 fork()
5.创建P 得到锁的函数 V 放回锁的函数
4、函数例子 p操作 v操作
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 pgetkey(int semid)
{
struct sembuf sops;
sops.sem_num=0;
sops.sem_op=-1;// 现在的锁表示拿走 锁拿走了1个
sops.sem_flg=SEM_UNDO;//一般是这个
semop(semid,&sops,1);
printf("锁拿走了 \n");
}
//v操作
void vputkey(int semid)
{
struct sembuf sops;
sops.sem_num=0;
sops.sem_op=1;//放回锁 信号量加一
sops.sem_flg=SEM_UNDO;
semop(semid,&sops,1);
printf("锁放回了 \n");
}
// int semget(key_t key, int nsems, int semflg);
int main()
{
key_t key;
int semid;
key=ftok(".",1);
if(key==-1){
printf("chuangjianshibai\n");
} //1为信号量集只有一个信号量
semid=semget(key,1,IPC_CREAT|0666);
//参考消息队列
// int semctl(int semid, int semnum, int cmd, ...);
union semun cmd;
//看门老头告诉告诉当前信号量为0 ;
cmd.val=0; //设置val为0 调用信号量状态为0 设置没有锁
//0为第一个信号量
semctl(semid,0,SETVAL,cmd);
//根据semun设定信号的值 cmd 把前面一个参数设置成 SETVAL 后使用
}
pid=fork();
if(pid>0){
pgetkey(semid);//通过子进程当前信号量的值为1大于或者等于sem_op的值
//当前sem_op为负数并且直到信号值大于或等于sem_op的绝对值
printf("this is father\n");
vputkey(semid);
semctl(semid,0,IPC_RMID);
}else if(pid==0){
printf("this is child\n");
vputkey(semid); //执行完成后信号值变成了0+1 变成了1
}else{
printf("chuangjian fork shibai \n");
}
return 0;
}
5、信号量解析
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。 出去一个车,老头知道有空地了,把车位给了等待的人,告诉他有车位了,等待的人进去以后,信号量减一,变为了0,并且,车位处于上锁阶段
信号量就是具有原子性的计数器,就相当于一把锁,在每个进程要访问临界资源时,必须要向信号量拿个锁”,它才能进去临界资源这个“房间”,并锁上门,不让其他进程进来,此时信号量执行P()操作,锁的数目减少了一个,所以计数器减1,;当它访问完成时,它出来,将锁还给信号量,执行V()操作,计数器加1;然后是下面的进程继续。这也体现了各个进程访问临时资源是互斥的。
七、共享内存与信号量进行配合
使用了【共享内存+信号量+消息队列】的组合来实现服务器进程与客户进程间的通信。
1、发送消息函数
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/msg.h>
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext; /* message data */
};
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) */
};
// type struct sembuf,
// unsigned short sem_num; /* semaphore number */
// short sem_op; /* semaphore operation */
// short sem_flg; /* operation flags */
int sem_v(int semid)
{
struct sembuf seml,
seml.sem_num=0;
seml.sem_op=1;
seml.sem_flg=SEM_UNDO;
semop(semid,&seml,1);
printf("放回了锁\n");
}
int sem_p(int semid)
{
//int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf seml,
seml.sem_num=0;
seml.sem_op=-1;
seml.sem_flg=SEM_UNDO;
semop(semid,&seml,1);
printf("得到了锁\n");
}
int creat_sem(key_t key)
{
int sem_id;
// int semget(key_t key, int nsems, int semflg);
semget(key,1,IPC_CREAT|0666);
if(sem_id==-1){
printf("xiaoxi liang chuangjian shibai \n");
}
union semun cmd;
cmd.val=1;
// int semctl(int semid, int semnum, int cmd, ...);
semctl(sem_id,0,SETVAL,cmd);
return sem_id;
}
int main()
{
key_t key;
int shmid;
int msgid;
int semid;
char *pshmat;
char str[128]={0};
int flag=1;
//创建key
key=ftok(".",1);
if(key==-1){
perror("why");
printf("key chuangjianshibai\n");
}
// int msgget(key_t key, int msgflg);
//创建消息队列
msgid=msgget(key,IPC_CREAT|0777);
if(msgid==-1){
printf("xiaoxiduilie chuangian shibai \n");
exit(-1);
}
// int shmget(key_t key, size_t size, int shmflg);
//创建共享内存
shmid=shmget(key,1024*4,0);
if(shmid==-1){
printf("shmid chuangjianshibai\n");
exit(-1);
}
//映射内存空间
pshmat=shmat(shmid,0,0);
//创建信息量
semid=creat_sem(key);
printf("创建映射完成\n");
printf("***************************************\n");
printf("* IPC *\n");
printf("* Input r to send data to server. *\n");
printf("* Input q to quit. *\n");
printf("***************************************\n");
while(1){
char c;
printf("shuru zifu \n");
scanf("%c",&c);
getchar();//吸收前面的那个回车
switch(c){
case 'r':
printf("Data to send: \n");
sem_p(semid);
gets(str);
strcpy(pshmat,str);
sem_v(semid);
struct msgbuf msg={888,'r'}; //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgsnd(msqid,&msg,sizeof(msg.mtext),0);
break;
case 'q':
msg.mtext='q';
msg.mtype=888;
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
flag = 1;
break;
default:
printf("Wrong input!\n");
while((c=getchar())!='\n' && c!=EOF);
break;
}
//关闭映射的共享内存
shmdt(pshmat);
return 0;
}
2、接受消息函数
接受函数相对发送函数相对简单
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/msg.h>
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext; /* message data */
};
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) */
};
int sem_v(int semid)
{
struct sembuf seml;
seml.sem_num=0;
seml.sem_op=1;
seml.sem_flg=SEM_UNDO;
semop(semid,&seml,1);
printf("fanghui suo le \n");
}
int sem_p(int semid)
{
struct sembuf seml;
seml.sem_num=0;
seml.sem_op=-1; //拿锁的时候信号量的值大于等于sem_op的绝对值
seml.sem_flg=SEM_UNDO;
// int semop(int semid, struct sembuf semoparray[], size_t numops);
semop(semid,&seml,1);
printf("nadao le suo \n");
}
int creat_sem(key_t key)
{
int sem_id;
// int semget(key_t key, int nsems, int semflg);
sem_id=semget(key,1,IPC_CREAT|0666);
if(sem_id==-1){
printf("xiaoxi liang chuangjian shibai \n");
}
union semun cmd;
cmd.val=1; //创建的当前信号的值为1
semctl(sem_id,0,SETVAL,cmd);
return sem_id;
}
int main()
{
// int shmget(key_t key, size_t size, int shmflg);
key_t key;
int shmid;//共享内存的返回值
int msgid;//消息队列的返回值
int semid;//消息量的返回值
char *pshmat;
struct msgbuf msg;
//创建id
key=ftok(".",1);
if(key==-1){
perror("why");
printf("key chuangjianshibai\n");
}
// 创建共享内存
shmid=shmget(key,1024*4,IPC_CREAT|0666);
if(shmid==-1){
printf("shmid chuangjianshibai\n");
exit(-1);
}
// int msgget(key_t key, int msgflg);
//创建消息队列 函数默认使用 IPC_CREAT|0777
msgid=msgget(key,IPC_CREAT|0777);
if(msgid==-1){
printf("xiaoxiduilie chuangian shibai \n");
exit(-1);
}
// void *shmat(int shmid, const void *shmaddr, int shmflg);
// 创建共享内存映射
pshmat=shmat(shmid,0,0);
printf("wancheng\n");
//创建信息量
semid = creat_sem(key);
//进行while循环
while(1){
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
// int msgflg);
//第一个表示msgid 第二个表示传递过来的结构体,第三个为消息队列号,最后默认0
msgrcv(msgid, &msg, 1, 888, 0);
if(msg.mtext == 'q'){ //为q的时候默认退出
break;
}
if(msg.mtext == 'r')
{
sem_p(semid);//拿到锁
char str[128];
strcpy(str,pshmat); //复制共享内存的信息
puts(str); //输出从共享内存传递过来的信息
// printf("%s\n",pshmat);
sem_v(semid);//执行完放回锁
}
}
// int shmdt(const void *shmaddr);
shmdt(pshmat);//关闭映射
// int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid, IPC_RMID,NULL);//关闭共享内存
msgctl(msgid, IPC_RMID,NULL);//关闭消息队列
semctl(semid,IPC_RMID,0);// 删除信息量
return 0;
}
把发送端的代码复制到接收端,接收端的代码复制到发送端,可以实现两个相互传输,不过是半双工的方式