Linux系统编程_进程间通信第2天: 共享内存(全双工)、信号(类似半双工)、信号量

1. 共享内存概述(433.10)(全双工)

2. 共享内存编程实现(434.11)

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区

特点

  1. 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
  2. 因为多个进程可以同时操作,所以需要进行同步。
  3. 信号量 + 共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

原型

c 复制代码
#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
  • 当用 shmget 函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为 0 。
  • 当一段共享内存被创建以后,它并不能被任何进程访问。必须使用 shmat 函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
  • shmdt 函数是用来断开 shmat 建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
  • shmctl 函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

代码

  • IPC/shmwr.c(写数据至共享内存)
c 复制代码
#include <sys/ipc.h>
#include <sys/shm.h>//shmget
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>//sleep
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);//共享内存的id,0让linux内核自动安排共享内存,0让连接到的空间是可读可写的
//int shmdt(const void *shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);//id,指令,0存放卸载共享内存时产生的信息
int main(){
	int shmid;
	char *shmaddr;
	key_t key;
	key = ftok(".",1);
	
	shmid = shmget(key,1024*4,IPC_CREAT|0666);//创建一个共享内存,可读可写的权限
	if(shmid == -1){
		printf("shmget not Ok\n");
		exit(-1);//异常退出返回-1
	}
	
	shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间,映射:
	printf("shmat ok\n");
	
	strcpy(shmaddr,"Jessie is me.");//写数据到共享内存的shmaddr

	sleep(5);//休眠让别的进程来读
	
	shmdt(shmaddr);//断开与共享内存的连接,卸载
	
	shmctl(shmid, IPC_RMID, 0);//删除此共享内存

	printf("quit\n");
	
	return 0;
}
  • IPC/shmrd.c(从共享内存读数据)
c 复制代码
#include <sys/ipc.h>//shmget
#include <sys/shm.h>//shmget
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>//sleep
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);//共享内存的id,0让linux内核自动安排共享内存,0让连接到的空间是可读可写的
//int shmdt(const void *shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);//id,指令,0存放卸载共享内存时产生的信息
int main(){
	int shmid;
	char *shmaddr;	
	key_t key;
	key = ftok(".",1);
	
	shmid = shmget(key,1024*4,0);//打开/获取此共享内存
	if(shmid == -1){
		printf("shmget not Ok\n");
		exit(-1);//异常退出返回-1
	}
	
	shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间,映射,读取字符串
	printf("shmat ok\n");
	
	printf("data: %s\n",shmaddr);//打印来自己写端的数据

	shmdt(shmaddr);//断开与共享内存的连接,卸载

	printf("quit\n");
	
	return 0;
}
  • 查看共享内存端的命令
  • 查看以下源码手册的方法

3. 信号概述(12)(类似半双工)

  • 对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。
  • 信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

信号概述

  1. 信号的名字和编号:
  • 每个信号都有一个名字和编号,这些名字都以 "SIG" 开头,例如 "SIGIO "、"SIGCHLD" 等等。
  • 信号定义在signal.h头文件中,信号名(的编号)都定义为正整数。
  • 具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从 1 开始编号的,不存在 0 号信号。kill 对于信号 0 有特殊的应用。
  1. 信号的处理:

信号的处理有三种方法,分别是:忽略、捕捉和默认动作

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。也可参考 《UNIX 环境高级编程(第三部)》的 P251------P256 中间对于每个信号都有详细的说明。

了解了信号的概述,那么,信号是如何来使用呢?

其实对于常用的 kill 命令就是一个发送信号的工具,kill -9 PID来杀死进程。比如,我在后台运行了一个 a 工具,通过 ps 命令可以查看他的 PID,通过 kill -9 来发送了一个终止进程的信号来结束了 a 进程。如果查看信号编号和名称,可以发现 9 对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了 9 号信号的默认动作------杀死进程。

信号处理函数的注册

  1. 入门版:函数signal
  2. 高级版:函数sigaction

信号处理发送函数

  1. 入门版:kill
  2. 高级版:sigqueue

4. 信号编程(13)

  • man 2 signalman 2 kill
  • IPC/signalDemo1.c
