进程间通信IPC(二)(共享内存、信号、信号量)

共享内存:
共享内存就是允许两个或多个进程共享一定的存储区。就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。因为数据不需要在客户机和服务器端之间复制,数据直接写到内存,不用若干次数据拷贝,所以这是最快的一种IPC。

注意:
共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如:信号量。
在这里插入图片描述
共享内存的优点:
当中共享内存的大致原理相信我们可以看明白了,就是让两个进程地址通过页表映射到同一片物理地址以便于通信,你可以给一个区域里面写入数据,理所当然你就可以从中拿取数据,这也就构成了进程间的双向通信,而且共享内存是IPC通信当中传输速度最快的通信方式没有之一,理由很简单,客户进程和服务进程传递的数据直接从内存里存取、放入,数据不需要在两进程间复制,没有什么操作比这简单了。再者用共享内存进行数据通信,它对数据也没啥限制。

最后就是共享内存的生命周期随内核。即所有访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在(除非显式删除共享内存区域对象),在内核重新引导之前,对该共享内存区域对象的任何改写操作都将一直保留;简单地说,共享内存区域对象的生命周期跟系统内核的生命周期是一致的,而且共享内存区域对象的作用域范围就是在整个系统内核的生命周期之内。

共享内存的缺陷:
共享内存也并不完美,共享内存并未提供同步机制,也就是说,在一个服务进程结束对共享内存的写操作之前,并没有自动机制可以阻止另一个进程(客户进程)开始对它进行读取。这明显还达不到我们想要的,我们不单是在两进程间交互数据,还想实现多个进程对共享内存的同步访问,这也正是使用共享内存的窍门所在。基于此,我们通常会用平时常谈到和用到 信号量来实现对共享内存同步访问控制。

操作共享内存的一般步骤:

  • 创建或打开共享内存
  • 将进程地址通过页表映射到物理地址
  • 进行数据交换
  • 释放共享内存

与共享内存有关的函数:

函数参数讲解

shmget()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);功能;创建或获取一个共享内存
参数:
(1)第一个参数key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。
(2)第二个参数size指定共享内存的大小,它的值一般为一页大小(4K也就是4M)的整数倍(未到一页,操作系统向上对齐到一页,但是用户实际能使用只有自己所申请的大小)。
(3)第三个参数shmflg是一组标志,创建一个新的共享内存,将shmflg 设置了IPC_CREAT标志后,共享内存存在就打开。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的共享内存,如果共享内存已存在,返回一个错误。一般我们会还或上一个文件权限.返回值:成功返回共享内存的ID,出错返回-1      

shmat()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
功能:创建共享存储段之后,将进程连接到它的地址空间,就是将进程地址映射到物理地址
参数:(1)第一个参数,shm_id是由shmget函数返回的共享内存标识。(2)第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。一般为0,表示连接到由内核选择的第一个可用地址上,否则,如果flag没有指定SHM_RND,则连接到addr所指定的地址上,如果flag为SHM_RND,则地址取整 (3)第三个参数,shm_flg是一组标志位,通常为0,若指定了SHM_RDONLY则以只读方式连接此段,否则以只写方式连接此段。
返回值:成功返回指向共享存储段的指针,并且内核将使其与该共享存储段相关的shnid_ds结构中的shm_nattch计数器加一(类似于引用计数)出错返回-1 

shmctl()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shm_id, int cmd, struct shmid_ds *buf); 
功能:操作共享内存
参数:(1)第一个参数,shm_id是shmget函数返回的共享内存标识符。(2)第二个参数,cmd是要采取的操作,它可以取下面的三个值 :    IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。    IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值    IPC_RMID:删除共享内存段(3)第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。(如果第二个参数是IPC_RMID,则这个参数返回的是结束共享内存时的一些信息,不关心可以设为NULL)shmid_ds结构至少包括以下成员:struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };  
返回值:调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

