LINUX系统编程-进程间通信
进程学习(一): 管道讲解,欢迎大家查看.
三、消息队列(Message Queue)
3-1什么是消息队列?
我们来看看消息队列的原理,就是再linux内核里有这么一个链表的消息队列,进程间的通信可以通过这个队列进行联系,与前两者不同的是这一个方法可以实现自己与自己的通信,而且也可以进行消息队列的销毁。
3-2消息队列API
那么我们会用到哪些API函数接口呢?
1.msgget函数,用来创建消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
函数的第一个参数是某个消息队列的名字,用ftok()产生。
函数的第二个参数是我们一般配置为IPC_CREAT|0777。
2.ftok函数,调用成功返回一个key值,用于创建消息队列,如果失败,返回-1
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
3.msgctl函数,消息队列的控制函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数的第一个参数是由msgget函数返回的消息队列标识码。
函数的第二个参数是我们这里配置为IPC_RMID.
IPC_STAT | 把msqid_ds结构中的数据设置为消息队列的当前关联值 |
---|---|
IPC_RMID | 把我们创建的消息队列删除 |
IPC_SET | 在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值 |
函数的第三个参数是我们这里配置为NULL;
4.msgsnd函数,把消息添加到消息队列中。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数的第一个参数,同上。
函数的第二个参数msgp是一个指向调用方定义的结构体的指针,一般形式如下:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
函数的第三个参数msgp指向的消息的长度。我们一般用strlen函数计算出结构体中mtext的大小。
函数的第四个参数我们一般设置为0。
5.msgrcv函数,从消息队列中接受消息。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
函数的第一个参数,同上。
函数的第二个参数,同上,就是将读取到的消息放到这个结构体面,然后我们再从结构体里面打印数据出来。实现进程间通信。
函数的第三个参数,同上。
函数的第四个参数,消息类型。
函数的第五个参数,默认为0。
3-3代码展示:
// 这个是get进程,接受进程
#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[50];
};
int main()
{
struct msgbuf readbuf;
struct msgbuf sendbuf = {988,"return for send"};
key_t key = ftok(".",'z');
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID==-1)
{
printf("chuangjian shibai !\n");
}
printf("the key = %x\n",key);
int msgID2 = msgrcv(msgID,&readbuf,sizeof(readbuf.mtext),888,0);
printf("we get the msg:%s\n",readbuf.mtext);
msgsnd(msgID,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgID,IPC_RMID,NULL);
return 0;
}
// send 进程
#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[50];
};
int main()
{
struct msgbuf readbuf ={888,"this is liushan"};
struct msgbuf sendbuf;
key_t key;
key = ftok(".",'z');
int msgID = msgget(key,IPC_CREAT|0777);
if(msgID==-1)
{
printf("chuangjian shibai !\n");
}
msgsnd(msgID,&readbuf,strlen(readbuf.mtext),0);
msgrcv(msgID,&sendbuf,sizeof(sendbuf.mtext),988,0);
printf("get form the get:%s\n",sendbuf.mtext);
msgctl(msgID,IPC_RMID,NULL);
return 0;
}
就是我们通过子啊两个终端上运行这两个程序,会在产生的两个进程中产生一个消息队列,通过该队列进行消息回应。
运行结果:
四、共享内存(Shared Memory)
4-1什么是共享内存?
共享内存,就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
还有一点就是共享内存一般没有同步机制,就是一个进程在写入消息的同时,另外一个进程也可以对该共享内存进行读取。打个比方就是你和你的同桌上课因为不能讲话,你们就在纸上用笔写出你们想说的话,你们本人就是进程,纸就是共享内存。
4-2函数API讲解
1.shmget函数,创建共享内存。
int shmget(key_t key, size_t size, int shmflg);
//参数一:标识系统的唯一IPC资源,我们用ftok生成,下文有讲。
//参数二:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,
// 一页是4个字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。比如1024*4
//参数三:我们设置为IPC_CREAT|0666。
2.shmat函数,挂接共享内存。
void *shmat(int shmid, const void *shmaddr, int shmflg);
//参数一:共享存储段的标识符。
//参数二:我们设定为0,交给linux内核进行处理。
//参数三:设定为0。
3.shmat函数,挂接共享内存。
int shmdt(const void *shmaddr);
//上述函数的返回值,关联共享内存,使进程与共享内存相互分离。
4.shmctl函数,删除共享内存。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//参数一:同上。
//参数二:用IPC_RMID进行卸载。
//参数三:设计为NULL即可。
4-3代码展示
// 写的程序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/shm.h>
#include<sys/ipc.h>
int main()
{
char *shmaddr ;
key_t key;
key = ftok(".",1);
int shmID = shmget(key,1024*4,IPC_CREAT|0666);
if(shmID==-1)
{
printf("chuangjian shibai!\n");
}
shmaddr = shmat(shmID,0,0);//映射
strcpy(shmaddr,"liushan");
printf("shmat ok\n");
sleep(5);
shmdt(shmaddr);//卸载
shmctl(shmID,IPC_RMID,NULL);//删除
return 0;
}
// 从共享内存读消息,头文件与上面的那个一样
int main()
{
char *shmaddr ;
key_t key;
key = ftok(".",1);
int shmID = shmget(key,1024*4,0);
if(shmID==-1)
{
printf("chuangjian shibai!\n");
}
shmaddr = shmat(shmID,0,0);//映射
printf("the data is:%s\n",shmaddr);
printf("shmat ok\n");
shmdt(shmaddr);//卸载
return 0;
}
运行结果!!!我们可以看到读取到的信息 : “liushan”,就编写成功。
五、信号(Signal)
5-1信号的定义?
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:
第一种方法是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
第二种方法是忽略某个信号,对该信号不做任何处理,就象未发生过一样。
第三种方法是对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
对于上面的通俗的解释来说就是,你在一个房间正在打电脑游戏,突然你的手机响了,这就是一个信号这是你可以选择不理会这个电话忽略,也可以选择接这个电处理,或者按照你往常的做法处理这个电话.(默认)。
// 一个简单的编写来展示信号控制
#include<stdio.h>
#include <signal.h>
// typedef void (*sighandler_t)(int); //函数原型
// sighandler_t signal(int signum, sighandler_t handler);//函数原型
void handler(int signum)
{
switch(signum)
{
case 2:
printf("the signal is :%d\n",signum);
printf(" SIGINT never stop\n");
break;
case 9:
printf("the signal is :%d\n",signum);
printf(" SIGKILL never stop\n");
break;
case 10:
printf("the signal is :%d\n",signum);
printf(" SIGUSR1 never stop\n");
break;
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);
printf("the jingcheng jieshu\n");
return 0;
}
  在这个程序中,就会截获我们发送的ctrl+c/ctrc+z的信号使程序不退出
