进程间通讯

简介:

进程间通讯方式有:

1.内存映射(mmap):

使用mmap函数将磁盘空间映射到内存

2.管道

3.信号

4.套接字(socket)

5.信号机制

通过进程中kill函数,去给另一个函数发送信号,另一个函数捕捉到该信号之后,重新定义该信号的操作,实现进程间通讯

6.system V IPC:

简介:

IPC对象包括:共享内存,消息队列,信号灯集

1.每个IPC对象都有唯一的ID与Key关联(通过访问key就知道访问的是哪一块IPC对象)

2.IPC对象创建后一直存在,直到被显式删除

3.属于内核中的数据结构

共享内存:

很老,但仍有应用

消息队列:

过时

信号灯集:

过时

方式1、内存映射

简介:

 使用系统调用函数mmap,将一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不需要再调用read,write。

(进程在内存中都有自己的内存空间,不能相互访问,即使内存地址相同也不能相互访问)

(同一个磁盘文件是可以被多个进程相互访问的,因此将磁盘文件映射到内存中的缓冲区,相当于直接在内存中对磁盘文件进行读写,实现用户空间和内核空间高校交互)

原本的进程间交互方式:

使用mmap系统调用函数后:

函数实现:

mmap函数:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

功能:创建共享内存映射

函数返回值:成功返回创建的映射区首地址,失败返回宏:MAP_FAILED( ((void *) -1) )(也就是-1),设置errno值

参数说明:

addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。

length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。

prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。

flags:由以下几个常值指定:

MAP_SHARED(共享的)(进程间通讯)

MAP_PRIVATE(私有的)(单个进程)(方便对文件快速读写)

MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正)

其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED 则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)(类似与无名管道)

fd:表示要映射的文件描述符。如果匿名映射写-1(不需要文件)。

offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。

注意事项:

(1) 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。

(2) 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。

当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。

(3) 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭,文件关闭后,使用修改映射内存中的数据,磁盘文件本身也会被修改。

(4) 用于映射的文件大小必须>0,当映射文件大小为0时

指定非0大小创建映射区,访问映射地址会报总线错误

指定0大小创建映射区,报非法参数错误(Invalid argument)

(5) 文件偏移量必须为0或者4K的整数倍(否则 会报非法参数Invalid argument错误).

(因为内存是按页分配的,一页为4k字节)

(6)可访问内存空间根据实际文件大小进行定义;和mmap函数参数映射的内存大小无关(只要mmap参数length不为0即可),但是大于文件实际大小的内存位置不会被写入文件

例:文件实际字节小于一页(也就是4k,4096字节),可访问内存就为一页(mmap参数length可以取不为0的任意数)

(7)文件大小代表映射的内存能写入的数据大小,文件中原有数据也会被映射到内存中去

munmap函数

int munmap(void *addr, size_t length);

返回值:成功返回0,失败返回-1,并设置errno值。

addr:调用mmap函数成功返回的映射区首地址

length:映射区大小(即:mmap函数的第二个参数)

注意事项:

相关函数:

lseek函数:

off_t lseek(int fd,off_t offset,int whence);

功能:查看文件长度,是一个在 C 语言中的系统调用,用于在文件中移动文件指针。

成功时,返回新的文件偏移量(从文件开头开始的字节数)。

失败时,返回 -1,并设置 errno 以指示错误。

fd:被查看文件的文件描述符

offset:在文件内的偏移量(也就是起始地址)

whence:一个整型常量,用于指定偏移量的引用点。

可取的值有:

SEEK_SET: 文件开头,offset 是相对于文件开头的字节偏移。

SEEK_CUR: 当前文件位置,offset 是相对于当前文件位置的字节偏移。

SEEK_END: 文件末尾,offset 是相对于文件末尾的字节偏移。

memcpy函数:

void *memcpy(void *dest, const void *src, size_t n);

功能:是 C 和 C++ 中的一个标准库函数,用于从一个内存位置复制数据到另一个内存位置。它定义在 <string.h> 头文件中,常用于处理内存块的拷贝。

无返回值

参数:

dest:指向要复制到的目标内存块的指针。

src:指向要复制的源内存块的指针。

n:要复制的字节数。

相关命令:

使用方式:

写:

#include<stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
int main() {
    void *addr;
    int fd;
    /*
     * 1.打开文件
     */
    fd=open("text",O_RDWR);
    if(fd<0) {
        perror("open");
        return 0;
    }
    /*
     * 2.映射文件到内存
     */
    addr=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(addr<0) {
        perror("mmap");
        return 0;
    }
    /*
     * 3.关闭文件
     */
    close(fd);
    /*
     * 4.读写数据-写
     */
    int i=0;
    while(i<4096) {
        memcpy(addr+i,"b",1);
        i++;
        sleep(1);
    }
    return 0;
}

读:

#include<stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
int main(){
	void *addr;
	int fd;
/*
 * 1.打开文件
 */
	fd=open("text",O_RDWR);
	if(fd<0){
		perror("open");
		return 0;
	}
/*
 * 2.映射文件到内存
 */
	addr=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(addr<0){
		perror("mmap");
		return 0;
	}
/*
 * 3.关闭文件
 */
	close(fd);
/*
 * 4.读写数据-读
 */
	while(1){
		printf("%s\n",(char *)(addr));
		sleep(1);
	}
	return 0;
}

注意事项:

方式2、system V IPC共享内存

简介:

1.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2.共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活。
3.由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用。

函数实现:

1.创建key

key_t  f tok(const char *path,  int id);

 #include  <sys/types.h>

 #include <sys/ipc.h>  

key_t  ftok(const char *path,  int proj_id);  

成功时返回合法的key值,失败时返回EOF  

path  存在且可访问的文件的路径  

proj_id  用于生成key的数字,范围1-255。

2.创建/打开共享内存

#include <sys/ipc.h>  

#include <sys/shm.h>  

int shmget(key_t key, int size, int shmflg);  

成功时返回共享内存的id,失败时返回EOF  

key  和共享内存关联的key,IPC_PRIVATE 或 ftok生成  

size 创建共享内存的大小(字节为单位)

shmflg 共享内存标志位,用于指定共享内存的创建方式和访问权限。

常用的标志包括:

IPC_CREAT: 如果指定的共享内存段不存在,则创建一个新的共享内存段。

IPC_EXCL: 如果指定的共享内存段已经存在,则调用失败。

权限标志位,如 0666,可以指定访问权限。

3.映射共享内存

即把指定的共享内存映射到进程的地址空间用于访问

#include <sys/ipc.h>  

#include <sys/shm.h>  