shmdt()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr); 
功能:该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。该操作不从系统中删除标识符和其数据结构,要显示调用shmctl(带命令IPC_RMID)才能删除它。
参数:(1)addr参数是以前调用shmat时的返回值
返回值:成功返回0,出错返回-1

发送端小demo:

#include <sys/shm.h>
#include<string.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{key_t key;int shmid;char* atreturn;key=ftok(".",6);shmid=shmget(key,1024*4,IPC_CREAT|0666);if(shmid==-1){printf("creat/open fail\n");perror("why");exit(-1);}atreturn=shmat(shmid,0,0);if(!strcmp(atreturn,"-1")){printf("at fail\n");exit(-1);}strcpy(atreturn,"你好");sleep(5);shmdt(atreturn);shmctl(shmid,IPC_RMID,NULL);printf("quite\n");return 0;
}

接收端小demo:

#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{key_t key;int shmid;char* atreturn;key=ftok(".",6);shmid=shmget(key,1024*4,IPC_CREAT|0666);if(shmid==-1){printf("creat/open fail\n");perror("why");exit(-1);}atreturn=shmat(shmid,0,0);if(!strcmp(atreturn,"-1")){printf("at fail\n");exit(-1);}printf("读取到:%s\n",atreturn);shmdt(atreturn);return 0;
}

以上两个demo没有用到信号量对共享内存进行控制,接下来将介绍信号和信号量

信号的基本概念

什么是信号?

  • 日常生活中,当我们走到马路上时,看到的绿灯是一种信号,它能提示我们怎样安全的过马路。又比如,新学期开始学校给每个班发的课表也是一种信号,它能提示同学们在适当的时间地点去上相应的课程而不是虚度光阴……生活中其实我们忽略了很多信号,正是由于这些信号的存在,才使得我们的生活方便而有序。
    总结一下你会发现信号是什么,信号就是当你看到它是知道它是什么,并且知道看到信号之后应该做什么,至于你遵不遵守就是你自己的事了,计算机中的信号也不例外。
  • 计算机中的信号
    同日常生活中的信号一样,计算机在收到信号之后,并不一定会立即处理它,它会将收到的信号记录在其相应进程的PCB中的信号部分,等待合适的时间再去处理它。换句话说,一个进程是否收到信号,需要查看其进程PCB中的信号信息,给进程发信号实则是向进程PCB中写入信号信息。同时,我们的操作系统是很智能的,当任何一个进程接收到任何一个信号时,操作系统会自动地知道各信号应作何处理。对于linux来说实际信号就是软中断。

信号概述:

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

    在这里插入图片描述

  • SIGHUP是挂起的意思、SIGINT是中断的意思就是ctrl+c的信号、SIGQUIT是退出的意思、SIGILL是出现问题的意思、SIGABRT是丢弃的意思、SIGBUS总线信号、SIGKILL杀死进程信号、SIGALRM是闹钟信号、SIGSTOP是停止程序信号…等等

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

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。
  • 可以通过kill -9 加进程号杀死进程

信号处理函数的注册:

  • 入门版:函数signal
  • 高级版:函数sigaction

信号发送函数:

  • 入门版:kill
  • 高级版:sigqueue

低级版本:

signal()函数:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:使用SIG_IGN这个宏就是表示将信号忽略掉参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(64)。其实这些信号时系统定义的宏。参数2:我们处理的方式(是系统默认还是忽略还是捕获)。handler函数的参数就是第一个参数signum,就是捕获到的信号。这个参数要求是指向函数的指针,就是函数名。
返回值:它返回一个函数指针这里的typedef就是定义了一种函数指针sighandler_t。typedef在语句中有变量的功能,也有定义类型的功能,如果typedef后面只跟了一个新的类型那么它就是定义类型的功能

关于typedef void ( * sighandler_t)(int)的理解

signal使用示例:

#include<stdio.h>
#include <signal.h>
void handler(int sig)
{switch(sig){case 2:printf("接受到ctrl+c\n");break;case 9:printf("接受到kill指令\n");break;case 10:printf("接受到SIGUSR1指令\n");break;}
}
int main()
{signal(SIGINT,handler);signal(SIGKILL,handler);signal(SIGUSR1,handler);while(1);return 0;
}
程序可以捕获ctrl+c(SIGINI)、SIGUSR1和SIGKILL指令
捕获SIGINI和SIGUSR1指令后程序不退出,但是捕获SIGKILL
指令后程序会强制退出。kill -9 +进程ID  表示:将序号9信号(SIGKILL)发送给进程号为ID的进程
kill -10 +进程ID  表示:将序号10信号(SIGUSR1)发送给进程号为ID的进程
kil +进程ID  表示:杀死该进程

kill()函数:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);函数参数:pid:指定进程的进程ID,注意用户的权限,比如普通用户不可以杀死1号进程(init)。pid>0:发送信号给指定进程pid=0:发送信号给与调用kill函数进程属于同一进程组的所有进程pid<0:发送信号给pid绝对值对应的进程组pid=-1:发送给进程有权限发送的系统中的所有进程
sig参数:建议使用信号名(宏名)而不是信号编号,因为涉及到跨平台的程序时,可能因为不同平台信号编号不同会导致错误。
返回值说明: 成功执行时,返回0。失败返回-1,errno被设为以下的某个值 EINVAL:指定的信号码无效(参数 sig 不合法) EPERM;权限不够无法传送信号给指定进程 ESRCH:参数 pid 所指定的进程或进程组不存在

atoi()函数:

#include <stdlib.h>
int atoi(const char *nptr);//字符串转int
long atol(const char *nptr);//字符串转long int
long long atoll(const char *nptr);//字符串转long long int

命令发送程序:

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include<string.h>
int main(int argc,char* argv[])
{int ret;char* cmd;cmd=(char*)malloc(128);if(argc!=3){printf("输入有误,请重新输入\n");}//int id=atoi(argv[2]);//int sig=atoi(argv[1]);/*ret=kill(pid,sig);if(ret>0){printf("命令发送成功\n");}*/printf("sig:%s,id:%s\n",argv[1],argv[2]);sprintf(cmd,"kill -%s %s",argv[1],argv[2]);printf("%s\n",cmd);system(cmd);return 0;
}

高级版本:

发信号思考:

  • 用什么发信号,sigqueue()函数
  • 既然都已经把信号发送过去了,为何不能再携带一些数据呢?
  • 怎么将内容放入信号

读信号思考

  • 用什么函数收消息并处理接收的信号,这里用到的是sigaction() 函数
  • 怎么读取信号所带的内容

sigaction()函数:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);第一个参数:signum参数指出要捕获的信号类型
第二个参数:是struct sigaction类型的结构体指针,act参数,指定新的信号处理方式
第三个参数:也是struct sigaction类型的结构体指针,oldact参数,记录原来对信号的处理方式如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
返回值:0 表示成功,-1 表示有错误发生。错误原因存于error中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表示能够接受数据};在这个结构体中,成员 sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。成员:sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。sa_flags 成员的值:1、包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。2、sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。3、re_restorer 成员则是一个已经废弃的数据域,不要使用。sa_flags 成员用于指定信号处理的行为,它可以是一下值的“按位或”组合。◆SA_RESTART:使被信号打断的系统调用自动重新发起。◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。◆ SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式。◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

关于上面提到的void ( * sa_sigaction)(int, siginfo_t * , void * ):

void* 是接收到信号所携带的额外数据,通过判断他是否为0来决定是否进行下面的操作
而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息。siginfo_t {int      si_signo;    /* Signal number */int      si_errno;    /* An errno value */int      si_code;     /* Signal code */int      si_trapno;   /* Trap number that causedhardware-generated signal(unused on most architectures) */pid_t    si_pid;      /* Sending process ID发送者的PID */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 */
}
其中的成员很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息
同时,在si_int或者si_ptr成员中也保存了对应的数据。