我们只有用kill -9 +pid 这个命令来干掉这个进程。
这个结果就需要我们进行多次的信号关闭,才能退出这个程序。
那么我们如何通过信号来携带消息呢?我们接着看看。
这里我们主要运用以下几个函数:
// 接收发函数原型
int sigqueue(pid_t pid, int sig, const union sigval value);//发消息的函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);//收消息函数原型
两个的函数原型都比较复杂,我们一一来进行分解
第一个函数的前两个函数就不说了,我们来讲讲第三个参数const union sigval value,这是一个联合体。我们通过查询函数原型可以看到:
union sigval {
int sival_int;
void *sival_ptr;
};
  第一个数传递一个整形数进去,第二个则是传递一个指针过去,我们可以通过此传递字符串。
我们继续看第二个函数的参数:第一个是信号,第二个是一个结构体,第三个是一个代码备份我们通常设计为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);
};
对于上面的结构体void (*sa_sigaction)(int, siginfo_t *, void *);第二个参数是个函数指针,所以我们就需要再写一个函数来进行调用。
参数分别对应以上的三个参数。在函数的第二个参数上又是一个结构体,主要用来存储接受到的信息。
// 结构体原型如下
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 */
sigval_t 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 kernel 2.6.32) */
}
对于我下面的程序,我们就用对几个进行使用就好了。
5-2代码展示
// 发消息的代码
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
int main(int argc,char**argv)
{
int signum;
int pid;
union sigval sig;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
sig.sival_int = 99;
sigqueue(pid,signum,sig);
kill(pid,signum);
printf("the send is ok!\n");
}
// 收消息的代码
#include<signal.h>
#include<stdio.h>
void handler(int signum,siginfo_t *info,void *temp)
{
if(temp!=NULL){
printf("the signum = %d\n",signum);
printf("the data = %d\n",info->si_value.sival_int);
printf("the data = %d\n",info->si_pid);
printf("the data = %s\n",);
}
}
int main()
{
struct sigaction action;
printf("the jincheng pid = %d\n",getpid());
action.sa_sigaction = handler;
action.sa_flags = SA_SIGINFO ;
sigaction(SIGUSR1,&action,NULL);
printf("shoudao !\n");
while(1);
return 0;
}
运行结果!!!
六、信号量(Semaphore)
6-1什么是信号量?
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,简单点来说信号量是用来调协进程对共享资源的访问的。
最简单的信号量只能取0和1的变量,这也是信号量最常见的一种形式,叫做二值信号量,而可以取多个整数的信号量称为通用信号量。
6-2Linux的信号量机制
我们要如何编写信号量这个东西呢?这就需要我们进行调用Linux上的一些函数接口。
1.semget这个函数我们用来创建一个信号量或者获取一个已经拥有的信号量。
// 头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
函数的第一个参数key是整数值,程序先通过调用semget函数并提供一个键,然后再通过函数的返回值来进行使用信号量。
函数的第二个参数nsems是我们所指定的信号集中的信号量的个数,我们一般设计为 1;
函数的第三个函数semflg,一般设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | 0777则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误
2. int semctl(int semid, int semnum, int cmd, …),用这个函数来控制我们获取到的信号量ID。初始化信号量。
int semctl(int semid, int semnum, int cmd, ...);
函数的第一个参数semid,是我们的信号量的ID。
函数的第二个参数semnum,是在信号量集中的位数。
函数的第三个参数cmd,我们一般配置为SETVAL 用来设置信号量的值,然后我们还需要继续配置第四个参数。
链接: 图片来源
函数的第四个参数是需要我们定义一个联合体,联合体的原型如下:
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) */
};
3. int semop(int semid, struct sembuf *sops, unsigned nsops);用来对信号量进行操作。
int semop(int semid, struct sembuf *sops, unsigned nsops);
函数的第一个参数semid,是信号量的ID。
函数的第二个参数struct sembuf *sops,是需要传递一个结构体的地址进去,结构体定义。
struct sembuf
{
unsigned short sem_num;//信号量急中的位数
short sem_op;//操作一次的增减,取得一次信号量集要减1
short sem_flg;//设定为SEM_UNDO
};
函数的第三个参数是我们操作的信号量个数,我们设定为1.
4.p操作函数
// p操作函数部分
void PGET(int semID)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = SEM_NUDO;
semop(semID,&sops,1);
}
5.v操作函数
//v操作函数
void Vpush_back(int semID)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1;//改变即可
sops.sem_flg = SEM_NUDO;
semop(semID,&sops,1);
}
6-3Linux的信号量的总体代码编写
// 代码编写
#include<stdio.h>
#incldue<sys/types.h>
#include<sys/sem.h>
#include<sys/ipc.h>
//联合体声明
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 PGET(int semID)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = SEM_NUDO;
semop(semID,&sops,1);
}
// V操作函数部分
void Vpush_back(int semID)
{
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = 1;//改变即可
sops.sem_flg = SEM_NUDO;
semop(semID,&sops,1);
}
int main()
{
key_t key = ftok(".",2);
int semID = semget(key,1,IPC_CREAT|0777);
union semum temp;
temp.val = 0;
semctl(semID,0,SETVAL,temp);
int pid = fork();
if(pid>0)
{
PGET(semID);
printf("this is father\n");
Vpush_back(semID);
}else if(pid==0)
{
printf("this is child\n");
Vpush_back(semID);
}else printf("fork error\n");
return 0;
}
// 运行结果
LS@Embed_Learn:~$ ./semw
this is child
this is father
LS@Embed_Learn:~$ ./semw
this is child
this is father
LS@Embed_Learn:~$ ./semw
this is child
this is father
LS@Embed_Learn:~$ ./semw
this is child
this is father
得到控制后,都是子进程先跑,编写成功。以上内容均为本人学习记录,也差不多写了几千字,大家觉得哪里还需要改进呢?欢迎交流学习。