一、共享内存原理
1原理:就是开辟一个独立的内存空间,来实现通信。
通俗点说:就好像两个学生在上课的时候,由于不能说话,就只好拿一个本子来聊天,这个本子就相当于共享的内存。
2.原形:
int shmget(key_t key,size_t size,int flag);//创建或者获取一个共享内存,成功返回ID,反之-1
void *shmat(int shm_id,const void *addr,int flag);//连接共享内存到当前进程的地址空间,成功返回共享内存指针,失败返回-1、
int shmdt(void *addr);//断开与共享内存的连接,成功返回0,反之-1
int shmctl(int shm_id,int cmd,struct shmid_ds *buf);//控制共享内存相关信息,成功返回0,失败返回-1
3.注意:
key为ftok生成的键值
size为共享内存的长度,以字节为单位
flag为所需要的操作和权限,可以用来创建一个共享存储空间并返回一个标识符或者获得一个共享标识符。
flag的值为IPC_CREAT:如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则直接返回共享存储标识符。
flag的值为 IPC_CREAT | IPC_EXCL:如果不存在key值的共享存储空间,且权限不为0,则创建共享存储空间,并返回一个共享存储标识符。如果存在,则产生错误。
4.补充:
函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。
ftok函数原型及说明:ftok(把一个已存在的路径名和一个整数标识符转换成IPC键值)
二、共享内存编程实现
1.共享内存空间的读取
int main(){
int shmId;
char* addr;
key_t key;
key=ftok(".",1);
shmId=shmget(key,1024*3,0);//直接获取空间
if(shmId == -1){//判断是否创建成功
printf("creat shared memory errno!\n");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);//链接共享内存到当前进程空间
addr=shmat(shmId,0,0);
if(*addr == -1){//判断是否链接成功
printf("shmat errno!\n");
}
else{
printf("shmat success!\n");
}
//strcpy(addr,"dugad is a handsome man");
printf("read datas:%s\n",addr);//读取写入共享内存的数据
//int shmdt(const void *shmaddr);//断开与共享内存的空间
int n_shmdt=shmdt(addr);
if(n_shmdt == -1){
printf("shmdt errno!\n");
}
else{
printf("shmdt success!\n");
}
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);//对共享内存进行控制
/*shmid就是shmget函数返回的共享存储标识符
cmd有三个,常用删除共享内存的为IPC_RMID;IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中;IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内。
buf就是结构体shmid_ds
*/
int n_shmctl=shmctl(shmId,IPC_RMID,0);
if(n_shmctl == -1){
printf("shmctl errno!\n");
}
else{
printf("shmctl success!\n");
}
return 0;
}
2.共享内存空间的写入
int main(){
int shmId;
char* addr;
key_t key;
key=ftok(".",1);
shmId=shmget(key,1024*3,IPC_CREAT|0666);//创建共享内存空间
if(shmId == -1){//判断是否创建成功
printf("creat shared memory errno!\n");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);//链接共享内存到当前进程空间
addr=shmat(shmId,0,0);
if(*addr == -1){//判断是否链接成功
printf("shmat errno!\n");
}
else{
printf("shmat success!\n");
}
strcpy(addr,"dugad is a handsome man");//往共享内存内写入数据
sleep(10);//给读取内容的shmWrite一点时间,不然后面就直接清除内存空间了
//int shmdt(const void *shmaddr);//断开与共享内存的空间
int n_shmdt=shmdt(addr);
if(n_shmdt == -1){
printf("shmdt errno!\n");
}
else{
printf("shmdt success!\n");
}
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);//对共享内存进行控制
int n_shmctl=shmctl(shmId,IPC_RMID,0);
if(n_shmctl == -1){
printf("shmctl errno!\n");
}
else{
printf("shmctl success!\n");
}
return 0;
}
3.运行结果
4.注意 :一定要先写入才能去读
三、信号概述
1.信号:对Linux来说就是软中断,与单片机的硬件中断类似。如在linux中输入 ctrl+c 来停止一个程序
2.信号的名字与编号:可在linux中通过 kill -l 查询(不存在0信号,0信号有特殊的应用:在系统级的应用中被占用)
3. 信号的处理方式:忽略,捕捉和默认动作
忽略:就跟字面意思一样忽略掉它(注意:SIGKILL,SIGSTOP不能被忽略)
捕捉:就是一些信号处理的函数,然后让这个函数告诉内核,当信号产生时,内核调用该函数,实现某种信号的处理
默认动作:每个信号都有其对应的默认的处理动作,当触发了某种信号,系统就会立刻去执行。
4.信号处理函数的注册:signal ,sigaction
5.信号处理发送函数:kill ,sigqueue
四、信号编程实现
#include <stdio.h>
#include <signal.h>
//typedef void (*sighandler_t)(int);//函数指针,无返回值,传入参数为一个int型
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum){
switch(signum){
case 2:
printf("get SIGINT,signum=%d\n",signum);
break;
case 9:
printf("get SIGKILL,signum=%d\n",signum);
break;
case 10:
printf("get SIGUSR1,signum=%d\n",signum);
break;
}
printf("never quit\n");
}
int main(){
signal(SIGINT,handler);//捕捉信号
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
for(;;);//等于while(1);
return 0;
}
//int kill(pid_t pid, int sig);
//typedef void (*sighandler_t)(int);//函数指针,无返回值,传入参数为一个int型
//sighandler_t signal(int signum, sighandler_t handler);
int main(int argc,char **argv){
int signum;
int pid;
char cmd[128];
signum=atoi(argv[1]);//atoi(),将字符串转为整型
pid=atoi(argv[2]);
printf("num=%d,pid=%d\n",signum,pid);
//kill(pid,signum);//发送信号
sprintf(cmd,"kill -%d %d",signum,pid);
system(cmd);
printf("send signal ok");
return 0;
}
运行结果:
五、信号携带消息编程实现
// int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
//sigactio(),会依照参数signum指定的信号编号来设置该信号的处理函数
//const struct sigaction *act,你要做什么
//struct sigaction *oldact,是否备份,不备份用NULL
/* struct sigaction {
void (*sa_handler)(t struct sigaction *actnt);
此参数和signal()的参数handler相同,此参数主要用来对信号旧的安装函数signal()处理形式的支持
void (*sa_sigaction)(int, siginfo_t *, void *);
新的信号安装机制,处理函数被调用的时候,不但可以得到信号编号,而且可以获悉被调用的原因以及产生问题的上下文的相关信息。
sigset_t sa_mask;
用来设置阻塞,阻塞作用,默认就是阻塞的作用
int sa_flags;
用来设置信号处理的其他相关操作,下列的数值可用,可用OR运算(|)组合
SA_SIGINFO:信号处理函数是带有三个参数的sa_sigaction
void (*sa_restorer)(void);
};
*/
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
// int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
//sigactio(),会依照参数signum指定的信号编号来设置该信号的处理函数
//const struct sigaction *act,你要做什么
//struct sigaction *oldact,是否备份,不备份用NULL
/* struct sigaction {
void (*sa_handler)(t struct sigaction *actnt);
此参数和signal()的参数handler相同,此参数主要用来对信号旧的安装函数signal()处理形式的支持
void (*sa_sigaction)(int, siginfo_t *, void *);
新的信号安装机制,处理函数被调用的时候,不但可以得到信号编号,而且可以获悉被调用的原因以及产生问题的上下文的相关信息。
sigset_t sa_mask;
用来设置阻塞,阻塞作用,默认就是阻塞的作用
int sa_flags;
用来设置信号处理的其他相关操作,下列的数值可用,可用OR运算(|)组合
SA_SIGINFO:信号处理函数是带有三个参数的sa_sigaction
void (*sa_restorer)(void);
};
*/
void handler(int signum,siginfo_t *info, void *context){//void *context,空值无数据,非空有数据传来
printf("receive signum:%d\n",signum);
if(context != NULL){//判断是否有数据传来
printf("get datas:%d\n",info->si_int);//siginfo_t,是一个结构体,通过调用相应的结构体类型打印接受的值
printf("get datas:%d\n",info->si_value.sival_int);//si_value,也是一个结构体
printf("get pid:%d\n",info->si_pid);//打印进程号
printf("get string:%s\n",(char*)info->si_value.sival_ptr);//接受字符串
}
}
int main(){
struct sigaction act;
printf("pid=%d\n",getpid());
act.sa_sigaction=handler;//函数指针指向handler
act.sa_flags=SA_SIGINFO;//接受信息需要使用该宏定义
int n_sig=sigaction(SIGUSR1,&act,NULL);//注册信号
if(n_sig == -1){
printf("errno!\n");
}
else{
printf("success!\n");
}
for(;;);//等于while(1)
return 0;
}
// int sigqueue(pid_t pid, int sig, const union sigval value);//发送消息
/*
pid_t pid:信号的pid号
int sig:要发送的信号
const union sigval value:共用体
*/
int main(int argc ,char **argv){
int signum;
pid_t pid;
signum=atoi(argv[1]);//传入的参数1
pid=atoi(argv[2]);//传入的参数2
union sigval value;//定义一个共用体
value.sival_int=100;//发送一个整型数据
value.sival_ptr="you are a lucky dog\n";
// strcpy(value.sival_ptr,"you are a lucky dog");
int n_sigqueue=sigqueue(pid,signum,value);
printf("sigqueue's pid=%d\n",getpid());
printf("Ending!\n");
return 0;
}
运行结果:
六、信号量概述
1.信号量:是一个计数器,用于实现进程间的互斥与同步,不用于存储进程间的通信数据
2.特点:
(1).用于进程间同步,若要在进程间传递数据需要结合共享内存
(2).信号量是基于PV操作,程序对信号量是原子操作
(3).对信号量的操作不仅限于对信号的+1,-1,可以是任意数
3.原型:
int semget(key_t key,int num_sems,int sem_flags);//创建或获取一个信号量组,成功返回ID,反之-1
int semop(int semid,struct sembuf semoparry[],size_t numops);//对信号量组操作,改变信号量的值,成功返回0,反之-1
int semctl(int semid,int sem_num,int cmd,.....);//控制信号量相关操作
4.PV操作
可以这样理解:一间房间的门前有一个盒子,盒子有钥匙,一个人拿了钥匙(P操作),开了门并走进了房间,且门外还有人等着,得等进去的人出来放钥匙(V操作),这个人才能拿钥匙(P操作)进去房间
七、信号量编程实现
//int semget(key_t key, int nsems, int semflg);//获取或创建信号量
//int semctl(int semid, int semnum, int cmd, ...);//初始化信号量
// int semop(int semid, struct sembuf *sops, size_t nsops);//取钥匙操作
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 semId){//取钥匙操作
struct sembuf set;
set.sem_num=0;//信号量的编号
set.sem_op=-1;//取钥匙-1
set.sem_flg=SEM_UNDO;//设置为当进程截止的时候,取消对锁的操作
printf("getkey success!\n");
}
void vputBackKey(int semId){//放钥匙操作
struct sembuf set;
set.sem_num=0;//信号量的编号
set.sem_op=1;//取钥匙+1
set.sem_flg=SEM_UNDO;//设置为当进程截止的时候,取消对锁的操作
semop(semId,&set,1);
printf("putBackey success!\n");
}
int main(){
int semId;
key_t key;
key=ftok(".",3);
semId=semget(key,1,IPC_CREAT|0666);//创建或获取信号量,1,表示信号量集合中有一个信号量
union semun initsem;
initsem.val=0;//设置钥匙(这里表示钥匙被拿了)
int n_semctl=semctl(semId,0,SETVAL,initsem);//初始化信号量,0,表示操作第0个信号(即操作第一位);SETVAL设置信号量的初值,设置>为inisem
pid_t pid;
pid=fork();//创建进程
if(pid > 0){
pgetKey(semId);//拿锁(前面val设置为0,等于没有锁,拿不到锁会让子进程先运行)
printf("this is father fork!\n");
vputBackKey(semId);//放钥匙
}
else if(pid == 0){
printf("this is child fork!\n");
vputBackKey(semId);//放钥匙
}
else{
printf("creat fork errno!\n");
}
return 0;
}
运行结果: