进程间通信

        进程间通信是指在不同进程之间传播或交换信息。

        IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享内存Socket、Streams等。其中Socket和Streams支持不同主机上的两个进程IPC。

一、管道

        管道,通常指无名管道,是UNIX系统IPC最古老的形式。

1.特点:

  1. 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  2. 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
  3. 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

2.原型:

       #include <unistd.h>

       int pipe(int fd[2]);//返回值:若成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开(巧记:我们说话时说01和读写,0对应读,1对应写)。如下图:

 要关闭管道只需将这两个文件描述符关闭即可。

3.例子:

        单个进程中的管道几乎没有任何用处。所以,通常调用pipe的进程接着调用fork,这样就创建了父进程与子进程之间的IPC通道。如下图所示:

4. 无名管道的编程实现:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(){
        int fd[2];
        int pid;
        char buf[128];
//      int pipe(int pipefd[2]);

        if(pipe(fd) == -1){
                printf("creat pipe failed\n");
        }
        pid = fork();
        if(pid < 0){
                printf("creat child failed\n");
        }else if(pid > 0){
                sleep(3);
                printf("this is father\n");
                close(fd[0]);//写入要关闭fd[0],打开fd[1]
                write(fd[1],"hello is from father",strlen("hello is from father"));
                wait();//防止父进程先退出
        }else{
                printf("this is child\n");
                close(fd[1]);//读出要关闭fd[1],打开fd[0]
                read(fd[0],buf,128);
                printf("read from father:%s\n",buf);
                exit(0);
        }
        return 0;
}

 二、FIFO

        FIFO,也称为命名管道,它是一种文件类型。

1.特点:

  1. FIFO可以在无关的进程之间交换数据,与无名管道不同。
  2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

2.原型:

#include <sys/types.h>
#include <sys/stat.h>
//返回值:成功返回0,出错返回-1
int mkfifo(const char *pathname, mode_t mode);

        其中的mode参数与open函数中的mode相同。一旦创建了一个FIFO,就可以用一般的文件I/O函数操作它。当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开此FIFO。类似的,只写open要阻塞到某个其他进程为读而打开它。
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <errno.h>
    #include <fcntl.h>
    
    //int mkfifo(const char *pathname, mode_t mode);
    int main(){
    
            if(mkfifo("./file",0600) == 0 && errno != EEXIST){
                    printf("mififo failed\n");
                    perror("why");
            }
            int fd = open("./file",O_RDONLY);
            printf("open file success\n");
            return 0;
    }
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <errno.h>
    #include <fcntl.h>
    
    //int mkfifo(const char *pathname, mode_t mode);
    int main(){
    
            int fd = open("./file",O_WRONLY);
            printf("write open file success\n");
            return 0;
    }
    
  • 若指定了O_NONBLOCK,则只读open立即返回。而只写open将出错返回-1如果没有进程已经为读而打开该FIFO,其errno置ENXIO。
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <errno.h>
    
    //int mkfifo(const char *pathname, mode_t mode);
    int main(){
    
            if(mkfifo("./file",0600) == -1 && errno != EEXIST){
                    printf("mififo failed\n");
                    perror("why");
            }
            return 0;
    }
    

3.例子:

        FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。下面的例子演示了使用FIFO进行IPC的过程:

write.c

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

//int mkfifo(const char *pathname, mode_t mode);
int main(){
        int fd = open("./file",O_WRONLY);
        printf("write open file success\n");
        char *str = "hello world!";
        while(1){
                write(fd,str,strlen(str));
                sleep(1);
        }
        close(fd);
        return 0;
}

read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

//int mkfifo(const char *pathname, mode_t mode);
int main(){
        char buf[30] = {0};
        int nread;
        if(mkfifo("./file",0600) == 0 && errno != EEXIST){
                printf("mififo failed\n");
                perror("why");
        }
        int fd = open("./file",O_RDONLY);
        printf("open file success\n");
        while(1){
                nread = read(fd,buf,30);
                printf("nread cout:%d,read buf:%s\n",nread,buf);
        }
        close(fd);
        return 0;
}