sigqueue()函数:

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {int   sival_int;void *sival_ptr;//发送字符串只能在 共享内存或者 同一程序下才可以发送};使用这个函数之前,必须要有几个操作需要完成:
1、使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
2、sigaction 结构体中的 sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。
3、sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展)
对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。
但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。

sigqueue()发送信号程序demo:

#include<stdio.h>
#include <signal.h>
#include <stdlib.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char*argv[])
{union sigval value;value.sival_int=10;int signum=atoi(argv[1]);int pid=atoi(argv[2]);if(argc!=3){printf("输入有误\n");}sigqueue(pid,signum,value);printf("my pid:%d\n",getpid());return 0;
}
注意:
发送时因为需要使用联合体,那么一次要么发送整型,要么发送字符串
还有一点需要注意:发送字符串只能在 共享内存或者同一程序下才可以发送

sigaction()接收端demo:

#include<stdio.h>
#include <signal.h>
#include<stdlib.h>
void action(int signum,siginfo_t *info,void* context)
{printf("get signum:%d\n",signum);if(context !=NULL){printf("发送命令的进程号是:%d\n",info->si_pid);printf("接受到的整数是:%d\n",info->si_int);printf("接受到si_value的整数是:%d\n",info->si_value.sival_int);}else{printf("没有接受到数据\n");}
}
int main()
{struct sigaction act;act.sa_flags=SA_SIGINFO;act.sa_sigaction=action;sigaction(SIGUSR1,&act,NULL);while(1);return 0;
}

信号量:

什么是信号量?

  • 信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
  • 信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待和发送信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。
  • 临界资源:多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。
  • 信号量集:所谓信号量集,就是由多个信号量组成的一个数组。作为一个整体,信号量集中的所有信号量使用同一个等待队列。Linux的信号量集为进程请求多个资源创造了条件。Linux规定,当进程的一个操作需要多个共享资源时,如果只成功获得了其中的部分资源,那么这个请求即告失败,进程必须立即释放所有已获得资源,以防止形成死锁。
  • 原子性:保证某个操作是一个整体,要么全部做完,要么根本不做,不会出现第三种状态。同理,我们用信号量保证我们访问临界资源的操作是原子操作,而操作系统会为我们保证信号量的变动也是原子操作。这就是为什么信号量能够达到互斥的效果。对信号量的操作被称为P、V操作,这是荷兰科学家迪杰斯特拉提出的,在荷兰语里,P是申请资源,V是释放资源的意思。所以执行P操作,信号量的值会减少,执行V操作,信号量的值会增加。
  • PV操作的含义:PV操作由P操作原语和V操作原语组成(原语是不可中断的过程),针对信号量进行相应的操作。
  • PV操作的定义:其中S表示信号量的值,P表示P操作,V表示V操作。
  • P(S):①将信号量S的值减1,即进行S = S-1;②如果S < 0,则该进程进入阻塞队列; ③如果S >= 0, 则该进程继续执行; ④执行一次P操作其实就是意味请求分配一个资源,所以针对②和③来说就好理解了,当信号量的值小于0,那么就表示没有可用资源,那么进程就只能进行等待其他拥有该资源的进程释放资源之后,才能进行执行;当信号量大于0的时候,那么表示还有足够的资源,所以,当前进程就可以继续执行;
  • V(S):①将信号量S的值加1,即 S = S + 1; ②如果S > 0,则该进程继续执行; ③如果S < 0, 则释放阻塞队列中的第一个等待信号量的进程; ④执行一次V操作其实就是意味释放一个资源,所以针对②和③来说就好理解了,当信号量的值大于0,那么就表示有可用资源,那么表示信号量的资源足够进程进行申请,就不需要将进程进行放入到阻塞队列中;而当信号量小于0的时候,就表示针对这个信号量,还有其他的进程是已经进行了申请信号量的操作,而只是之前是无法满足进程获取资源的,简单点说,就是表示阻塞队列中还有其他的进程是执行了P操作,在等待信号量,所以,这样的话,就讲阻塞队列中的第一个等待信号量的进程进行处理即可;