void  *shmat(int shmid, const void *shmaddr, int shmflg);  

成功时返回映射后的地址,失败时返回(void *)-1  

shmid   要映射的共享内存id  

shmaddr   映射后的地址, NULL表示由系统自动映射  

shmflg   标志位  0表示可读写;SHM_RDONLY表示只读

4.读写共享内存

5.撤销共享内存映射

#include <sys/ipc.h>  

#include <sys/shm.h>  

int  shmdt(void *shmaddr);  

成功时返回0,失败时返回EOF  

6.删除共享内存对象

#include <sys/ipc.h>  

#include <sys/shm.h>  

int  shmctl(int shmid, int cmd, struct shmid_ds *buf);  

成功时返回0,失败时返回EOF  

shmid   要操作的共享内存的id  

  • cmd: 操作的命令,决定了函数的行为。常用的命令包括:
    • IPC_RMID: 删除共享内存段。
    • IPC_STAT: 获取共享内存段的状态信息(存储在 struct shmid_ds 中)。
    • IPC_SET: 设置共享内存段的属性。
  • buf: 指向 struct shmid_ds 的指针,用于存储或提供状态信息。如果 cmd 是 IPC_STATbuf 应指向一个有效的 struct shmid_ds 结构体。如果 cmd 是 IPC_SETbuf 应指向一个包含要设置的新值的 struct shmid_ds 结构体;如果 cmd 是 IPC_RMID,则可以设置为 NULL。

相关函数:

strcpy函数

char *strcpy(char *dest, const char *src)

返回目标字符串 dest 的指针。

功能:函数是 C 语言中用于字符串复制的标准库函数,定义在 <string.h> 头文件中。它的基本功能是将一个字符串的内容复制到另一个字符串中。

参数:

dest:目标字符串,复制后的内容将存放在这里。这个字符串必须有足够的空间来存放源字符串及其结束符(\0)。

src:源字符串,内容将被复制的对象。

相关命令:

ipcs命令:

可以在终端查看共享内存、消息队列、信号量(都属于内核中数据结构)

touch命令:创建文件

使用方式:

读:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main() {
    key_t key;
    int shmid;
    void *addr;
    int dt;
    int ctl;
    /*
     * 1.生成key
     */
    key=ftok("keytest",34);
    if(key<0) {
        perror("ftok");
        return 0;
    }
    printf("key=%d\n",key);
    /*
     * 2.创建共享内存
     */
    shmid=shmget(key,512,IPC_CREAT|0666);
    if(shmid<0) {
        perror("shmget");
        return 0;
    }
    printf("ID=%d\n",shmid);
    /*
     * 3.映射共享内存
     */
    addr=shmat(shmid,NULL,0);
    if(addr<0) {
        perror("shmat");
        return 0;
    }
    printf("addr=%p\n",addr);
    /*
     * 4.读写内存-读
     */
    printf("%s\n",(char *)addr);
    /*
     * 5.撤销共享内存
     */
    dt=shmdt(addr);
    if(dt<0) {
        perror("shmdt");
        return 0;
    }
    /*
     * 6.删除共享内存
     */
    ctl=shmctl(shmid,IPC_RMID,NULL);
    if(ctl<0) {
        perror("shmctl");
    }
    return 0;
}

写:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main() {
    key_t key;
    int shmid;
    void *addr;
    /*
     * 1.生成key
     */
    key=ftok("keytest",34);
    if(key<0) {
        perror("ftok");
        return 0;
    }
    printf("key=%d\n",key);
    /*
     * 2.创建共享内存
     */
    shmid=shmget(key,512,IPC_CREAT|0666);
    if(shmid<0) {
        perror("shmget");
        return 0;
    }
    printf("ID=%d\n",shmid);
    /*
     * 3.映射共享内存
     */
    addr=shmat(shmid,NULL,0);
    if(addr<0) {
        perror("shmat");
        return 0;
    }
    printf("addr=%p\n",addr);
    /*
     * 4.读写内存-写
     */
    strcpy(addr,"hello");

}

注意事项:

1.写内存使如果创建了共享内存,读内存时就不需要再创建

2.共享内存创建后不会随着程序结束而消失,如果共享内存不被删除则一直存在,共享内存中数据一直也存在

3.使用shmdt撤销后,此处共享内存将不能被访问,但是该共享内存还存在,如果不在使用该共享内存,需要使用shmctl将共享内存删除

方式3、system V IPC消息队列

简介:

队列:先进先出(链表实现)(内核中的一种数据结构)

消息队列由消息队列 ID 来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
消息队列可以按照类型来发送/接收消息

函数实现:

1.打开/创建消息队列

 #include <sys/ipc.h>

 #include <sys/msg.h>

 int msgget(key_t key, int msgflg);

  成功时返回消息队列的id,失败时返回EOF

key: 消息队列的唯一标识符的键值。这个值可以通过ftok函数生成,它通常是一个整数,用于标识不同的消息队列。

msgflg: 控制消息队列创建和访问的标志,是一个位标志,可以使用以下几种选项组合:

IPC_CREAT: 如果消息队列不存在,则创建一个新的消息队列。

IPC_EXCL: 与IPC_CREAT一起使用,确保如果消息队列已存在,msgget将失败,而不是返回现有队列的ID。

其他标志,如权限位,可以控制访问权限(例如:0666 表示所有用户可读写)。

2.发送消息

#include <sys/ipc.h>

 #include <sys/msg.h>

 int msgsnd(int msgid, const void *msgp, size_t size, int msgflg);

  成功时返回0,失败时返回-1

 参数1:msgid   消息队列id
 参数2:msgp    消息缓冲区地址(msgT结构体地址)
参数:msgp:

消息格式:

typedef struct{

long msg_type;//必须是long型,表示消息的类型

char buf[128];

}msgT;

注意:

1.消息结构必须有long类型的msg_type字段,表示消息的类型。(与接收方关联)

2.消息长度不包括结构体中"消息类型long msg_type"的长度(也就是参数size=结构体长度-消息类型长度=buf消息长度)

3.发送端消息类型取值必须大于0

参数3:size    消息正文长度
参数4:msgflg   标志位 0 或 IPC_NOWAIT
参数:msgflg:

0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列

IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回

3.接收消息:

#include <sys/ipc.h>

 #include <sys/msg.h>

 int msgrcv(int msgid, void *msgp, size_t size, long msgtype,int msgflg);

  成功时返回收到的消息长度,失败时返回-1

参数1:msgid   消息队列id
参数2:msgp   消息缓冲区地址
参数3:size   指定接收的消息长度
参数4:msgtype   指定接收的消息类型   
参数msgtype:

