不久前接了个外包,需要跟nodejs后端配合控制外设。
Ubuntu下访问设备需要sudo,然而后端程序员发现无法sudo调用我写的上位机程序,所以方案就改成了共享内存+信号量。
由于信号量这个我以前写过,以为不会有什么问题,但由于当时掌握不透彻,还是出现了很多莫名其妙的问题。百度了很多,发现网上错误的太多,以讹传讹的也数不胜数,所以下面写一下正确的方法。
首先介绍下用到的函数与结构。
函数:
int semget(key_t key,int nsems,int semflg);
int semctl(int semid,int semnum,int cmd, union semun arg);
int semop(int semid,struct sembuf *sops,size_t nsops);
- key_t key : 为信号量的Key,自己随意设定。int nsems :需要的信号量的数量。 int semflg : 创建信号量的方式等。
- int semid :为调用semget返回的值,标示信号量。int semnum : 信号量的下标,0号,1号等等,如果后面是SETALL的话此处会被忽略,填什么都不影响。如果后面填的是SETVAL,则会将对应下标的信号量设置为对应的值。 union semun arg : 初始化的参数,见下文。
- int semid : 同上。 struct sembuf *sops:对信号量所做的操作,见下文。size_t nsops : 操作信号量的个数,一般一次操作一个所以写1。
结构:
struct sembuf
{
unsigned short int sem_num; /* semaphore number *//*想要操作的信号量下标*/
short int sem_op; /* semaphore operation *//*想要对信号量的操作,1为加1,-1为减1*/
short int sem_flg; /* operation flag *//*这里参数有两个,SEM_UNDO与一个IPC_NOWAIT*/
};
union semun
{
int val;/*设置单个信号量的值*/
struct semid_ds *buf;
unsigned short *array;/*设置多个信号量*/
};
我重点说下short int sem_flg这个属性。
SEM_UNDO,程序如果退出了,不管怎么退出,对信号量所有的操作都取消,但其他程序对信号量的操作不受影响。
IPC_NOWAIT,不会堵塞,信号量为0返回错误代码。
如果既不想用IPC_NOWAIT,也不想用SEM_UNDO,一定要将其设置为0,否则会有奇怪问题。
初始化代码:
void Process_Sem_FirstConfig()
{
unsigned short array[2]={1,0};
union semun sem_union;
int ShareMem_Flag = semget(650, 2, 0666 |IPC_CREAT);//获取Key为650的两个信号量组,没有则创建一个,权限为0666
sem_union.array = array;//初始化信号量组的初值,0号为1,1号为0
semctl(ShareMem_Flag, 0, SETALL, sem_union);//设置信号量
}
//信号量第一次创建,若非第一次创建只需要调用semget(650, 2, 0666)获得信号量标示即可。
//千万不要再次调用semctl设置信号量初值
PV操作:
void Process_Sem_Wait(int Sem_ID, int Sem_Index)//(信号量标示,信号量组中信号量下标)
{
struct sembuf sem_b;
sem_b.sem_num = Sem_Index; /*id*/
sem_b.sem_op = -1; /* P operation*/
sem_b.sem_flg = 0;
semop(Sem_ID, &sem_b, 1);
}
void Process_Sem_Post(int Sem_ID, int Sem_Index)
{
struct sembuf sem_b;
sem_b.sem_num = Sem_Index; /* id */
sem_b.sem_op = 1; /* V operation */
sem_b.sem_flg = 0;
semop(Sem_ID, &sem_b, 1);
}
销毁信号量
void Process_Sem_Delete(int ShareMem_Flag)//信号量标示
{
union semun sem_union;
semctl(ShareMem_Flag, 0, IPC_RMID, sem_union);
semctl(ShareMem_Flag, 1, IPC_RMID, sem_union);
}
倒是很简单,但如果研读不细的确很容易出错。