c 复制代码
#include <signal.h>
#include <stdio.h>
//       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;
		case 9:
			printf("SIGKILL\n");
			break;
		case 10:
			printf("SIGUSR1\n");
			break;
	}
	printf("never quit\n");
}

int main()
{//直接使用信号处理函数注册信号处理程序
	//signal(SIGINT,handler);
	//signal(SIGKILL,handler);
	signal(SIGINT,SIG_IGN);//使用 SIG_IGN 宏来忽略信号
	signal(SIGKILL,SIG_IGN);//忽略无效,不能被捕获、忽略或处理
	signal(SIGUSR1,handler);
	
	while(1);
	return 0;
}
  • IPC/signalDemo1CON.c
c 复制代码
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>//atoi system

int main(int argc ,char **argv)
{
	int signum;
	int pid;
	char cmd[128]={0};

	signum = atoi(argv[1]);//将字符串转换为整数类型
	pid = atoi(argv[2]);

	printf("num=%d,pid=%d\n",signum,pid);	

//	kill(pid,signum);
	sprintf(cmd,"kill -%d %d",signum,pid);//构建有效的cmd格式
	
	system(cmd);//执行shell命令

	printf("send signal ok\n");
		
	return 0;
}



5. 信号如何携带消息(14)

信号注册函数------高级版

  • sigaction的函数原型
c 复制代码
#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
   //void     (*sa_restorer)(void);//不再使用,已经被弃用
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一
  • 关于void (*sa_sigaction)(int, siginfo_t *, void *);处理函数来说还需要有一些说明。void*是接收到信号所携带的额外数据;而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。
c 复制代码
 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 */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

信号发送函数------高级版

  • sigqueue的函数原型
c 复制代码
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };


6. 信号携带消息编程实战(15)

  • IPC/NiceSignal.c
c 复制代码
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
//int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//struct sigaction { ; ; ; ;};
//void (*sa_sigaction)(int, siginfo_t *, void *); /
void   handler(int signum ,siginfo_t *info,void *context)
{//信号处理程序,能够接受额外数据
	printf("get signum %d\n",signum);
	
	if(context != NULL){
		printf("get data=%d\n",info->si_int);//等同下面的int值
		printf("get data=%d\n",info->si_value.sival_int);//接收一个整数的消息
		printf("from:%d\n",info->si_pid);//发送者进程的pid
		//printf("get data=%s\n",(char *)context);
		//printf("get data=%d\n",*(int *)(info->si_value.sival_ptr));//额外数据
	}
}

int main()
{
	struct sigaction act;
	printf("pid = %d\n",getpid());//此进程的pid

	act.sa_sigaction = handler;//信号处理程序,能够接受额外数据
	act.sa_flags = SA_SIGINFO; //be able to get message能够接受数据


	sigaction(SIGUSR1,&act,NULL);//信号名,指向的函数,无备份
	
	while(1);
	
	return 0;
}
  • IPC/send.c
c 复制代码
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>//getpid
#include <unistd.h>//getpid
#include <stdlib.h>//atoi system
#include <string.h>
//int sigqueue(pid_t pid, int sig, const union sigval value);
//union sigval {int sival_int;void *sival_ptr;};
int main(int argc, char **argv)
{
	int signum;
	int pid;
	
	signum = atoi(argv[1]);
	pid = atoi(argv[2]);
	
	union sigval value;//共用体
	value.sival_int = 100;//传递一个整数的消息
	
	/*value.sival_ptr = malloc(strlen("vale\0") + 1);
	strcpy(value.sival_ptr,"vale\0");//复制字符串到 sival_ptr
	free(value.sival_ptr);*/
	//int a = 10;
	//value.sival_ptr = &a;	

	sigqueue(pid,signum,value);//信号发送函数------高级版
	printf("%d,done\n",getpid());//打印自身的pid
	
	return 0;
}
  • 如果打印info->si_value.sival_ptr额外数据,会段错误


7. 信号量概述(16)

  • 信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。
  • 信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
  • 临界资源:采取互斥的方式,实现共享的资源
  • 多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。
  • 一次仅允许一个进程使用的资源称为临界资源。
  • 许多物理设备都属于临界资源,如输入机、打印机、磁带机等。