msgtype=0:收到的第一条消息,任意类型都接收。

msgtype>0:收到的第一条 msg_type类型的消息。

msgtype<0:接收类型等于或者小于msgtype绝对值的第一个消息。

例子:如果msgtype=-4,只接受类型是1、2、3、4的消息

参数5:msgflg   标志位  
参数:msgflg:

0:阻塞式接收消息(如果没有消息,将会一直等待)

IPC_NOWAIT:没有消息也返回,返回错误码,此时错误码为ENOMSG

MSG_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息

4.控制消息队列:

#include <sys/ipc.h>

 #include <sys/msg.h>

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

  成功时返回0,失败时返回-1

  msgid    消息队列id

  cmd    要执行的操作  IPC_STAT / IPC_SET / IPC_RMID(删除)

  buf   存放消息队列属性的地址(删除操作时给空)

相关函数:

创建key

key_t  f tok(const char *path,  int id);

 #include  <sys/types.h>

 #include <sys/ipc.h>  

key_t  ftok(const char *path,  int proj_id);  

成功时返回合法的key值,失败时返回EOF  

path  存在且可访问的文件的路径  

proj_id  用于生成key的数字,范围1-255。

strcpy函数

char *strcpy(char *dest, const char *src)

返回目标字符串 dest 的指针。

功能:函数是 C 语言中用于字符串复制的标准库函数,定义在 <string.h> 头文件中。它的基本功能是将一个字符串的内容复制到另一个字符串中。

参数:

dest:目标字符串,复制后的内容将存放在这里。这个字符串必须有足够的空间来存放源字符串及其结束符(\0)。

src:源字符串,内容将被复制的对象。

相关命令:

ipcs命令:

可以在终端查看共享内存、消息队列、信号量(都属于内核中数据结构)

使用方式:

发送端:

1 申请Key

2打开/创建消息队列   msgget

3向消息队列发送消息   msgsnd

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

#define MSGLEN (sizeof(msgT)-sizeof(long))
typedef struct {
    long msg_type;
    char buf[128];
} msgT;
int main() {
    key_t key;
    int msgid;
    int ret;
    msgT msg;
    /*
     * 1.创建key
     */
    key=ftok(".",100);
    if(key<0) {
        perror("ftok");
        return 0;
    }
    /*
     * 2.创建消息队列
     */
    msgid=msgget(key,IPC_CREAT|0666);
    if(msgid<0) {
        perror("msgget");
        return 0;
    }
    /*
     * 3.发送消息
     */
    msg.msg_type=1;
    strcpy(msg.buf,"this msg type 1");
    ret=msgsnd(msgid,&msg,MSGLEN,0);
    if(ret<0) {
        perror("msgsnd");
        return 0;
    }
}

接收端:

1打开/创建消息队列   msgget

2从消息队列接收消息   msgrcv

3 控制(删除)消息队列   msgctl

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

#define MSGLEN (sizeof(msgT)-sizeof(long))
typedef struct{
	long msg_type;
	char buf[128];
}msgT;
int main(){
	key_t key;
	int msgid;
	int ret;
	msgT msg;
/*
 * 1.创建key
 */
	key=ftok(".",100);
	if(key<0){
		perror("ftok");
		return 0;
	}
/*
 * 2.创建消息队列
 */
	msgid=msgget(key,IPC_CREAT|0666);
	if(msgid<0){
		perror("msgget");
		return 0;
	}
/*
 * 3.接收消息
 */
	ret=msgrcv(msgid,&msg,MSGLEN,0,0);
	if(ret<0){
		perror("msgrcv");
		return 0;
	}
	printf("%d %s\n",(int)msg.msg_type,msg.buf);
}

循环接收:

 /*
     * 3.接收消息
     */
    while(1) {
        ret=msgrcv(msgid,&msg,MSGLEN,0,0);
        if(ret<0) {
            perror("msgrcv");
            return 0;
        }
        printf("%d %s\n",(int)msg.msg_type,msg.buf);
    }

删除消息队列:

ret=msgctl(msgid,IPC_RMID,NULL);
	if(ret<0){
		perror("msgctl");
		return 0;
	}

注意事项:

1.执行一次msgsnd就会将消息发送一次

2.执行一次msgrcv就会将消息接收一次

方式4、管道

4.1、无名管道

特点:

1.只能用于具有亲缘关系的进程之间的通讯(父子进程)

2.单工的通信模式,具有固定的读端和写端(一个无名管道只能一端写,一端读。如果要实现双工通讯(两端都可以同时作为读端和写端)就需要建立两条无名管道)

3.无名管道创建时会返回两个文件描述符,分别用于读写管道

创建无名管道


#include <unistd.h>
int pipe(int pfd[2]);
成功时返回 0,失败时返回 EOF
pfd 包含两个元素的整形数组,用来保存文件描述符
pfd[0] 为读描述符
pfd[1] 为写描述符

代码实现:

父子进程:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int pfd[2];
    int ret;
    char buf[20]= {0};
    pid_t pid;
    ret=pipe(pfd);
    if(ret<0) {
        perror("pipe");
        return 0;
    }
    pid=fork();
    if(pid<0) {
        perror("fork");
        return 0;
    }
    /*
     * 子进程写
     */
    else if(pid==0) {
		close(pfd[0]);
        while(1) {
            strcpy(buf,"hhhahahah");
            write(pfd[1],buf,strlen(buf));
            sleep(1);
        }
    }
    /*
     * 父进程读
     */
    else {
		close(pfd[1]);
        while(1) {
            ret=read(pfd[0],buf,20);
            if(ret>0) {
                printf("%s\n",buf);
            }
        }
    }
    return 0;
}
多进程:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int pfd[2];
    int ret,i;
    char buf[40]= {0};
    char endbuf[40]= {0};
    pid_t pid;
        ret=pipe(pfd);
        if(ret<0) {
            perror("pipe");
            return 0;
        }
 		for(i=0; i<2; i++) {
        pid=fork();
        if(pid<0) {
            perror("fork");
            return 0;
        }
        /*
         * 子进程
         */
        else if(pid==0) {
            break;
            //跳出循环,子进程不再执行fork
        }
        /*
         * 父进程
         */
        else {
        }
    }
    /*
     * 父进程代码
     */
    if(i==2) {
        close(pfd[1]);
        while(1) {
            ret=read(pfd[0],endbuf,40);
            if(ret>0) {
                printf("%s\n",endbuf);
            }
        }
        return 0;
    }
    /*
     * 第二个子进程代码
     */
    if(i==1) {
        close(pfd[0]);
        while(1) {
            strcpy(buf,"This is second");
            write(pfd[1],buf,strlen(buf));
            sleep(1);
        }
        return 0;
    }
    /*
     * 第一个子进程代码
     */
    if(i==0) {
        close(pfd[0]);
        while(1) {
            strcpy(buf,"This is one");
            write(pfd[1],buf,strlen(buf));
            usleep(10000);
        }

        return 0;
    }
    return 0;
}


