嵌入式入门学习笔记,遇到的问题以及心得体会!
DAY27
概述:
一、System V 共享内存
二、POSIX 信号量
三、POSIX 消息队列
笔记:
注意:
不论是怎样的通信,只要牵扯公共资源
读读不互斥
读写互斥
写写互斥
一、System V 共享内存
1.概念:
可以说,共享内存是一种最为高效的进程间通信方式。因为,进程可以直接读写内存,不需要任何数据的复制。为了在多个进程间交换信息。内核专门流出了一块内存。这段内存可以由需要访问的进程将其映射到自己的私有地址空间。因此,进程就可以直接读写这一内存区而不要进行数据的复制,从而大大提高了效率。当然,由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等。
2.实现过程:
共享内存的实现分为两个步骤:
(1)创建共享内存,这里用到的函数时shmget(),也就是从内存种获得一段共享内存区域。
(2)映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数时shmat()。到这里就可以使用这段共享内存了,也就是可以使用不带缓冲的I/O读写命令对其进行操作。
(3)撤销映射,使用shmdt()函数。
3.所用函数:
(1)ftok函数:
#include <sys/types.h>
#include <sys/ipc.h>
/*
*函数名:ftok
*函数功能:convert a pathname and a project identifier to a
* System V IPC key, Key可用于msgget, semget, or
* shmget的key参数
*函数参数:
* const char *pathname:想要输出的字符串的首地址
* int proj_id:一个整型标识
*函数返回值:key_t:成功返回一个key_t的key,失败返回-1
*/
key_t ftok(const char *pathname, int proj_id);
(2)shmget函数:
#include <sys/ipc.h>
#include <sys/shm.h>
/*
函数名:shmget
函数功能:获得共享内存
函数参数:key:共享内存的键值,多个进程可以通过它访问同一个共享内存。其中有个特殊值 IPC_PRIVATE,用于创建当前进程的私有共享内存。
size:共享内存的大小
shmflg:同open()函数的权限位,也可以使用八进制表示法
函数返回值:
成功:共享内存段标识符
失败:-1
*/
int shmget(key_t key, size_t size, int shmflg);
(3)shmat函数:
#include <sys/types.h>
#include <sys/shm.h>
函数参数:
shmid:要映射的共享内存区标识符。
shmaddr:将共享内存映射到指定地址若为NULL,则表示系统自动分配地址,并把该段共享 内存映射到调用进程的地址空间)
shmflg:SHM_RDONLY:共享内存只读
默认0:共享内存可读可写
函数返回值:成功:被映射的段地址
出错:-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
(4)shmdt函数:
#include <sys/types.h>
#include <sys/shm.h>
函数原型:int shmdt(const void *shmaddr);
函数参数:shmaddr:被映射的共享内存段地址
函数返回值:
成功:0
失败:-1
4.如何查看共享内存:
ipcs -m
5.如何删除共享内存:
ipcrm -m [shmid]
6.代码演示
1.read_p ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[50]; /* message data */
}Msg_Buf;
int main()
{
//ftok
key_t key = -1;
key = ftok(".",'a');
if(key < 0)
{
puts("ftok error.");
return -1;
}
//msgget
int msgqid = -1;
msgqid = msgget(key, IPC_CREAT | 0664);
if(msgqid < 0)
{
puts("msgget error.");
return -1;
}
while(1)
{
puts("recv message from queue:");
//msgsnd
Msg_Buf msg;
memset(msg.mtext, 0, 50);
msg.mtype = getpid();
int ret = msgrcv(msgqid, &msg, 50, 0, 0);
if(ret > 0)
{
puts(msg.mtext);
}
else
{
break;
}
if(strncmp(msg.mtext,"quit",4) == 0)
{
break;
}
}
msgctl(msgqid,IPC_RMID,NULL);
return 0;
}
2.write_p ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
typedef struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[50]; /* message data */
}Msg_Buf;
int main()
{
//ftok
key_t key = -1;
key = ftok(".",'a');
if(key < 0)
{
puts("ftok error.");
return -1;
}
//msgget
int msgqid = -1;
msgqid = msgget(key, IPC_CREAT | 0664);
if(msgqid < 0)
{
puts("msgget error.");
return -1;
}
while(1)
{
puts("input some message send to queue:");
//msgsnd
Msg_Buf msg;
char *p = fgets(msg.mtext, 50, stdin);
if(NULL == p)
{
puts("get msg.text error.");
continue;
}
msg.mtype = getpid();
int ret = msgsnd(msgqid, &msg, 50, 0);
if(ret < 0)
{
puts("msgsnd error.");
continue;
}
if(strncmp(msg.mtext,"quit",4) == 0)
{
break;
}
}
return 0;
}
二、POSIX 信号量
1.需求:
7个进程实现一个餐厅的流水线
买菜—>洗菜—>切菜---->炒菜---->装盘—>吃—>洗盘
7个进程之间有顺序关系
如何保证7个进程对菜操作权限和顺序
2.信号量概述:
(1)在多任务操作系统中,多个进程/线程会同时运行。多个任务可能为了完成同一个事情而相互协作,这样就形成了任务之间的同步关系。同样,不同任务之间为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态,这样就是任务之间的互斥关系。
(2)任务之间的互斥与同步关系存在的根源在于临界资源。临界资源是指在同一个时刻只允许有限个(通常只有一个)任务可以访问(读)或者修改(写)的资源。通常包括硬件资源(处理器、内存、存储器以及其他外围设备等)和软件资源(共享代码段、共享结构体和变量)。访问临界资源的代码称为临界区。
(3)信号量是用来解决进程/线程之间的同步与互斥问题的一种通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(PV操作)。其中,信号量对应于某一种资源,取一个非负的整数值。信号量值指的是当前可用的该资源的数量,若它等于0则意味着目前没有可用的资源。
3.PV原子操作的具体定义:
(1)P操作:
如果有可用的资源(信号量的值大于0),则占用一个资源(信号量值减1,进入临界区代码);如果没 有可用的资源(信号量值等于0),则被阻塞,直到系统将资源分配给该任务(进入等待队列,一直 等到有资源的时候被唤醒)。
(2)V操作:
如果该信号量的等待队列中有任务在等待资源,则唤醒一个阻塞任务,如果没有任务等待它,则 释放一个资源(信号量值加1)
注意:
最简单的信号量只有0和1两种值,这两种信号量被称为二值信号量。在本节中,主要讨论二值信号量。二值信号量的应用比较容易扩展到使用多值信号量的情况。
4.信号量编程:
步骤:
Linux下,使用有名信号量通常分为以下几个步骤:
(1)创建信号量或获得系统已经存在的有名信号量,此时需要调用sem_open()函数,不同的进程通过使用同一个信号量键值来获得同一个信号量。并初始化信号量的值。
(2)获取信号量的值,此时使用sem_getvalue()。
(3)信号量减一,使用sem_wait()。
(4)信号量加一,使用sem_post()。
(5)关闭信号量,使用sem_close()。
(1)sem_open:
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *pathname, int oflag);
/*
*函数名:sem_open
*函数功能:打开一个信号量,如果信号量不存在,则创建信号量
*函数参数:
* const char *pathname:信号量的名字(标识)
* int oflag:同文件IO的创建时的权限
* O_CREAT | O_RDWR
* mode_t mode:同文件权限
* unsigned in value:信号量的初始值
*函数返回值:sem_t *:成功返回信号量操作对象的指针
*/
sem_t *sem_open(const char *pathname, int oflag, mode_t mode, unsigned int value);
(2)sem_getvalue:
#include <semaphore.h>
/*
*函数名:sem_getvalue
*函数功能:获取当前信号量的值
*函数参数:
* sem_t *sem:信号量的操作指针
* int *sval:存储信号量值得内存首地址
*函数返回值:int:成功返回0,失败返回-1
*/
int sem_getvalue(sem_t *sem, int *sval);
(3)sem_wait:
#include <semaphore.h>
/*
*函数名:sem_wait
*函数功能:信号量值减一
*函数参数:sem_t *sem:信号量的操作指针
*函数返回值:int:成功返回0,失败返回-1
*/
int sem_wait(sem_t *sem);
(4)sem_post:
#include <semaphore.h>
/*
*函数名:sem_post
*函数功能:信号量值加一
*函数参数:sem_t *sem:信号量的操作指针
*函数返回值:int:成功返回0,失败返回-1
*/
int sem_post(sem_t *sem);
(5)sem_close:
#include <semaphore.h>
/*
*函数名:sem_close
*函数功能:关闭一个有名信号量
*函数参数:sem_t *sem:信号量的操作指针
*函数返回值:int:成功返回0,失败返回-1
*/
int sem_close(sem_t *sem);
注意:
进程中使用的信号量,称之为有名信号量
编译时链接库 -lpthread
5.代码演示:
代码中以4个进程来代替前面需求中说的7个进程,进行简单的演示:
1.p1.c ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
int main()
{
//ftok
key_t key = -1;
key = ftok(".",'c');
if(key < 0)
{
puts("ftok error.");
return -1;
}
char buf[30];
//shmget
int shmid = shmget(key,sizeof(buf),IPC_CREAT | 0664);
if(shmid < 0)
{
puts("shmget error.");
return 0;
}
//shmat
char *shmaddr = NULL;
shmaddr = (char *)shmat(shmid, NULL, 0);
sem_t *mysem = NULL;
mysem = sem_open("mysem", O_CREAT | O_RDWR, 0664, 6);
if(NULL == mysem)
{
puts("sem_open error.");
shmdt(shmaddr);
return -1;
}
while(1)
{
int sem_v = 0;
sem_getvalue(mysem, &sem_v);
printf("sem_v:%d\n",sem_v);
if(sem_v == 6)
{
sem_wait(mysem);
puts(shmaddr);
sprintf(buf,"buy v");
memset(shmaddr,0,sizeof(buf));
memcpy(shmaddr,buf,strlen(buf));
}
sleep(3);
}
//shmdt
shmdt(shmaddr);
sem_close(mysem);
return 0;
}
2.p2.c ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
int main()
{
//ftok
key_t key = -1;
key = ftok(".",'c');
if(key < 0)
{
puts("ftok error.");
return -1;
}
char buf[30];
//shmget
int shmid = shmget(key,sizeof(buf),IPC_CREAT | 0664);
if(shmid < 0)
{
puts("shmget error.");
return 0;
}
//shmat
char *shmaddr = NULL;
shmaddr = (char *)shmat(shmid, NULL, 0);
sem_t *mysem = NULL;
mysem = sem_open("mysem", O_CREAT | O_RDWR, 0664, 6);
if(NULL == mysem)
{
puts("sem_open error.");
shmdt(shmaddr);
return -1;
}
while(1)
{
int sem_v = 0;
sem_getvalue(mysem, &sem_v);
printf("sem_v:%d\n",sem_v);
if(sem_v == 5)
{
sem_wait(mysem);
puts(shmaddr);
sprintf(buf,"brush v");
memset(shmaddr,0,sizeof(buf));
memcpy(shmaddr,buf,strlen(buf));
}
sleep(3);
}
//shmdt
shmdt(shmaddr);
sem_close(mysem);
return 0;
}
3.p3.c ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
int main()
{
//ftok
key_t key = -1;
key = ftok(".",'c');
if(key < 0)
{
puts("ftok error.");
return -1;
}
char buf[30];
//shmget
int shmid = shmget(key,sizeof(buf),IPC_CREAT | 0664);
if(shmid < 0)
{
puts("shmget error.");
return 0;
}
//shmat
char *shmaddr = NULL;
shmaddr = (char *)shmat(shmid, NULL, 0);
sem_t *mysem = NULL;
mysem = sem_open("mysem", O_CREAT | O_RDWR, 0664, 6);
if(NULL == mysem)
{
puts("sem_open error.");
shmdt(shmaddr);
return -1;
}
while(1)
{
int sem_v = 0;
sem_getvalue(mysem, &sem_v);
printf("sem_v:%d\n",sem_v);
if(sem_v == 4)
{
sem_wait(mysem);
puts(shmaddr);
sprintf(buf,"cut v");
memset(shmaddr,0,sizeof(buf));
memcpy(shmaddr,buf,strlen(buf));
}
sleep(3);
}
//shmdt
shmdt(shmaddr);
sem_close(mysem);
return 0;
}
4.p4.c ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
int main()
{
//ftok
key_t key = -1;
key = ftok(".",'c');
if(key < 0)
{
puts("ftok error.");
return -1;
}
char buf[30];
//shmget
int shmid = shmget(key,sizeof(buf),IPC_CREAT | 0664);
if(shmid < 0)
{
puts("shmget error.");
return 0;
}
//shmat
char *shmaddr = NULL;
shmaddr = (char *)shmat(shmid, NULL, 0);
sem_t *mysem = NULL;
mysem = sem_open("mysem", O_CREAT | O_RDWR, 0664, 6);
if(NULL == mysem)
{
puts("sem_open error.");
shmdt(shmaddr);
return -1;
}
while(1)
{
int sem_v = 0;
sem_getvalue(mysem, &sem_v);
printf("sem_v:%d\n",sem_v);
if(sem_v == 3)
{
sem_wait(mysem);
puts(shmaddr);
sprintf(buf,"do v");
memset(shmaddr,0,sizeof(buf));
memcpy(shmaddr,buf,strlen(buf));
sem_post(mysem);
sem_post(mysem);
sem_post(mysem);
sem_post(mysem);
}
sleep(3);
}
//shmdt
shmdt(shmaddr);
sem_close(mysem);
return 0;
}
三、System V消息队列:
1.概念:
消息队列:
顾名思义,消息队列就是一些消息的列表。用户可以在消息队列中添加消息和读取消息等。从这个观点上看,消息队列具有一定的FIFO特性,但是它可以实现消息的随机查询,比FIFO具有更大的优势。同时,这些消息又存在于内核中,由“队列ID”来标识。
2.消息队列的实现步骤
消息队列的实现,包括创建或打开消息队列,添加消息,读取消息和控制消息队列这四种操作。
(1)其中创建或打开消息队列使用的函数是msgget(),这里创建的消息队列的数量会受到系统消息队列数量的限制。
(2)添加消息使用的函数是msgsnd(),它把消息添加到已经打开的消息队列的末尾;
(3)读取消息使用的函数是msgrcv(),它把消息从消息队列中取走,与FIFO不同的是,这里可以取走指定的某一条消息
(4)控制消息队列使用的函数时msgctl(),它可以完成更多项功能。
(1)msgget:
需要添加的头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:int msgget(key_t key, int msgflg);
函数参数:
key:消息队列的键值,多个进程可以通过它来访问同一个消息队列,其中有个特殊值 IPC_PRIVATE。它用于创建当前进程的私有消息队列。
msgflg:权限标志位
使用的是IPC_CREAT, 同共享内存
函数返回值:成功:消息队列ID
失败:-1
(2)msgsnd:
需要添加的头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数参数:
msgid:消息队列的ID
msgp:指向消息结构的指针,该消息结构msgbuf通常为:
struct msgbuf
{
long mtype; /*消息类型,该结构必须从这个域开始*/
char mtext[L]; /*消息正文*/
}
msgsz:消息正文的字节数(不包括消息类型指针变量)
msgflag:
IPC_NOWAIT:若消息无法立即送达(比如:当前消息队列已满),函数会立即返回
0:msgsnd调用阻塞直到发送成功为止
函数返回值:成功:0
失败:-1
(3)msgrcv:
需要添加的头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
函数参数:
msgid:消息队列的ID
msgp:指向消息结构的指针,该消息结构msgbuf通常为:
struct msgbuf
{
long mtype; /*消息类型,该结构必须从这个域开始*/
char mtext[L]; /*消息正文*/
}
msgsz:消息正文的字节数(不包括消息类型指针变量)
msgtyp:
0:接收消息队列中第一个消息
大于0:接收消息队列中第一个类型为msgtyp的消息
小于0:接收消息队列中第一个类型值不小于msgtyp绝对值且类型值又最小的消息
msgflag:
MSG_NOERROR:若返回的消息比msgsz字节多,则消息就会被截短到msgsz字节,且 不通知消息发送进程。
IPC_NOWAIT:若消息队列中并没有相应类型的消息可以接收,函数会立即返回
0:msgsnd调用阻塞直到接收一条相应类型的消息为止
函数返回值:成功:读取的字节数
失败:-1
(4)msgctl:
需要添加的头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数参数:
msgid:消息队列的ID
cmd:
IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中
IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm域(IPC操作权限描述结构)值。 这个值取自buf参数。
IPC_RMID:从系统内核中删除消息队列
buf:描述消息队列的msqid_ds结构类型变量的首地址
函数返回值:成功:0
失败:-1
3.代码实现
1.read_shm.c ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
typedef struct book{
char id[10];
char name[30];
float price;
}BOOK;
int main()
{
//ftok
key_t key = -1;
key = ftok(".",'a');
printf("key:%d\n",key);
if(key == -1)
{
puts("ftok error.");
return -1;
}
//shmget
int shmid = -1;
shmid = shmget(key, sizeof(BOOK)*2, IPC_CREAT | 0664);
if(shmid == -1)
{
puts("shmget error.");
return -1;
}
printf("shmid:%d\n",shmid);
//shmat
BOOK *shmaddr = NULL;
shmaddr = (BOOK *)shmat(shmid,NULL,0);
if(NULL == shmaddr)
{
puts("shmat error.");
return -1;
}
int i = 0;
for(i = 0; i < 2; i++)
{
printf("id:%s\tname:%s\tprice:%f\n",shmaddr[i].id, \
shmaddr[i].name, shmaddr[i].price);
}
//shmdt
shmdt(shmaddr);
return 0;
}
2.writep.c ---------------------------------------------------------------------------------------如下:
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
typedef struct book{
char id[10];
char name[30];
float price;
}BOOK;
int main()
{
BOOK arr[2] = {
{"001","Chinese",23.7},
{"002","math",20.5}
};
printf("sizeof(arr):%d\n",sizeof(arr));
//ftok
key_t key = -1;
key = ftok(".",'a');
printf("key:%d\n",key);
if(key == -1)
{
puts("ftok error.");
return -1;
}
//shmget
int shmid = -1;
shmid = shmget(key, sizeof(arr), IPC_CREAT | 0664);
if(shmid == -1)
{
puts("shmget error.");
return -1;
}
//shmat
BOOK *shmaddr = NULL;
shmaddr = (BOOK *)shmat(shmid,NULL,0);
if(NULL == shmaddr)
{
puts("shmat error.");
return -1;
}
memcpy(shmaddr, arr, sizeof(arr));
//shmdt
shmdt(shmaddr);
return 0;
}
四、进程间通信的总结
进程间通信机制使用范围比较:
1.大量数据共享:
(1)有名管道:
主要用于非亲缘间进程通信
在文件系统中有文件节点
文件大小为0
(2)无名管道:
主要用于亲缘间进程通信
半双工
管道破裂
读阻塞
写阻塞
不能使用lseek
(3)消息队列:
非亲缘进程间通信
可以接收某一种类型的信息
能够通信的信息较为复杂
(4)共享内存:
非亲缘进程间通信
好操作
需要配套的同步/互斥机制
2.同步和互斥机制:
(1)信号:
主要实现异步通知
非亲缘/亲缘间进程通信都可
信号处理函数中不能处理较为复杂的内容
无法共享大量信息
(2)信号量:
主要用于同步/互斥问题的解决
不能传递复杂消息
没有优先级
(3)消息队列:
有消息类型,可以实现同步和互斥机制
能够传递复杂消息
有优先级
能进行信息共享
读取顺序随机