特点

  1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
  4. 支持信号量组。

原型

  • 最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
  • Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
c 复制代码
#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

8. 信号量编程实现一(17)

  • man 2 semgetman 2 semctl
  • IPC/sem.c
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
// int semop(int semid, struct sembuf *sops, unsigned 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) */
};

int main(int argc, char const *argv[])
{
	key_t key;
	int semid;

	key = ftok(".",2);
                       //信号量集合中有一个信号量  
	semid = semget(key, 1, IPC_CREAT|0666);//获取/创建信号量

	union semun initsem;
	initsem.val = 0;//信号量里有一把锁
                  //操作第0个信号量   
	semctl(semid, 0, SETVAL, initsem);//初始化信号量
                  //SETVAL设置信号量的值,设置为inisem                             
    int pid = fork();
    if(pid > 0){
    	//去拿锁
    	printf("this is father\n");
    	//锁放回去
    }
    else if(pid == 0){
    	printf("this is child\n");
    }else{
    	printf("fork error\n");
    }            
	return 0;
}

9. 信号量编程实现二(436.18)

  • IPC/sem.c(子进程先走放回钥匙,父进程再拿钥匙走)
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned 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 id)//拿钥匙
{
	struct sembuf set;

	set.sem_num = 0;
	set.sem_op = -1;//-1把钥匙,相当于获取锁
	set.sem_flg=SEM_UNDO;

	semop(id,  &set ,1);
	printf("getkey\n");
}
void vPutBackKey(int id)//放回钥匙
{
	struct sembuf set;

	set.sem_num = 0;//对信号量0进行操作
	set.sem_op = 1;//+1把钥匙,相当于放回锁
	set.sem_flg=SEM_UNDO;

	semop(id,&set,1);//操作1个信号量
	printf("put back the key\n");
}

int main(int argc, char const *argv[])
{
	key_t key;
	int semid;//信号量集ID

	key = ftok(".",2);
                       //信号量集合中有一个信号量  
	semid = semget(key,1,IPC_CREAT|0666);//获取/创建一个包含1个信号量的信号量集合,可读可写权限

	union semun initsem; //联合体,用于semctl初始化
	initsem.val = 0;//信号量的初值:0把锁
                  //操作第0个信号量   
	semctl(semid,0,SETVAL,initsem);//初始化信号量
                  //SETVAL指令模式(设置信号量的值),设置为inisem.val的值
                  
                  
    int pid = fork();
    if(pid > 0){
	pGetKey(semid);//去拿钥匙,开门,钥匙剩余0
    	printf("this is father\n");
	vPutBackKey(semid);//放回钥匙,关门,钥匙剩余1
	semctl(semid,0,IPC_RMID);//移除钥匙
    }
    else if(pid == 0){
    	printf("this is child\n");
	vPutBackKey(semid);//放回钥匙,关门,钥匙剩余1
    }else{

    	printf("fork error\n");
    }            
    
	return 0;
}
相关推荐
qq_3129201118 分钟前
Nginx限流与防爬虫与安全配置方案
运维·爬虫·nginx·安全
GanGuaGua25 分钟前
Linux系统:线程的互斥和安全
linux·运维·服务器·c语言·c++·安全
lsnm34 分钟前
【LINUX网络】IP——网络层
linux·服务器·网络·c++·网络协议·tcp/ip
全糖去冰吃不了苦34 分钟前
ELK 集群部署实战
运维·jenkins
不掰手腕1 小时前
在UnionTech OS Server 20 (统信UOS服务器版) 上离线安装PostgreSQL (pgsql) 数据库
linux·数据库·postgresql
Lynnxiaowen1 小时前
今天继续昨天的正则表达式进行学习
linux·运维·学习·正则表达式·云计算·bash
努力学习的小廉1 小时前
深入了解linux系统—— POSIX信号量
linux·运维·服务器
刘一说2 小时前
CentOS部署ELK Stack完整指南
linux·elk·centos
从零开始的ops生活2 小时前
【Day 50 】Linux-nginx反向代理与负载均衡
linux·nginx
IT成长日记2 小时前
【Linux基础】Linux系统配置IP详解:从入门到精通
linux·运维·tcp/ip·ip地址配置