流程:

文件描述符0,1,2分别被标准输出、标准输入、标准错误占用,只能从文件描述符3开始

创建子进程,由于子进程继承了父进程打开的文件描述符,所以父子进程就可以通过创建的管道进行通信。

如果父进程使用写端,就将父进程中的读端关闭,子进程的写端关闭,反之如此

相关函数:

fork函数
write函数
read函数
strcpy函数
sleep函数
usleep函数

注意事项:

1.在同一个进程中是可以又读又写的,只不过无名管道用于亲缘关系进程通讯,因此一般不用于同一个进程又读又写

2.管道可以用于大于两个进程共享

3.创建的管道固定为64k字节

4.管道中的数据,只要读出来了,就不存在于管道中了

无名管道的读写特性:


① 读管道:
1. 管道中有数据,read 返回实际读到的字节数。
2. 管道中无数据:
(1) 管道写端被全部关闭,read 返回 0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read 阻塞等待(不久的将来可能有数据递达,此时会让出 cpu)

② 写管道:

1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉 SIGPIPE 信号,使进程不终止)
2. 管道读端没有全部关闭:
(1) 管道已满,write 阻塞。(管道大小 64K)
(2) 管道未满,write 将数据写入,并返回实际写入的字节数。

问题记录:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int pfd[2];
    int ret;
    char buf[20]= {0};
    char endbuf[20]= {0};
    pid_t pid;
    ret=pipe(pfd);
    if(ret<0) {
        perror("pipe");
        return 0;
    }
    pid=fork();
    if(pid<0) {
        perror("fork");
        return 0;
    }
    /*
     * 子进程
     */
    else if(pid==0) {
        //	close(pfd[0]);
        while(0) {
            strcpy(buf,"hhhahahah");
            write(pfd[1],buf,strlen(buf));
            ret=read(pfd[0],endbuf,20);
            if(ret>0) {
                printf("%s\n",endbuf);
            }
            sleep(1);
        }
    }
    /*
     * 父进程
     */
    else {
        //	close(pfd[1]);
        while(1) {
            strcpy(buf,"hhhahahah");
            write(pfd[1],buf,strlen(buf));
            ret=read(pfd[0],endbuf,20);
            if(ret>0) {
                printf("%s\n",endbuf);
            }
            sleep(1);
        }
    }
    while(1);
    return 0;
}

思路:父进程读写管道,将buf中的字符串写入管道,再从管道中读取字符串到endbuf,如果读取成功,将ednbuf打印出来。

子进程同样操作,都是可以单进程通过无名管道读写的。

4.2有名管道:

特点:


1 有名管道可以使非亲缘的两个进程互相通信
2 通过路径名来操作,在文件系统中可见,但内容存放在内存中
3 文件 IO 来操作有名管道
4 遵循先进先出规则
5 不支持 leek 操作(虽然是文件)
6 单工读写

函数实现:

创建管道


#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);

成功时返回 0 ,失败时返回 EOF
path 创建的管道文件路径
mode 管道文件的权限,如 0666

注意:

不能将管道建立在共享目录下,因为windows不支持管道

#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
int main(){
	int ret;
	ret=mkfifo("/myfifo",666);
	if(ret<0){
		perror("mkfifo");
		return 0;
	}

	return 0;
}

打开管道

open(const char *path, O_RDONLY);//1
open(const char *path, O_RDONLY | O_NONBLOCK);//2
open(const char *path, O_WRONLY);//3
open(const char *path, O_WRONLY | O_NONBLOCK);//4

四种打开模式,默认为阻塞状态,与上O_NONBLOCK为非阻塞状态

注意:

只能使用文件IO,不能使用标准IO

读进程:
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main() {
    int ret,fd;
    char buf[20]= {0};
    ret=mkfifo("/myfifo",666);
    if(ret<0) {
        perror("mkfifo");
        //return 0;
    }
    fd=open("/myfifo",O_RDONLY);
    if(fd<0) {
        perror("open");
        return 0;
    }
    while(1) {
        ret=read(fd,buf,20);
        if(ret>0) {
            printf("%s\n",buf);
        }
    }
    return 0;
}

写进程:
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main() {
    int ret,fd;
    char buf[20]= {0};
    ret=mkfifo("/myfifo",777);
    if(ret<0) {
        perror("mkfifo");
        //return 0;
    }
    fd=open("/myfifo",O_WRONLY);
    if(fd<0) {
        perror("open");
        return 0;
    }
    while(1) {
        strcpy(buf,"ahhhhh");
        write(fd,buf,strlen(buf));
    }
    return 0;
}

注意事项:

1 程序也是可以用 O_RDWR(读写)模式打开 FIFO 文件进行读写操作,但是其行为也未明确定义,
且,有名管道一般用于进程间通讯,不使用同一个进程实现读写的方式(同有名管道)

2 第二个参数中的选项 O_NONBLOCK,选项 O_NONBLOCK 表示非阻塞,加上这个选项后,表示
open 调用是非阻塞的,如果没有这个选项,则表示 open 调用是阻塞的


3.对于以只读方式(O_RDONLY)打开的 FIFO 文件,如果 open 调用是阻塞的(即第二个参
数为 O_RDONLY),除非有一个进程以写方式打开同一个 FIFO,否则它不会返回;如果 open
调用是非阻塞的的(即第二个参数为 O_RDONLY | O_NONBLOCK),则即使没有其他进程以写
方式打开同一个 FIFO 文件,open 调用将成功并立即返回。


对于以只写方式(O_WRONLY)打开的 FIFO 文件,如果 open 调用是阻塞的(即第二个参数为
O_WRONLY),open 调用将被阻塞,直到有一个进程以只读方式打开同一个 FIFO 文件为止;
如果 open 调用是非阻塞的(即第二个参数为 O_WRONLY | O_NONBLOCK),open 总会立即返回,但如果没有其他进程以只读方式打开同一个 FIFO 文件,open 调用将返回-1,并且 FIFO 也
不会被打开。


4.数据完整性,如果有多个进程写同一个管道,使用 O_WRONLY 方式打开管道,如果写入
的数据长度小于等于 PIPE_BUF(4K),那么或者写入全部字节,或者一个字节都不写入,
系统就可以确保数据决不会交错在一起。