三、消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1.特点:

  1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2.原型:

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       //创建或打开消息队列:成功返回队列ID,失败返回-1
       int msgget(key_t key, int flag);
       //添加消息:成功返回0,失败返回-1
       int msgsnd(int msqid, const void *ptr, size_t size, int flag);
       //读取消息:成功返回消息数据的长度,失败返回-1
       int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
       //控制消息队列:成功返回0,失败返回-1
       int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msqid:消息队列的ID

在以下两种情况下,msgget将创建一个新的消息队列:

  • 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT(有直接获取,没有直接创建)标志位。
  • key参数为IPC_PRIVATE。

函数msgrcv在读取消息队列时,type参数有下面几种情况:

  • type == 0,返回队列中的第一个消息;
  • type > 0,返回队列中消息类型为type的第一个消息;
  • type < 0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,则取类型值最小的消息。

可以看出,type值非0用于以非先进先出次序读消息。也可以把type看做优先级的权值。

ftok函数:

函数原型:

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok( const char * fname, int id )

fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:

key_t key;

key = ftok(".", 1); 这样就是将fname设为当前目录。

id是子序号。虽然是int类型,但是只使用8bits(1-255)。

在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

 cmd中常用的IPC_RMID表示把消息队列的链表从内核中移除。

3.例子:

        下面写了一个简单的使用消息队列进行IPC的例子,服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来。

消息获取:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

//      int msgget(key_t key, int msgflg);
//      int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
//                    int msgflg);
struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[128];    /* message data */
};//消息结构

int main(){
        key_t key;
        key = ftok(".",'z');
        printf("key = %x\n",key);
        int msgId = msgget(key,IPC_CREAT|0777);//获取或创建
        struct msgbuf readbuf;
        struct msgbuf sendbuf = {988,"thank you!"};
        if(msgId == -1){
                printf("get que failure\n");
        }
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);//0:以默认方式读取,如果读取不到类型888的消息会一直阻塞
        printf("read form que msgsend:%s\n",readbuf.mtext);
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
//      int msgctl(int msqid, int cmd, struct msqid_ds *buf);
        msgctl(msgId,IPC_CREAT,NULL);//把消息队列的链表从内核中移除
        return 0;
}

消息发送: 

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

//      int msgget(key_t key, int msgflg);
//      int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[128];    /* message data */
};

int main(){
        key_t key;
        key = ftok(".",'z');
        printf("key = %x\n",key);
        int msgId = msgget(key,IPC_CREAT|0777);//IPC_CREAT有该队列直接获取,没有直接创建,0777可读可写可执行

        struct msgbuf Sendbuf = {888,"hello world!"};
        struct msgbuf readbuf;
        if(msgId == -1){
                printf("get que failure\n");
        }
        msgsnd(msgId,&Sendbuf,strlen(Sendbuf.mtext),0);
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),988,0);
        printf("read form que msgget:%s\n",readbuf.mtext);
        msgctl(msgId,IPC_CREAT,NULL);//把消息队列的链表从内核中移除

        return 0;
}

 四、共享内存

1.特点:

        共享内存指 (shared memory)在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存。由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存(Cache)。任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则不同的处理器可能用到不同的数据。共享内存是 Unix下的多进程之间的通信方法 ,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。

2.函数原型:

#include <sys/ipc.h>
#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(const void *addr);
//控制共享内存的相关消息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

[参数key]:由ftok生成的key标识,标识系统的唯一IPC资源。

 [参数size]:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。

[参数flag]:如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0;若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段。。

[参数shm_id]:共享存储段的标识符。

[参数*addr]:addr = 0,则存储段连接到由内核选择的第一个可以地址上(推荐使用),连接以后返回的地址。

[参数shmid]:共享存储段标识符。

[参数cmd]:指定的执行操作,设置为IPC_RMID时表示可以删除共享内存。

[参数*buf]:设置为NULL即可。

  1. 创建/打开共享内存:shmget,当用shmget函数创建一段共享内存时,必须指定其size;而如果引用一个已存在的共享内存,则将size指定为0。
  2. 映射:shmat,当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
  3. 数据交换
  4. 释放共享内存:shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
  5. 移除共享内存:shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