特点:

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

最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。linux下的信号函数都是在通用的信号数组上进行操作,而不是在一个单一的二值信号量上进行操作。

信号量需要用到的函数:

semget()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建一个新的信号量或获取一个已经存在的信号量的键值。
参数:key_t 为整型值,用户可以自己设定。有两种情况:1.键值是IPC_PRIVATE,该值通常为0,意思就是不能用于毫无关系的 进程间通信。2.键值不是IPC_PRIVATE,我们可以指定键值,例如1234;也可以一个ftok()函数来取得一个唯一的键值。nsems 表示初始化信号量集中信号量的个数。比如我们要创建一个信号量,则该值为1.,创建2个就是2。semflg:信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL。IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取。IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误。返回值:成功返回信号量的标识码ID。失败返回-1如果用semget创建了一个新的信号量集对象时,则semid_ds结构成员变量的值设置如下:sem_otime设置为0。sem_ctime设置为当前时间。msg_qbytes设成系统的限制值。sem_nsems设置为nsems参数的数值。semflg的读写权限写入sem_perm.mode中。sem_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。

semctl()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);功能:在这个函数中我们可以删除信号量或初始化信号量,控制信号量的信息。
参数:semid:信号量的标志码(ID),也就是semget()函数的返回值semnum:  操作信号在信号集中的编号。从0开始。cmd:命令,表示要进行的操作。IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。IPC_RMID将信号量集从内存中删除。GETALL用于读取信号量集中的所有信号量的值。GETNCNT返回正在等待资源的进程数目。GETPID返回最后一个执行semop操作的进程的PID。GETVAL返回信号量集中的一个单个的信号量的值。GETZCNT返回这在等待完全空闲的资源的进程数目。SETALL设置信号量集中的所有的信号量的值。SETVAL设置信号量集中的一个单独的信号量的值。Semunion :第4个参数是可选的;semunion :union semun的实例。union semun {int val;          /*SETVAL用的值*/struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/unsigned short* array; /*SETALL、GETALL用的数组值*/struct seminfo *_buf;   /*为控制IPC_INFO提供的缓存*/} ;返回值:返回值:成功返回0,失败返回-1