方式5、信号机制

简介:

1.信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

2.linux内核通过信号通知用户进程,不同的信号类型代表不同的事件

3.进程对信号有不同的相应方式:

(1)缺省信号(根据信号指示执行)

(2)忽略信号(忽略信号指示)

(3)捕捉信号(捕捉信号,改变信号行为)

信号的产生:

1.按键产生

2.系统调用函数产生(例如raise、kill)

3.硬件异常产生

4.命令行产生(例:kill)

5.软件条件(例:被0除,非法访问内存)

常用信号:

命令实现:

kill +选项 +进程pid

通过终端命令给进程发送信号

函数实现:

kill函数:

int kill(pid_t pid, int signum)
功能:发送信号
参数:
pid:

> 0:发送信号给指定进程
= 0:发送信号给跟调用 kill 函数的那个进程处于同一进程组的进程。
< -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员。
= -1:发送信号给,有权限发送的所有进程。(轻易不要使用)
signum:待发送的信号

实现案例:

1.nx.c生成可执行文件nx,执行nx

#include<stdio.h>
int main(){
	while(1);
	return 0;
}

2.ps -ef|grep nx查看nx进程pid

3.cs.c文件执行kill函数

#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(){
	kill(2664,11);
	return 0;
}

4.结果

raise函数:

int raise(int sig);

功能:给自己发送信号,等价于 kill(getpid(), signo);

sig:要发送的信号

实现案例:

1.cs.c文件

#include<stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(){
	raise(11);
	return 0;
}

2.结果:

定时器函数:

int alarm(unsigned int seconds);

成功时返回上个定时器的剩余时间,失败时返回 EOF
seconds 定时器的时间


注意:

一个进程中只能设定一个定时器,时间到时产生 SIGALRM

实现案例:

1.三秒后产生SIGALRM信号

#include<stdio.h>
#include<unistd.h>
int main(){
	alarm(3);
	while(1);
	return 0;
}

2.结果

int pause(void);

可以用于,阻止程序继续往下执行,收到信号后再继续执行,实现信号驱动程序


功能:进程一直阻塞,直到被信号中断
被信号中断后返回 -1 , errno 为 EINTR

函数行为:

1如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。

2如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回

3 如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。

4 pause收到的信号如果被屏蔽,那么pause就不能被唤醒 

实现案例1:

1.cs.c(代替while,直到被信号中断)

#include<stdio.h>
#include<unistd.h>
int main(){
	alarm(3);
	pause();
	return 0;
}

2.结果

实现案例2:
解释:

在程序执行中给pause函数,有信号过来再将信号阻塞,往下执行cs函数,cs函数执行完毕,解除信号阻塞,在阻塞期间来的信号会被延迟,阻塞解除之后将信号释放,pause接收到释放的信号,就会进入下一个循环。

#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle(int sig){
	printf("sig=%d\n",sig);
}
void cs(){
	printf("111\n");
	sleep(3);
	printf("222\n");
}
int main(){
	struct sigaction act;
	act.sa_handler=handle;
	act.sa_flags=0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGINT,&act,NULL);
	pause();
	printf("after pause\n");
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set,SIGINT);
	while(1){
		sigprocmask(SIG_BLOCK,&set,NULL);
		cs();
		sigprocmask(SIG_UNBLOCK,&set,NULL);
		pause();
	}
}
问题:

信号好像发生在解除对SIGINT的阻塞和pause之间。如果发生了这种情况,或者如果在解除阻塞时刻和pause之间确实发生了信号,那么就产生了问题。因为我们可能不会再见到该信号,所以从这种意义上而言,在此时间窗口(解除阻塞和pause之间)中发生的信号丢失了,这样就使pause永远阻塞。

为了纠正此问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠,这种功能是由sigsuspend函数提供的。

int sigsuspend(const sigset_t *sigmask);

函数功能相当于解除sigprocmaskg阻塞,并且把信号给pause

功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后使进程休眠

参数:

sigmask:希望屏蔽的信号

修改案例2如下:
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <signal.h>
void handle(int sig){
	printf("sig=%d\n",sig);
}
void cs(){
	printf("111\n");
	sleep(3);
	printf("222\n");
}
int main(){
	struct sigaction act;
	act.sa_handler=handle;
	act.sa_flags=0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGINT,&act,NULL);
	pause();
	printf("after pause\n");
	sigset_t set,set2;
	sigemptyset(&set);
	sigaddset(&set,SIGINT);
	while(1){
		sigprocmask(SIG_BLOCK,&set,NULL);
		cs();
		//sigprocmask(SIG_UNBLOCK,&set,NULL);
		//pause();
        sigsuspend(&set2)
	}
}

循环定时器函数:

ualarm 

useconds_t ualarm(useconds_t usecs, useconds_t interval);
以 useconds 为单位,第一个参数为第一次产生时间,第二个参数为间隔产生

例:ualarm(3,4),执行之后,3秒后产生信号,间隔4秒再产生信号(以后为4秒循环)

setitimer

int setitimer(int which,,const struct itimerval *new_value,struct itimerval *old_value);
功能:定时的发送 alarm 信号
参数:

which:
ITIMER_REAL:以逝去时间递减。发送 SIGALRM 信号
ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送 SIGVTALRM 信号
ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时
间。 发送 SIGPROF 信号


new_value: 负责设定 timout 时间


old_value: 存放旧的 timeout 值,一般指定为 NULL
struct itimerval {
struct timeval it_interval; // 闹钟触发周期(例如触发后,每隔 1 秒触发)
struct timeval it_value; // 闹钟触发时间(例如 5 秒后触发)
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
#include <sys/time.h>

void handle(int sig) {
    printf("cath the SIGALRM \n");
}
int main() {
    struct sigaction act;
    struct itimerval new_value;
    act.sa_handler=handle;
    act.sa_flags=0;
	sigemptyset(&(act.sa_mask));
    new_value.it_interval.tv_sec=3;
    new_value.it_interval.tv_usec=0;
    new_value.it_value.tv_sec=5;
    new_value.it_value.tv_usec=0;
    
    setitimer(ITIMER_REAL,&new_value,NULL);
    sigaction(SIGALRM,&act,NULL);
    while(1) {
        sleep(1);
    }
}

信号的捕捉:

解释:1.程序遇到信号,2.会进入内核,如果信号的处理动作是非自定义,内核将信号处进入5,再返回1;如果信号处理东西是自定义,如上图;

信号捕捉过程:

1.定义新的信号的执行函数handle。

2.使用signal/sigaction 函数,把自定义的handle和指定的信号相关联。

函数实现:
signal函数:

typedef void (*sighandler_t)(int);(只能写成这种形式)

typedef void (*sighander_t)(int)

含义:将一个返回值为void类型,参数为int类型的函数指针定义成sighander_t,函数参数就是传进来的信号

sighandler_t  signal(int signum, sighandler_t handler);

功能:捕捉信号执行自定义函数

返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR

参数:

 Signum 要捕捉的信号类型

 handler 指定的信号处理函数:

SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;  

系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。

实现1.

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
void handle(int sig){
	printf("cath the SIGINT \n");
}
int main(){
	signal(SIGINT,handle);
	while(1){
		sleep(1);
	}
}

实现2.

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
typedef void (*sighandler_t)(int);
sighandler_t old;
void handle(int sig){
	printf("cath the SIGINT \n");
	signal(SIGINT,old);
}
int main(){
	old=signal(SIGINT,handle);
	while(1){
		sleep(1);
	}
}

sigaction函数:

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);

}

参数:

signum:处理的信号

act,oldact::处理信号的新行为和旧的行为,是一个sigaction结构体。

sigaction结构体成员定义如下:

sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似

sa_sigaction: 另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。

sa_flags参考值如下:(不使用以下标志位可写0)

SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数

SA_RESTART:使被信号打断的系统调用自动重新发起。

SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

re_restorer:是一个已经废弃的数据域

实现1.

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
void handle(int sig){
	printf("cath the SIGINT \n");
}
int main(){
	struct sigaction act;
	act.sa_handler=handle;
	act.sa_flags=SA_RESETHAND;
	sigemptyset(&(act.sa_mask));
	sigaction(SIGINT,&act,NULL);
	while(1){
		sleep(1);
	}
}

信号的阻塞:

信号来的时候会打断程序进行,如果希望信号不打断程序进行,就需要将信号阻塞

信号的阻塞概念

信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生(程序是可以收到信号的)。

信号的状态:

信号递达(Delivery ):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)

信号未决(Pending):从产生到递达之间的状态(程序不中断,程序接收不到这个信号)

将一个信号写进信号屏蔽字,如果信号来了,就将信号写入未决信号集里(挂起),未决信号集和信号屏蔽字每一位都代表一个信号。

例:2 SIGINT信号,未决信号集和信号屏蔽字中第二个比特位就表示SIGINT信号

        3 SIGOUT信号,未决信号集和信号屏蔽字中第三个比特位就表示SIGOUT信号

使用方式:可以将某个信号的未决信号集位,置1,当该信号到来,就将该信号的信号屏蔽字中的表示位,置1,该信号就被挂起,不会打断程序进行。

函数实现:

sigset_t set;  自定义信号集。  是一个32bit  64bit  128bit的数组每一个bit位表示一个信号。

sigemptyset(sigset_t *set); 清空信号集

sigfillset(sigset_t *set); 将信号集全部置1

sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中

sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除

sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。

(此时还未真正的将信号屏蔽,以下函数才将信号真正屏蔽)

#include <signal.h>

int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );

返回值:若成功则返回0,若出错则返回-1

首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。

其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)

SIG_BLOCK :   把参数set中的信号添加到信号屏蔽字中

SIG_UNBLOCK: 从信号屏蔽字中删除参数set中的信号

SIG_SETMASK: 把信号屏蔽字设置为参数set中的信号

使用流程:

1.创建信号集

2.清空信号集

3.将信号add进信号集

4.将set中的信号添加到信号屏蔽字中

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
	printf("get sig=%d",sig);
}
int main(){
	struct sigaction act;
	act.sa_handler=handle;
	act.sa_flags=0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGINT,&act,NULL);
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set,SIGINT);
	sigprocmask(SIG_BLOCK,&set,NULL);
	while(1){
		sleep(1);
	}
}

相关命令:

kill -l:查看当前系统支持哪些信号

ps -ef |grep +进程名:查看此进程

相关函数:

sigemptyset 

是一个 C 语言的标准库函数,属于信号处理部分,主要用来初始化一个信号集合。在使用信号相关功能时,通常需要创建并管理信号集,比如在信号屏蔽或处理程序中。

### 函数原型
```c
#include <signal.h>

int sigemptyset(sigset_t *set);
```

### 参数
- `set`: 指向需要初始化的信号集合的指针。

### 返回值
- 成功时返回 `0`,失败时返回 `-1`,并设置 `errno`。

### 功能
`sigemptyset` 将给定的信号集合 `set` 清空,确保集合中不包含任何信号。这通常在需要创建一个新的信号集合时使用,特别是在设置信号屏蔽或者与信号处理程序交互的情况下。

### 示例
以下是一个使用 `sigemptyset` 的示例代码:

```c
#include <stdio.h>
#include <signal.h>

int main() {
    sigset_t set;

    // 初始化信号集
    if (sigemptyset(&set) != 0) {
        perror("sigemptyset");
        return 1;
    }

    // 检查集合是否为空
    if (sigismember(&set, SIGINT)) {
        printf("集合中包含 SIGINT\n");
    } else {
        printf("集合中不包含 SIGINT\n");
    }

    return 0;
}
```

### 注意事项
- 使用 `sigemptyset` 后,信号集是一个空集合,您可以使用其他函数如 `sigaddset` 向其中添加特定的信号。
- 该函数通常用于在多线程和进程间的信号处理时,确保信号的正确管理。

相关信号:

SIGCHLD信号实现回收子进程

SIGCHLD的产生条件

1子进程终止时

2子进程接收到SIGSTOP信号停止时

3子进程处在停止态,接受到SIGCONT后唤醒时

#include<stdio.h>
#include<unistd.h>
#include <signal.h>
#include<unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include<stdlib.h>
void handle(int sig) {
	wait(NULL);
    printf("cath the sig=%d \n",sig);
}
int main() {
	int pid;
    struct sigaction act;
    act.sa_handler=handle;
    act.sa_flags=0;
    sigemptyset(&(act.sa_mask));

	pid=fork();
	if(pid>0){
		 sigaction(SIGCHLD,&act,NULL);
		 while(1){
		 	printf("this is father\n");
			sleep(1);
		 }
	}
	else{
		sleep(5);
		exit(0);
	}
 }

含义:父进程执行,在父进程内捕捉SIGCHLD信号,子进程exit(0)结束后,产生SIGCHLD信号,父进程捕捉到该信号,跳转到相关函数,将子进程回收

方式7、信号灯

进程和线程间通讯的一种方式,并且经常与共享内存一块使用,管理进程或线程间同步操作,只作为一种行为。

信号量和信号灯是同一种东西。