3.例子:

共享内存写入:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>

//      int shmget(key_t key, size_t size, int shmflg);
int main(){
        key_t key;
        key = ftok(".",1);
        int shmId = shmget(key,1024*4,IPC_CREAT|0666);//共享内存大小至少1M,且以M为单位,IPC_CREAT代表创建共享内存
        if(shmId == -1){
                printf("shmget failed\n");
        }
        char *shmaddr = shmat(shmId,0,0);//映射:定义一个指针变量指向共享内存
        printf("shmat ok!\n");
        strcpy(shmaddr,"hello world!");//把要写字符串复制金shmaddr
        sleep(5);//有些版本调用sleep需要包含#include <unistd.h>
        shmdt(shmaddr);
        shmctl(shmId,IPC_CREAT,0);//删除共享内存
        return 0;
}

共享内存读出:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>

//      int shmget(key_t key, size_t size, int shmflg);
int main(){
        key_t key;
        key = ftok(".",1);
        int shmId = shmget(key,1024*4,0);//直接获取不用创建
        if(shmId == -1){
                printf("shmget failed\n");
        }
        char *shmaddr = shmat(shmId,0,0);//映射:定义一个指针变量指向共享内存
        printf("shmat ok!\n");
        printf("data:%s\n",shmaddr);//直接打印shmaddr的内容

        shmdt(shmaddr);
        printf("Quit!\n");
        return 0;
}

五、信号

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

1.信号概述:

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

CLC@Embed_Learn:~/IPC$ kill -l
 1) SIGHUP	    2) SIGINT	     3) SIGQUIT	     4) SIGILL	     5) SIGTRAP
 6) SIGABRT	    7) SIGBUS	     8) SIGFPE	     9) SIGKILL	    10) SIGUSR1
11) SIGSEGV	    12) SIGUSR2	    13) SIGPIPE	    14) SIGALRM	    15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	    18) SIGCONT	    19) SIGSTOP	    20) SIGTSTP
21) SIGTTIN	    22) SIGTTOU	    23) SIGURG	    24) SIGXCPU	    25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	    28) SIGWINCH	29) SIGIO	    30) SIGPWR
31) SIGSYS	    34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

2.信号的处理:

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

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

        具体的信号默认动作可以使用 man 7 signal 来查看系统的具体定义。

3.信号使用用例:

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

CLC@Embed_Learn:~/IPC$ ps aux|grep a.out
CLC      15135  0.0  0.0   4156   348 pts/0    S+   16:40   0:00 ./a.out
CLC      15147  0.0  0.0  13588   948 pts/2    S+   16:49   0:00 grep --color=auto a.out
CLC@Embed_Learn:~/IPC$ kill -9 15135

CLC@Embed_Learn:~/IPC$ ./a.out
Killed

        对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通信的手段。

信号处理函数的注册:

        信号处理函数的注册不只一种方法,分为入门版和高级版

        1.入门版:函数 signal

        2.高级版:函数 sigaction

       #include <signal.h>

       typedef void (*sighandler_t)(int);//void型函数,里面一个int型的信号参数

       sighandler_t signal(int signum, sighandler_t handler);

[参数signum]:要被捕捉的信号 

[参数handler]:捕捉到信号后,调用运行该函数。

#include <signal.h>
#include <stdio.h>
//       typedef void (*sighandler_t)(int);

//       sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum){
        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSR1\n");
                        break;
        }

}
int main(){
        signal(SIGINT,handler);
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
        while(1);
        return 0;
}

信号处理发送函数:

        信号发送函数也不止一个,同样分为入门版和高级版

        1.入门版:kill

        2.高级版:sigqueue

       #include <sys/types.h>
       #include <signal.h>

       int kill(pid_t pid, int sig);

[参数pid]:要发送信号的进程pid号。