semop()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);功能:用户改变信号量的值,也就是使用资源还是释放资源使用权。
参数:semid : 信号量的标识码。也就是semget()的返回值。sops指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:struct  sembuf{unsigned short  sem_num;//信号量集合中的信号量编号,0代表第1个信号量short  sem_op;//若sem_op>0进行V操作信号量值加val,表示进程释放控制的资源 /*若sem_op<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*//*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/short sem_flg; /*0 设置信号量的默认操作*//*IPC_NOWAIT 对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。*//*SEM_UNDO 程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定*/};sem_num:  进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作返回值:返回值:成功返回0,失败返回-1

利用信号量控制父子进程运行顺序示例:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
union semum{int val;          struct semid_ds* buf; unsigned short* array; struct seminfo *_buf; 
};void pGet(int id)
{struct sembuf set;set.sem_num=0;set.sem_op=-1;set.sem_flg=SEM_UNDO;semop(id,&set,1);printf("get recourse\n");
}
void vPut(int id)
{struct sembuf put;put.sem_num=0;put.sem_op=1;put.sem_flg=SEM_UNDO;semop(id,&put,1);printf("put recourse\n");
}
int main()
{key_t key;int semid,pid;union semum initsem;initsem.val=0;key=ftok(".",3);	semid=semget(key,1,IPC_CREAT|0600);semctl(semid,0,SETVAL,initsem);pid=fork();if(pid>0){pGet(semid);printf("这是父进程\n");vPut(semid);semctl(semid,0,IPC_RMID);}else if(pid==0){printf("这是子进程\n");vPut(semid);}else{printf("frok error\n");}return 0;
}

补充:
系统中共享内存、消息队列、信号的查看和删除

进程组:
进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号。每个进程组都有一个组长,进程组的ID和进程组长ID一致。

权限保护:
root用户可以发送信号给任何用户,而普通信号不可以向系统用户(的进程)或者其他普通用户(的进程)发送任何信号。普通用户只可以向自己创建的进程发送信号。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/272205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式理论CAP定理

CAP原则又称CAP定理&#xff0c;指的是在一个分布式系统中&#xff0c; Consistency&#xff08;一致性&#xff09;、 Availability&#xff08;可用性&#xff09;、Partition tolerance&#xff08;分区容错性&#xff09;&#xff0c;三者不可兼得。 998年&#xff0c;加州…

xmlWriter 以UTF-8格式写xml问题

dom4j中的XMLWriter提供以下几种构造方法&#xff1a; XMLWriter() XMLWriter(OutputFormat format) XMLWriter(OutputStream out) XMLWriter(OutputStream out, OutputFormat format) XMLWriter(Writer writer) XMLWriter(Writer writer, OutputFormat format) 最简单常…

linux线程(互斥锁、条件)

线程概念&#xff1a; 典型的UNIX/Linux进程可以看成只有一个控制线程&#xff1a;一个进程在同一时刻只做一件事情。有了多个控制线程后&#xff0c;在程序设计时可以把进程设计成在同一时刻做不止一件事&#xff0c;每个线程各自处理独立的任务。 进程是程序执行时的一个实例…

linux网络编程、socket编程

进程间通信&#xff1a; 特点&#xff1a;依赖于内核&#xff0c;造成缺陷——无法实现多机通信。 网络&#xff1a; 地址&#xff1a;由IP地址&#xff08;IP地址是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一个逻辑地址&#xf…

Linux文件和目录权限笔记

查看文件或者目录的权限命令&#xff1a;ls -al # -a 表示全部文件包含隐藏文件&#xff0c;-l 表示列出每个文件的详细信息比如执行 ls -altotal 115drwxr--x--- 4 root root 4096 Oct 24 02:07 install.log格式说明&#xff1a;通过 ls -al 格式化输出的文件详细信息&#x…

python基础:序列(列表、元组、字符串)、函数、字典、集合

Python语言运行环境&#xff1a; windowslinuxunixMacos等等 博客记录内容&#xff1a; Python3的所有语法、面向对象思维、运用模块进行编程、游戏编程、计算机仿真。 Python是什么类型的语言&#xff1a; Python是脚本语言&#xff0c;脚本语言(Scripting language)是电脑…

python基础(文件、异常、模块、类、对象)

文件&#xff1a; 打开文件使用open函数&#xff0c;open()的第一个参数是&#xff1a;要打开文件的路径&#xff0c;如果只传入文件名那么将在当前文件下查找文件并打开。第二个参数是&#xff1a;文件的打开模式&#xff0c;其他参数都是默认的。文件的打开模式如下图所示&a…

redis 和 memcached 的区别

redis 和 memcached 的区别 对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存&#xff0c;而且 redis 自身也越来越强大了&#xff01; redis支持更丰富的数据类型&#xff08;支持更复杂的应用场景&#xff09;&#xff1a;Redis不仅仅支持简…

树莓派入门(树莓派登录的几种方式)

什么是嵌入式&#xff1f; 嵌入式即嵌入式系统&#xff0c;IEEE&#xff08;美国电气和电子工程师协会&#xff09;对其定义是用于控制、监视或者辅助操作机器和设备的装置&#xff0c;是一种专用的计算机系统。国内普遍认同的嵌入式系统定义是以应用为中心&#xff0c;以计算…

Linux库概念及相关编程(动态库、静态库、环境变量)

分文件编程&#xff1a; 好处&#xff1a;分模块编程思想&#xff0c;功能和责任划分清楚便与调试&#xff0c;main函数简洁&#xff0c;代码易于阅读。编程时头文件有的是使用<>这个符号括起来的&#xff0c;有的是" "使用的是双引号&#xff0c;使用尖括号括…

kali扫描内网ip_Metasploit路由转发实现内网渗透

利用背景在渗透的过程中常常会遇到这种场景&#xff1a;我们已经通过web渗透拿下一台内网服务器&#xff0c;为了进一步进行内网渗透&#xff0c;我们会利用“沦陷主机”作为跳板进行进一步的内网渗透&#xff0c;扩大战果。现在假设的场景是此时我们已经拿下一台内网服务器的远…

.NET Core 3.0 中的新变化

译者&#xff1a;楚人Leo译文&#xff1a;http://www.cnblogs.com/leolion/p/10585834.html原文&#xff1a;https://msdn.microsoft.com/en-us/magazine/mt848631.aspx.NET Core 3.0 是 .NET Core 平台的下一主要版本。本文回顾 .NET Core 发展历史&#xff0c;并展示了它是如…

树莓派GPIO口的使用(外设相关开发WringPi库的使用,超声波、继电器)

树莓派的接口&#xff1a; 大而简单的类别&#xff1a;IO口&#xff0c;input和output是相对于主控芯片来说的&#xff0c;是根据MCU和外设之间的关系将IO口的功能分为output和input。当IO作为input使用时外设有&#xff1a;人体传感器、烟雾传感器、火焰传感器、振动传感器等…

volatile指令重排_有多少人面试栽到Volatile上?面试问题都总结到这儿了

Volatile关键字volatile 是Java虚拟机提供的 轻量级 的同步机制.何为 轻量级 呢&#xff0c;这要相对于 synchronized 来说。Volatile有如下三个特点。要搞清楚上面列举的名词 可见性 原子性 指令重排 的含义我们需要首先弄清楚JMM(Java内存模型是怎么回事)JMM规定了内存主要划…

Dubbo核心概念

节点角色规范 节点角色规格Provider提供者公开远程服务Consumer消费者致电远程服务Registry注册表负责服务发现和配置Monitor监视器计算服务调用的数量和耗时Container容器管理服务的生命周期 服务关系 Container负责启动&#xff0c;加载和运行服务Provider。ProviderRegiste…

良心推荐11款可以称得上“神器”的Windows工具集合

1、最快文件搜索工具 Everything&#xff1a;当之无愧的最强本地文件搜索神器&#xff0c;搜索任何关键词基本是秒速出现&#xff0c;比Windows自带的搜索快了太多&#xff0c;电脑文件比较多的人必备&#xff01; 2、专业软件卸载器 Revo Uninstaller Pro&#xff1a;Windows电…

LD3320语音识别模块二次开发及与树莓派间的通讯

实物图如下&#xff1a; 一般这种模块的资料厂家都会给&#xff0c;需要的话可以私信我发邮箱&#xff0c;下面介绍该模块的各种参数。型号&#xff1a;YS-LDV7名称&#xff1a;一体化语音识别模块规格&#xff1a;43*29.7MM供电电压&#xff1a;5V &#xff08;内部工作电压…

多生产者_你是生产者还是消费者?这决定了你的层次。

不知道你有没有注意到&#xff0c;每天乘坐地铁上下班的时候&#xff0c;大部分人都在刷剧、看视频、打游戏等等&#xff0c;总之都属于玩乐。用生产和消费的关系来看的话&#xff0c;其实这一大部分人都属于消费者&#xff0c;“时间和注意力”是他们用于交换的筹码&#xff1…

eclipse Android 开发基础 Activity 窗体 界面

eclipse Android 开发基础 新建工程 新建布局layout,new Android Activity就相当于窗体Form。 新建Activity自动生成src下同名的java代码。 public class Tform2activity extends Activity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(saved…