信号量代表某一类资源,其值表示系统中该资源的数量。

信号量是一个受保护的变量,只能通过三种操作来访问。

初始化

P操作(申请资源)(资源-1)

V操作(释放资源)(资源+1)

posix信号灯的使用:

初始化:sem_open()、sem_init()

wait是P操作,post是V操作

与共享内存使用:

初始共享内存为空;此时写信号量初始应为1,可写;读信号量初始为0,不可读

写入共享内存数据后;写信号量减1,为0,不可再写,读信号量加1,为1,可读;

读出共享内存数据后;读信号量减1,为0,不可再读,写信号量加1,为1,可写;

如此循环;

注意:

sem_open创建好信号量文件之后,如果没有将文件删除:关闭可执行文件,再打开可执行文件,就不可再次使用;因此需要在退出时将信号量文件删掉;

posix有名信号灯:

使用pthread库实现,编译时需要链接上pthread库

有名信号灯打开:

sem_t *sem_open(const char *name, int oflag);

sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);

参数:

name:name是给信号灯起的名字

oflag:打开方式,常用O_CREAT

mode:文件权限。常用0666

value:信号量值。二元信号灯值为1,普通表示资源数目(有10个资源,就可以初始化写为10)

信号灯文件位置:/dev/shm

使用open打开信号灯后,文件会自动放在该目录下边

有名信号灯关闭:

int sem_close(sem_t *sem);参数为open函数的返回值

作用:关闭信号灯,不可使用(文件依旧存在)

有名信号灯的删除:

int sem_unlink(const char* name);

作用:删除/dev/shm下创建的信号灯文件

实现:

一般创建两个信号量,一个读,一个写;

向共享内存中写入数据,需要sem_wait检查写信号量是否有资源,有资源就继续往下执行,写入数据,再sem_post读信号量。

从共享内存读数据,需要sem_wait检查读信号量是否有组员,有资源就继续往下执行,读出数据,再sem_post写信号量。

写端:

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <signal.h>
#include<stdlib.h>
void unlink(int arg){
	sem_unlink("sem_w");
	exit(0);
}
int main(){
	sem_t *sem_w;
	sem_t *sem_r;
	void *addr;
	pthread_t shmid;
	key_t key;
	struct sigaction act;
	act.sa_handler=unlink;
	act.sa_flags=0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGINT,&act,NULL);
	key=ftok("/tmp",22);
	if(key<0){
		perror("ftok");
		return 0;
	}
	shmid=shmget(key,512,IPC_CREAT|0666);
	if(shmid<0){
		perror("shmget");
		return 0;
	}
	addr=shmat(shmid,NULL,0);
	if(addr<0){
		perror("shmat");
		return 0;
	}
/*
 * 初始化信号量
 */
	sem_r=sem_open("/sem_r",O_CREAT,0666,0);
	sem_w=sem_open("/sem_w",O_CREAT,0666,1);
	while(1){
		sem_wait(sem_w); 	//判断信号量,有资源才会继续往下执行,同时信号量会减1
		printf(">");
		fgets(addr,512,stdin);
		sem_post(sem_r); 	//将信号量加1
	}	
		
}

读端:

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <signal.h>
#include<stdlib.h>
void unlink(int arg){
	sem_unlink("sem_r");
	exit(0);
}
int main(){
	sem_t *sem_w;
	sem_t *sem_r;
	char *addr;
	pthread_t shmid;
	key_t key;
	struct sigaction act;
	act.sa_handler=unlink;
	act.sa_flags=0;
	sigemptyset(&act.sa_mask);
	sigaction(SIGINT,&act,NULL);
	key=ftok("/tmp",22);
	if(key<0){
		perror("ftok");
		return 0;
	}
	shmid=shmget(key,512,IPC_CREAT|0666);
	if(shmid<0){
		perror("shmget");
		return 0;
	}
	addr=shmat(shmid,NULL,0);
	if(addr<0){
		perror("shmat");
		return 0;
	}
/*
 * 初始化信号量
 */
	sem_r=sem_open("/sem_r",O_CREAT,0666,0);
	sem_w=sem_open("/sem_w",O_CREAT,0666,1);
	while(1){
		sem_wait(sem_r); 	//判断信号量,有资源才会继续往下执行,同时信号量会减1
		printf("%s\n",addr);
		sem_post(sem_w); 	//将信号量加1
	}	
		
}

posix无名信号灯:

(linux中只支持线程同步)

使用pthread库实现,编译时需要链接上pthread库

无名信号灯初始化/打开

int sem_init(sem_t *sem, int shared, unsigned int value);

参数: 

sem:需要初始化的信号灯变量

shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。

Value:信号量的值(初始值)

无名信号灯销毁:

int sem_destroy(sem_t* sem);

参数:信号灯变量

System信号灯:

创建/打开信号灯:

int semget(key_t key, int nsems, int semflg);

功能:创建/打开信号灯

参数:key:ftok产生的key值(和信号灯关联的key值)

  nsems:信号灯集中包含的信号灯数目

  semflg:信号灯集的访问权限,通常为IPC_CREAT |0666

返回值:成功:信号灯集ID ; 失败:-1

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

空压机接入配置实例:利用 MODBUS - TCP 转 Ethernet IP 网关实现连接

在工业自动化生产环境中&#xff0c;空压机作为重要的气源设备&#xff0c;其稳定运行和有效监控对于整个生产流程至关重要。然而&#xff0c;不同厂家生产的空压机可能采用不同的通信协议&#xff0c;这给集中监控和管理带来了挑战。在本次案例中&#xff0c;我们遇到的空压机…

基于 Boost.Asio 和 Boost.Beast 的异步 HTTP 服务器(学习记录)

已完成功能&#xff1a; 支持 GET 和 POST 请求的路由与回调处理。 解析URL请求。 单例模式 管理核心业务逻辑。 异步 I/O 技术和 定时器 控制超时。 通过回调函数注册机制&#xff0c;可以灵活地为不同的 URL 路由注册处理函数。 1. 项目背景 1.1 项目简介 本项目是一个基于…

Harmony开发【笔记1】报错解决(字段名写错了。。)

在利用axios从网络接收请求时&#xff0c;发现返回obj的code为“-1”&#xff0c;非常不解&#xff0c;利用console.log测试&#xff0c;更加不解&#xff0c;可知抛出错误是 “ E 其他错误: userName required”。但是我在测试时&#xff0c;它并没有体现为空&#xff0c;…

Spring源码分析之事件机制——观察者模式(二)