[参数sig]:发送的信号。

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
int main(int arg,char **argv){

        int signum;
        int pid;
        char cmd[128] = {0};
        signum = atoi(argv[1]);//atoi是转换为ASCII码函数,第一个参数为发送的信号
        pid = atoi(argv[2]);//第二个参数为进程的pid号
        printf("num = %d,pid = %d\n",signum,pid);
//      kill(pid,signum);//第一种最直接的做法
        sprintf(cmd,"kill -%d %d",signum,pid);//第二种调用system的作法
        system(cmd);
        printf("send signal ok\n");
        return 0;
}

信号注册函数——高级版

sigaction的函数原型:(可以通过 man sigaction 来查看)

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
    void     (*sa_handler)(int);//信号处理程序,不接受额外数据
    void     (*sa_sigaction)(int, siginfo_t *, void *);//信号处理程序,能接受额外数据
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

        sigaction 是一个系统调用,根据这个函数原型,我们不难看出,在函数原型中,第一个参数 signum 应该就是注册的信号的编号;第二个参数 ant 如果不为空说明需要对该信号有新的配置;第三个参数 oldact 如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

#include <signal.h>
#include <stdio.h>
//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
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);
                printf("get data = %d\n",info->si_value.sival_int);
                printf("from:%d\n",info->si_pid);
        }
}

int main(){
        struct sigaction act;
        printf("pid = %d\n",getpid());
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;//be able to get message

        sigaction(SIGINT,&act,NULL);
        while(1);
        return 0;
}

信号发送函数——高级版

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
    int   sival_int;
    void *sival_ptr;
};

#include <stdio.h>
#include <signal.h>
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;
        sigqueue(pid,signum,value);
        printf("done:%d\n",getpid());
//      int sigqueue(pid_t pid, int sig, const union sigval value);
        return 0;
}

六、信号量

        信号量与IPC结构不同,它是一个计数器。信号量用于实现进程间的互斥同步,而不是用于存储进程间通信数据。

1.特点:

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

2.原型:

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

        假设一个场景,一个房间有一把锁,第一个人进去房间办事拿走了盒子的钥匙(进门后上锁),第二个人来时,盒子没有钥匙,要等第一个人办完事情后把钥匙放回盒子,第二个人才能那盒子里的钥匙进房间办事。

        钥匙:信号量。        房间:临界资源。        信号量集:Linux AP      

        p操作:拿锁。         v操作:放回锁。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
//对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
//semid:信号量的id,numops:配置信号量个数,如果是1个,做一个sembuf semoparray[]
int semop(int semid, struct sembuf semoparray[], size_t numops);
//控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

        当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1;如果是引用一个现有的集合,则将num_sems指定为0。

        如果信号量集有两个或以上,需要做数组,man 手册例子

 struct sembuf sops[2];
           int semid;

           /* Code to set semid omitted */

           sops[0].sem_num = 0;        /* Operate on semaphore 0 */
           sops[0].sem_op = 0;         /* Wait for value to equal 0 */
           sops[0].sem_flg = 0;

           sops[1].sem_num = 0;        /* Operate on semaphore 0 */
           sops[1].sem_op = 1;         /* Increment value by one */
           sops[1].sem_flg = 0;

           if (semop(semid, sops, 2) == -1) {
               perror("semop");
               exit(EXIT_FAILURE);
           }
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.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;//信号量编号,一般默认为0,这句代码可写可不写
        set.sem_op = -1;//拿钥匙,钥匙的值-1
        set.sem_flg = SEM_UNDO;//当进程终止时会自动取消wait的操作
        semop(id,&set,1);
        printf("getkey\n");
}
void vPutBackKey(int id){
        struct sembuf set;
        set.sem_num = 0;
        set.sem_op = 1;//放回锁,钥匙的值+1
        set.sem_flg = SEM_UNDO;
        semop(id,&set,1);
        printf("put back the key\n");
}
int main(){
        key_t key;
        key = ftok(".",2);
        int semid;
                        //信号量集合中有一个信号量
        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){
                //去拿锁
                pGetKey(semid);
                printf("this is father\n");
                vPutBackKey(semid);
                //锁放回去
                semctl(semid,0,IPC_RMID);
        }else if(pid == 0){

                printf("this is child\n");
                vPutBackKey(semid);
        }else{
                printf("fork error\n");
        }
        return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从入门到捕蛇者说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值