目录 获取监听器的入口方法 实际检索监听器的核心方法 监听器类型检查方法 监听器的注册过程 监听器的存储结构 过程总结 Spring源码分析之事件机制——观察者模式&#xff08;一&#xff09;-CSDN博客 Spring源码分析之事件机制——观察者模式&#xff08;二&#xff…

关于Mac中的shell

1 MacOS中的shell 介绍&#xff1a; 在 macOS 系统中&#xff0c;Shell 是命令行与系统交互的工具&#xff0c;用于执行命令、运行脚本和管理系统。macOS 提供了多种 Shell&#xff0c;主要包括 bash 和 zsh。在 macOS Catalina&#xff08;10.15&#xff09;之前&#xff0c…

【C++】20.二叉搜索树

文章目录 1. 二叉搜索树的概念2. 二叉搜索树的性能分析3. 二叉搜索树的插入4. 二叉搜索树的查找5. 二叉搜索树的删除6. 二叉搜索树的实现代码7. 二叉搜索树key和key/value使用场景7.1 key搜索场景&#xff1a;7.2 key/value搜索场景&#xff1a;7.3 主要区别&#xff1a;7.4 ke…

【大模型+本地自建知识图谱/GraphRAG/neo4j/ollama+Qwen千问(或llama3)】 python实战(中)

一、建立基本的知识图谱并导入neo4j 这里我举例用的属性表、关系表&#xff0c;大概格式如下 id名字颜色a1苹果红色 startrelenda1属于b1 启动neo4j&#xff08;关于neo4j的安装此处不再赘述&#xff09; import pandas as pd from py2neo import Graph, Node, Relationship…

【pyqt】(四)Designer布局

布局 之前我们利用鼠标拖动的控件的时候&#xff0c;发现一些部件很难完成对齐这些工作&#xff0c;pyqt为我们提供的多种布局功能不仅可以让排版更加美观&#xff0c;还能够让界面自适应窗口大小的变化&#xff0c;使得布局美观合理。最常使用的三种布局就是垂直河子布局、水…

解决“KEIL5软件模拟仿真无法打印浮点数”之问题

在没有外部硬件支持时&#xff0c;我们会使用KEIL5软件模拟仿真&#xff0c;这是是仿真必须要掌握的技巧。 1、点击“Project”&#xff0c;然后点击“Options for target 项目名字”&#xff0c;点击“Device”,选择CPU型号。 2、点击“OK” 3、点击“Target”,勾选“Use Mi…

【项目实战1】五子棋游戏

目录 C语言编程实现五子棋&#xff1a;&#xff1a; game.h game.c 1.打印菜单 2.打印棋盘 3.玩家下棋 4.判断五子连珠 5.判断输赢 6.游戏运行 game.c完整源代码展示 test.c C语言编程实现五子棋&#xff1a;&#xff1a; game.h #pragma once #include<stdio.h> …

用ResNet50+Qwen2-VL-2B-Instruct+LoRA模仿Diffusion-VLA的论文思路,在3090显卡上训练和测试成功

想一步步的实现Diffusion VLA论文的思路&#xff0c;不过论文的图像的输入用DINOv2进行特征提取的&#xff0c;我先把这个部分换成ResNet50。 老铁们&#xff0c;直接上代码&#xff1a; from PIL import Image import torch import torchvision.models as models from torch…

Spring Boot 项目自定义加解密实现配置文件的加密

在Spring Boot项目中&#xff0c; 可以结合Jasypt 快速实现对配置文件中的部分属性进行加密。 完整的介绍参照&#xff1a; Spring Boot Jasypt 实现application.yml 属性加密的快速示例 但是作为一个技术强迫症&#xff0c;总是想着从底层开始实现属性的加解密&#xff0c;…

A/B实验之置信检验(一):如何避免误判 (I类) 和漏报 (II类)

假设检验的依据&#xff1a;如何避免误判和漏报 A/B实验系列相关文章&#xff08;置顶&#xff09; 1.A/B实验之置信检验&#xff08;一&#xff09;&#xff1a;如何避免误判和漏报 2.A/B实验之置信检验&#xff08;二&#xff09;&#xff1a;置信检验精要 引言 在数据驱动…

每日一题:链表中环的入口结点

文章目录 判断链表环的入口节点描述数据范围&#xff1a;复杂度要求&#xff1a;输入输出 示例代码实现思路解析注意事项&#xff1a; 判断链表环的入口节点 描述 给定一个链表&#xff0c;判断该链表是否存在环。如果存在环&#xff0c;返回环的入口节点&#xff1b;如果不存…

深度学习blog-Meanshift均值漂移算法-最大熵模型

均值漂移&#xff08;Mean Shift&#xff09;是一种无监督的聚类算法&#xff0c;广泛应用于数据挖掘和计算机视觉任务。它通过移动样本点到其近邻的均值位置来寻找数据的高密度区域&#xff0c;最终形成聚类。 均值漂移算法原理 均值漂移算法的核心思想是通过滑动窗口&#…

51c自动驾驶~合集45

我自己的原文哦~ https://blog.51cto.com/whaosoft/13020031 #运动控制和规划控制需要掌握的技术栈~ 各大垃圾家电造车厂又要开始了~~~​ 1、ROS的通信方式 李是Lyapunov的李&#xff1a;谈谈ROS的通信机制 话题通信和服务通信&#xff0c;其中话题通信是通过发布和订阅…

Python基于jieba和wordcloud绘制词云图

【Cesium】自定义材质&#xff0c;添加带有方向的滚动路线 &#x1f356; 前言&#x1f3b6;一、实现过程✨二、代码展示&#x1f3c0;三、运行结果&#x1f3c6;四、知识点提示 &#x1f356; 前言 Python基于jieba和wordcloud绘制词云图 &#x1f3b6;一、实现过程 读取文本…

计算机网络与服务器

目录 架构体系及相关知识 三层架构&#xff1a; 四层架构&#xff1a; 常见的应用的模式&#xff1a; OSI模型 分层 数据链路层 TCP/IP模型 TCP和UDP都是传输层的协议 TCP三次握手、四次次分手 URL&HTTP协议详解 网址URL 结构化 报文行 报文头 空行 报文体…

Cursor实现go项目配置并实现仓库Gin项目运行

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a;知识备份 ✨特色专栏&#xff1a;知识分享 &#x…

141.环形链表 142.环形链表II

141.环形链表 & 142.环形链表II 141.环形链表 思路&#xff1a;快慢指针 or 哈希表 快慢指针代码&#xff1a; class Solution { public:bool hasCycle(ListNode *head) {if(headnullptr||head->nextnullptr)return false;ListNode *fasthead->next; //不能设置成…