Linux C进程间通信(IPC)

news2024/11/27 2:25:13

概述

每个进程有独立的进程空间:

好处————安全

缺点:开销大(独立的地址空间);进程的通信更加困难(对其他进程的操作开销也大)

广义上的进程间通信:

A进程写给文件/数据库,B进程从文件/数据库里读取

 狭义上的真正的“进程间通信”

  1. 管道
  2. 信号
  3. 消息队列
  4. 共享内存
  5. 信号量
  6. 套接字
     

进程间通信的原理

尽管进程空间是各自独立的,相互之间没有任何可以共享的空间,但至少还有一个共享的,那就是OS,因为甭管运行有多少个进程,但是它们共用OS只有一个
既然大家共用的是同一个OS,那么显然,所有的进程可以通过大家都共享第三方OS来实现数据的转发。
因此进程间通信的原理就是,OS作为所有进程共享的第三万,会提供相应的机制,以实现进程间数据的转发,达到数据共享的目的

信号

信号是一种向进程发送通知,告诉其某件事情发生了的一种简单通信机制

古老,应用广泛;        仅做通知,不做数据传输;        本质上是整数值(SIG开头);

信号列表

信号的产生

另一个进程发生信号;内核发送信号;底层硬件发送信号

信号发送

ps命令:查看进程的信息

终端

kill命令:kill -s 《signal》 《pid》

程序中给一个进程发信号

给当前进程发信号

raise
 alarm

abort
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{
    // kill(-1, SIGINT);//给所有进程发出信号(SIGINT是终止信号)
    
    // raise(SIGINT);   //给当前进程发出终止信号
    
    alarm(5);           //定时器到期,操作系统将发送 SIGALRM 信号给进程。
                        //默认情况下,如果进程没有对 SIGALRM 信号进行处理,它将终止进程的执行
    
    //while(1);
    pause();            //挂起当前进程(相比于while(1)这种cpu消耗性更节约资源)
    

    abort();          //错误地退出
    
    return 0;
}

信号的处理方式

1.默认处理;                2.忽略;                3.执行用户需要执行的操作(捕获)

信号处理API

signal

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{
    // kill(-1, SIGINT);//给所有进程发出信号(SIGINT是终止信号)
    
    // raise(SIGINT);   //给当前进程发出终止信号
    
    alarm(5);           //定时器到期,操作系统将发送 SIGALRM 信号给进程。
                        //默认情况下,如果进程没有对 SIGALRM 信号进行处理,它将终止进程的执行
    
    //while(1);
    pause();            //挂起当前进程(相比于while(1)这种cpu消耗性更节约资源);直到有一个信号来
   
    return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig)       // 信号处理函数
{
    if (sig == SIGALRM)     // 可以判断是哪个信号调用的处理函数
    {
        printf("handler with alarm\n");
    }
    else if (sig == SIGINT)
    {
        printf("handler with ctrl+c\n");
    }
}
int main()
{
    // signal(SIGALRM,SIG_IGN);/sigalrm信号被忽略        则pause不会接收到信号,一直挂起

    // signal(SIGALRM,SIG_DFL);//sigalrm信号变为默认     则五秒后,printf输出;

    signal(SIGALRM, handler); // sigalrm信号转向 ”处理函数“————handler;
    signal(SIGINT, handler);

    alarm(5);
    pause(); // 当处理了一个信号处理函数,会唤醒pause

    printf("main is over\n");
    return 0;
}

sigaction


异步IO的实现

fcntl(0, __F_SETOWN, getpid()); // 将sigio信号设置成由当前的进程接收

signal(SIGIO, handler); // 设置信号处理函数

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

void handler(int sig) // 定义的信号处理函数
{
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    int ret = read(0, buffer, sizeof(buffer) - 1);
    buffer[ret] = '\0';
    printf("%s\n", buffer);
}

int main(int argc, char **argv)
{
    int fd;
    fd = open("/dev/input/mouse0", O_RDWR); // 打开一个文件描述符
    if (fd == -1)
    {
        perror("fd open error\n");
        exit(-1);
    }
    int flags = fcntl(0, F_GETFL);
    flags = flags | O_ASYNC; // 获取fd的flags,并加上0_ASYNC(异步读取)
    fcntl(0, F_SETFL, flags);

    fcntl(0, __F_SETOWN, getpid()); // 将sigio信号设置成由当前的进程接收

    signal(SIGIO, handler); // 设置信号线处理函数

    while (1) // 主函数的操作不受影响
    {
        int cor = 0;
        read(fd, &cor, sizeof(int));
        printf("handler is going: cor =%d\n", cor);
    }
    return 0;
}

优化进程等待

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

void handler(int sig)
{
    wait(NULL);
    printf(("handler & wait\n"));
}
int main()
{
    signal(SIGCHLD, handler);
    pid_t pid = fork();
    if (pid > 0)
    {
        while (1)
        {
            printf("father is going\n");
            sleep(1);
        }
    }
    if (pid == 0)
    {
        printf("child is going\n");
    }
    return 0;
}

信号屏蔽字

作用:屏蔽信号

sigset_t数据类型

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

int main(int argc, char **argv)
{
    sigset_t set;                         // 定义一个信号字
    sigemptyset(&set);                    // 清空:全置为0
    sigaddset(&set, SIGINT);              // 将sigint信号加入该信号集——也就是将对应位置为1
    sigprocmask(SIG_SETMASK, &set, NULL); // 设置信号罩

    pid_t pid = fork();
    if (pid > 0)
    {
        while (1)
        {
            printf("father \n");
            sleep(1);
        }
    }
    if (pid == 0)
    {
        while (1)
        {
            printf("child \n");
            sleep(1);
        }
    }
    return 0;
}

未决(处理)信号集

也是六十四位的int,记录了未处理的信号

pause() 函数会一直等待直到收到一个信号。当进程接收到一个信号时,如果该信号没有被忽略并且没有注册对应的信号处理函数,pause() 函数会被信号中断并返回 -1,同时将 errno 设置为 EINTR。 

管道

无格式,读取后数据会删除

无名管道

内核会开辟一个“管道”,通信的进程通过共享这个管道从而实现通信

int pipe(int pipefd【2】);

特点:

1.只允许具有血管关系的进程间通信,如父子进程间的通信

2.管道只允许单向通信

3.读管道时,没有数据就会堵塞;写数据,写满缓冲区会休眠

4.数据被读出后,数据就会被管道删除

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

int main(int agrc, char **argv)
{
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    if (pid > 0)
    {
        close(fd[0]);
        char buffer[1024];
        while (1)
        {
            memset(buffer, 0, sizeof(buffer));
            scanf("%s", buffer);
            write(fd[1], buffer, sizeof(buffer));
        }
    }
    else if (pid == 0)
    {
        int flags =fcntl(fd[0],F_GETFL);
        flags=flags|O_NONBLOCK;
        fcntl(fd[0],F_SETFL,flags);
        
        close(fd[1]);
        char buffer[1024];
        while (1)
        {
            memset(buffer, 0, sizeof(buffer));
            read(fd[0], buffer, sizeof(buffer));
            printf("buffer is %s\n", buffer);
            sleep(1);
        }
    }
    return 0;
}

注意事项

SIGPIPE信号:

1.写管道时,如果管道的读端被close了话,向管道"写"数据的进程会被内核发送一个SIGPIPE号,发这个信号的目的就是想通知你,管道所有的"读"都被关闭了。

2.由于这个信号的默认动作是终止,所以收到这个信号的进程会被终止,如果你不想被终止的
话,你可以忽略、捕获、或者屏蔽这个信号。
3.只有当管道所有的读端都被关闭时,才会产生这个信号,只有还有一个读端开着,就不会产生

 

signal (SIGPIPE, SIG_IGN)来忽略sigpipe这个信号

无名管道结合异步IO 

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

int fd[2];
void handle(int sig)
{
    if (sig == SIGIO)
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        read(fd[0], buffer, sizeof(buffer) - 1);
        printf("%s\n", buffer);
    }
}
int main(int agrc, char **argv)
{

    pipe(fd);
    pid_t pid = fork();
    if (pid > 0) // 父进程:写
    {
        close(fd[0]);
        char buffer[1024];
        while (1)
        {
            memset(buffer, 0, sizeof(buffer));
            scanf("%s", buffer);
            write(fd[1], buffer, strlen(buffer));
        }
    }
    else if (pid == 0) // 子进程:读
    {
        // close(fd[0]);
        close(fd[1]);

        int flags = fcntl(fd[0], F_GETFL);
        flags = flags | O_ASYNC;
        fcntl(fd[0], F_SETFL, flags);

        fcntl(fd[0], __F_SETOWN, getpid());
        signal(SIGIO, handle);
        
        pause();
    }
    return 0;
}

有名管道

管道应用的一个重大限制是它没有名字,只适合具有亲缘性质的进程之间通信。命名管道克服了这种限制,FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。 

例子:

FILE1:

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

#define FILE_PATH "./pipe"

void rm_pipe(int sig)
{
    if(sig==SIGINT)
    {
        remove(FILE_PATH);
    }
}

int main()
{
    if(mkfifo(FILE_PATH,0777)<0)
    {
        perror("mkfifo error");
        exit(-1);
    }
    int fd=open(FILE_PATH,O_WRONLY);
    if(fd==-1)
    {
        perror("fd open error");
        exit(-1);
    }
    signal(SIGINT,rm_pipe);
    while(1)
    {
        char buffer[1024];
        memset(buffer,0,sizeof(buffer));
        scanf("%s",buffer);
        write(fd,buffer,strlen(buffer));
    }
    return 0;
}

FILE2:

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

#define FILE_PATH "./pipe"

int main()
{
    int fd=open(FILE_PATH,O_RDONLY);
    if(fd==-1)
    {
        perror("fd open error");
        exit(-1);
    }
    while(1)
    {
        char buffer[1024];
        memset(buffer,0,sizeof(buffer));
        int ret=read(fd,buffer,sizeof(buffer)-1);
        buffer[ret]='\0';
        printf("%s\n",buffer);
    }
    return 0;
}

注意事项:

“"有名管道"这种特殊文件,只能使用mkfifo函数来创建
为了保证有名管道一定被创建,最好是两个进程都包含创建管道的代码,谁先运行就谁先创建,后运行的发现管道经创建好了,那就直接open打开使用。

不能以O_RDWR模式打开命名管道FIFO文件,否则其行为是未定义的,管道是单向的,不能同时读写
 


System V IPC

特点:

与管道不同,他完全使用了不同的实现机制,与文件没有任何关系,也就是说内核不再以文件形式

System V IPC不在以文件形式存在,所以没有文件描述符这个东西,但有类似的标识符

任何进程间通信时,都可以使用System V IPC来通信

优点:减少进程间通信的开销(文件的开销大于链表、内存、整形);Linux和Unix都通用

消息队列

消息队列的本质就是由内核创建的用于存放消息的链表,由于是存放消息的,所以我们就把这个链表称为消息队列


分类

System V的消息队列
Posix消息队列团


消息的组成(结构体)

1.消息编号:识别消息;

2.消息正文:真正的信息内容

消息队列API

创建

key值:

1.指定为IPC_PRIVATE宏,指定这个宏后,每次调用msgget时都会创建一个新的消息
队列。如果你每次使用的必须是新消息队列的话,就可以指定这个,不过这个用的很少。因为一般来说,只要有一个消息队列可以用来通信就可以了﹐并不需要每次都创建一个全新的消息队列。
2.自己指定一个整数型,但容易重复指定。本来我想创建一个新的消息队列,结果我所指定的这个整形数﹐之前就已经被用于创建某个消息队列了,当我的指定重复时msgget就不会创建新消息队列,而是使用的是别人之前就创建好的消息队列。所以我们也不会使用这种方式来指定key值

3.key_t ftok(const char *pathname, int proj_id);
ftok通过指定路径名和一个整形数,就可以计算并返回一个唯一对应的key值,只要路径名和整形数不变,所对应的key值就唯一不变的。
不过由于ftok只会使用整形数《 proj_id》的低8位,因此我们往往会指定为一个ASCII码值,因为ASCII码值刚好是8位的整形数。

msgflag
指定创建时的原始权限,比如0664
创建一个新的消息队列时,除了原始权限,还需要指定IPC_CREAT选项。
msgid = msgget(key, 0664 | IPC_CREAT);
 

查看消息队列命令

ipcs -a是默认的输出信息:打印出当前系统中所有的进程间通信方式的信息

ipcs -m打印出使用共享内存进行进程间通信的信息

ipcs -q打印出使用消息队列进行进程间通信的信息

ipcs -s打印出使用信号量进行进程间通信的信息
 

获取属性及删除

进程结束后,system v ipc不会自动删除,进程结束后,使用ipc依然能够查看到

1.重启OS

2.使用ipcrm命令删除:

ipcrm -Q msgkey移除用msqkey创建的消息队列
ipcrm -q msqid移除用msqid标识的消息队列


3.int msgctl(int msqid, int cmd, struct msqid_ds *buf);//也可以获取消息队列的属性

cmd

IPC_STAT:将msqid消息队列的属性信息,读到第三个参数所指定的缓存。

IPC_SET:IPC_SET:使用第三个参数中的新设置去依改消息队列的属性
        定一个struct msqid_ds buf
        将新的属性信息设置到buf中
        cmd指定为IPC_SET后,msgctl函数就会使用buf中的新属性去修改消息队列原有的属性

IPC_RMID:删除消息队列,第三个参数置为空

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define FILE "./msg_file"
int main()
{
    key_t key;
    key = ftok(FILE, 'F'); // 定义key值

    int msgid = msgget(key, 0777 | IPC_CREAT); // 创建消息队列
    if (msgid < 0)
    {
        perror("msgget error");
    }
    printf("%x\n", key);
    printf("%d\n", msgid);

    // msgctl(msgid,IPC_RMID,NULL); 删除队列
    return 0;
}

发送

接收

 删除

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>

#define FILE_PATH "./path"
int msgpid;
void handler(int sig)
{
    if (sig == SIGINT)
    {
        msgctl(msgpid, IPC_RMID, NULL);
    }
}
struct msgbuf
{
    long mstype;
    char mstext[1024];
};
int main(int argc, char **argv)
{
    signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列

    key_t key = ftok(FILE_PATH, 'K');

    msgpid = msgget(key, 0777 | IPC_CREAT);
    if (msgpid < 0)
    {
        perror("msgget error");
    }
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork error");
    }
    else if (pid == 0) // 发消息到队列
    {
        while (1)
        {
            struct msgbuf m1;
            memset(&m1, 0, sizeof(struct msgbuf));

            printf("input type:");
            scanf("%ld", &m1.mstype);
            printf("input text:");
            scanf("%s", m1.mstext);

            if (msgsnd(msgpid, &m1, sizeof(m1.mstext), IPC_NOWAIT) < 0)
            {
                perror("msgsnd error");
                _exit(-1);
            }
        }
    }
    else // 从队列读消息
    {
        while (1)
        {
            struct msgbuf m2;
            memset(&m2, 0, sizeof(struct msgbuf));
            if (msgrcv(msgpid, &m2, sizeof(m2.mstext), 3, MSG_NOERROR) < 0)
            {
                perror("msgrcv error");
                exit(-1);
            }
            else
            {
                printf("msg rcv:%s\n", m2.mstext);
            }
            sleep(1);
        }
    }

    return 0;
}

消息队列的使用步骤

 
创建        收发        删除


代码实例


消息队列的特点

传送有格式的消息流

多进程网状交叉通信,消息队列是上上之选

能实现大规模(进程规模多,不是说数据量大)数据的通信

通过共同参数的ftok函数,生成的信号队列,可以实现两个无血缘关系进程的读写:

msg_write.c:
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>

#define FILE_PATH "./path"
int msgpid;
void handler(int sig)
{
    if (sig == SIGINT)
    {
        msgctl(msgpid, IPC_RMID, NULL);
    }
}
struct msgbuf
{
    long mstype;
    char mstext[1024];
};
int main(int argc, char **argv)
{
    signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列

    key_t key = ftok(FILE_PATH, 'K');

    msgpid = msgget(key, 0777 | IPC_CREAT);
    if (msgpid < 0)
    {
        perror("msgget error");
    }
        while (1)
        {
            struct msgbuf m1;
            memset(&m1, 0, sizeof(struct msgbuf));

            printf("input type:");
            scanf("%ld", &m1.mstype);
            printf("input text:");
            scanf("%s", m1.mstext);

            if (msgsnd(msgpid, &m1, sizeof(m1.mstext), IPC_NOWAIT) < 0)
            {
                perror("msgsnd error");
                _exit(-1);
            }
        }
    return 0;
}


msg_read.c :
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <stdlib.h>

#define FILE_PATH "./path"
int msgpid;
void handler(int sig)
{
    if (sig == SIGINT)
    {
        msgctl(msgpid, IPC_RMID, NULL);
    }
}
struct msgbuf
{
    long mstype;
    char mstext[1024];
};
int main(int argc, char **argv)
{
    signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列

    key_t key = ftok(FILE_PATH, 'K');

    msgpid = msgget(key, 0777 | IPC_CREAT);
    if (msgpid < 0)
    {
        perror("msgget error");
    }
    while (1)
    {
        struct msgbuf m2;
        memset(&m2, 0, sizeof(struct msgbuf));
        if (msgrcv(msgpid, &m2, sizeof(m2.mstext), 3, 0) < 0)  //阻塞的读
        {
            perror("msgrcv error");
            exit(-1);
        }
        else
        {
            printf("msg rcv:%s\n", m2.mstext);
        }
        sleep(1);
    }
    return 0;
}

共享内存

让同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新

API:

创建:

删除:

1.重启OS

2.使用ipcrm命令删除:

ipcrm -M shmkey移除用shmkey创建的共享内存段
ipcrm -m shmid移除用shmid标识的共享内存段

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

映射:

char *shm_c = shmat(shmid1, NULL, 0); // shmat结果是void类型的指针(强制类型转换)
    // null代表系统分配内存地址;
    // 0代表可读可写;shm_rdonly代表只读

取消映射:

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

#define SIZE_T 4096
#define FILE_PATh ".demo1"

int shmid1;

void handler(int sig)
{
    shmctl(shmid1, IPC_RMID, NULL);
    printf("delete done\n");
    exit(1);
}

int main(int argc, char **argv)
{
    key_t key1 = ftok(FILE_PATh, 'F');
    shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);
    if (shmid1 == -1)
    {
        perror("shmget error");
        exit(-1);
    }
    signal(SIGINT, handler);
    char *shm_c = shmat(shmid1, NULL, 0); // shmat结果是void类型的指针(强制类型转换)
    // null代表系统分配内存地址;
    // 0代表可读可写;shm_rdonly代表只读

    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork error");
        exit(-1);
    }
    else if (pid > 0) // 父进程 用来向内存写
    {
        while (1)
        {
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            scanf("%s", buffer);
            strcpy(shm_c, buffer);
        }
    }
    else // 子进程  用来从内存读
    {
        while (1)
        {
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            memcpy(buffer, shm_c, sizeof(buffer));
            memset(shm_c, 0, sizeof(buffer));
            printf("receive buffer = %s\n", buffer);
            sleep(1);
        }
    }
    return 0;
}

改进为阻塞读取(以节省CPU资源):

1.信号:

pause() 函数会一直等待直到收到一个信号。当进程接收到一个信号时,如果该信号没有被忽略并且没有注册对应的信号处理函数pause() 函数会被信号中断并返回 -1,同时将 errno 设置为 EINTR

2.信号量

特点

开销最小,减少进入内核次数;

直接使用地址来读写,效率更高,适用于大数据量的通信

作业:实现任意进程间的阻塞读取

读数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/shm.h>

#define FILE_PATH "./pipe_file"
#define SIZE_T 4096

void handler(int sig)
{
    if (sig == SIGINT)
    {
        remove(FILE_PATH);
        exit(-1);
    }
    if (sig == SIGUSR1)//空处理,可以唤醒pause
    {
    }
}
int main(int argc, char **argv) // 读
{
    signal(SIGINT, handler);
    signal(SIGUSR1, handler);
    if (mkfifo(FILE_PATH, 0777) < 0) // 创建有名管道
    {
        perror("mkfifo error");
        exit(-1);
    }
    int fd = open(FILE_PATH, O_WRONLY); /// 只写打开有名管道文件
    if (fd < 0)
    {
        perror("open error");
        exit(-1);
    }
    pid_t pid1 = getpid(); // 获取当前进程pid号,并通过有名管道传给 写 进程
    printf("%d\n", pid1);
    if (write(fd, &pid1, sizeof(pid_t)) < 0)
    {
        perror("write pid1 error");
        exit(-1);
    }

    key_t key1 = ftok(FILE_PATH, 'F'); // 配置共享内存
    int shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);
    if (shmid1 < 0)
    {
        perror("shmid1 error");
        exit(-1);
    }
    char *shm = (char *)shmat(shmid1, NULL, 0); // 共享内存
    if (shm == NULL)
    {
        perror("shm error");
        exit(-1);
    }
    while (1) // 取出共享内存内的数据
    {
        printf("please wait output\n");
        pause();
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        strcpy(buffer, shm);
        printf("receive buffer :%s\n", buffer);
        memset(shm, 0, SIZE_T);
    }
    return 0;
}

写数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/shm.h>

#define FILE_PATH "./pipe_file"
#define SIZE_T 4096

int main(int argc, char **argv) // 写
{
    int fd = open(FILE_PATH, O_RDONLY); // 只读打开有名管道
    if (fd < 0)
    {
        perror("open error");
        exit(-1);
    }
    pid_t pid1;
    read(fd, &pid1, sizeof(pid_t)); // 把 读 进程pid号通过管道读出来
    printf("%d\n",pid1);



    key_t key1 = ftok(FILE_PATH, 'F'); // 配置共享内存
    int shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);
    if (shmid1 < 0)
    {
        perror("shmid1 error");
        exit(-1);
    }
    char *shm = (char*)shmat(shmid1, NULL, 0); // 共享内存
    if (shm == NULL)
    {
        perror("shm error");
        exit(-1);
    }
    while (1) // 写数据,写完就传个信号给 读 进程
    {
        char buffer[1024];
        
        printf("please input\n");
        scanf("%s", buffer);
        strcpy(shm, buffer);
        kill(pid1,SIGUSR1);
    }
    return 0;
}

信号量(信号锁、信号灯

当多个进程/线程进行共享访问的时候,用于资源保护,以防止资源出现干扰的情况

进程同步:进程按照一定的顺序执行(不是指先后顺序,而是指互斥)

进程竞态:

互斥:对于互斥操作来说,多进程共享操作时,多个进程间不关心谁先操作、谁后操作的先后顺序问题,它们只关心,自己操作时候,别人不能操作

同步:所谓同步就是,多个共享操作时,进程必须要有统
操作的步调,按照一定的顺序来操作

解决方法:加锁

信号量(信号锁):信号量其实是一个OS创建的,供相关进程共享的int变量,只不过我们在调用相关API创建信号量时,我们创建的都是一个信号量集合,所谓集合就是可能会包含好多个信号量。
用于互斥时,集合中只包含一个信号量。
用于同步时,集合中会包含多个信号量,至于多少个,需要看情况

API:

1.配置信号队列:int semget (key_t key,int nsems,int semflg)

参数:
key用ftok获取key值
nsems指定集合中信号量的个数        【用于互斥时,数量都指定为1,因为只需要一个信号量】
semfig:权限:―般都设置为0664 | IPC_CREAT        【设置同消息队列和共享内存】        

2.控制信号队列:int semctl(int semid,int semnum,int cmd,...)

参数:

semnum:集合中某个信号量的编号(集合中某个信号量的编号:信号量的编号为非负整数,而且是自动从0开始)

cmd:IPC_STAT;        IPC_SET;         IPC_RMID

可变参数:......

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>

#define FILE_PATH "./sem_demo"

int semid;
void delete_sem(int, int);
void handler(int sig)
{
    delete_sem(semid, 0);
}

void creat_sem(int nsems) // 创建
{
    key_t key = ftok(FILE_PATH, 'f');
    int semid = semget(key, nsems, 0777 | IPC_CREAT);
    if (semid < 0)
    {
        perror("semget error");
        exit(-1);
    }
}
void init_sem(int semid, int semnum, int val) // 初始化制定信号量的值
{
    semctl(semid, semnum, SETVAL, val);
}
void delete_sem(int semid, int semnum) // 删除制定信号量的值
{
    if (semctl(semid, semnum, IPC_RMID) < 0)
    {
        perror("semctl delete error");
    }
}
int main(int argc, char **argv)
{

    key_t key = ftok(FILE_PATH, 'F');
    semid = semget(key, 3, 0777 | IPC_CREAT);
    if (semid < 0)
    {
        perror("semget error");
        exit(-1);
    }
    printf("%d\n", semid);
    pid_t pid = fork();
    if (pid > 0)
    {
        
        while (1)
        {
            sleep(1);
        }
    }
    if (pid == 0)
    {
        signal(SIGINT, handler);//只能在子进程中注册,在main函数内注册的话,当contrl+c时候,,父子进程都要调用一次handler,也就是删除的函数,则会报错
        while (1)
        {
            sleep(1);
        }
    }
    
    // printf("%x\n", key);
    //  semctl(semid, 0, IPC_RMID);

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

#define FILE_PATH "./sem_demo"

int semid;
pid_t pid;
void delete_sem(int);

void handler(int sig)
{
    printf("handler is used\n");
    delete_sem(0);
    semctl(semid, 0, IPC_RMID);
    exit(-1);
}

void lock(int semid1, int semnum1) // 封装 上锁 函数
{
    struct sembuf sembuffer1[1];
    sembuffer1[0].sem_num = semnum1;
    sembuffer1[0].sem_op = -1;
    sembuffer1[0].sem_flg = SEM_UNDO;
    semop(semid1, sembuffer1, 1);
}

void unlock(int semid1, int semnum1) // 封装 解锁 函数
{
    struct sembuf sembuffer1[1];
    sembuffer1[0].sem_num = semnum1;
    sembuffer1[0].sem_op = 1;
    sembuffer1[0].sem_flg = SEM_UNDO;
    semop(semid1, sembuffer1, 1);
}

void creat_sem(int nsems) // 创建
{
    key_t key = ftok(FILE_PATH, 'f');
    semid = semget(key, nsems, 0777 | IPC_CREAT);
    if (semid < 0)
    {
        perror("semget error");
        exit(-1);
    }
}
void init_sem(int semid, int semnum, int val) // 初始化指定信号量的值
{
    semctl(semid, semnum, SETVAL, val);
}
void delete_sem(int semnum) // 删除制定信号量的值
{
    if (semctl(semid, semnum, IPC_RMID) < 0)
    {
        perror("semctl delete error");
    }
    printf("delete done\n");
}
int main(int argc, char **argv)
{
    int fd = open("a.txt", O_WRONLY | O_APPEND | O_CREAT, 0655);
    if (fd == -1)
    {
        perror("fd error");
        exit(-1);
    }

    creat_sem(1);
    init_sem(semid, 0, 1);
    printf("%d\n", semid);

    pid = fork();
    if (pid > 0)
    {
        while (1)
        {
            lock(semid, 0);
            write(fd, "helloworld", 10);
            write(fd, "helloworld", 10);
            write(fd, "\n", 1);
            unlock(semid, 0);
            sleep(1);
        }
    }
    if (pid == 0)
    {
        signal(SIGINT, handler); // 只能在子进程中注册,在main函数内注册的话,当contrl+c时候,,父子进程都要调用一次handler,也就是删除的函数,则会报错
        while (1)
        {
            lock(semid, 0);
            write(fd, "hhhhhwwwww", 10);
            write(fd, "hhhhhwwwww", 10);
            write(fd, "\n", 1);
            unlock(semid, 0);
            sleep(1);
        }
    }
    return 0;
}

作业:

答: 

sem.h:
#ifndef _MYSEM_H_
#define _MYSEM_H_

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

#define FILE_PATH "./sem_demo"

void creat_sem(int *semid, int nsems);         // 创建
void init_sem(int semid, int semnum, int val); // 初始化指定信号量的值
void lock(int semid1, int semnum1);            // 封装 上锁 函数
void unlock(int semid1, int semnum1);          // 封装 解锁 函数
void delete_sem(int semid, int semnum);        // 删除制定信号量的值
#endif

sem.c:


#include "sem.h"

void delete_sem(int semid, int semnum) // 删除制定信号量的值
{
    if (semctl(semid, semnum, IPC_RMID) < 0)
    {
        perror("semctl delete error");
    }
    printf("delete done\n");
}

void lock(int semid1, int semnum1) // 封装 上锁 函数
{
    struct sembuf sembuffer1[1];
    sembuffer1[0].sem_num = semnum1;
    sembuffer1[0].sem_op = -1;
    sembuffer1[0].sem_flg = SEM_UNDO;
    semop(semid1, sembuffer1, 1);
}

void unlock(int semid1, int semnum1) // 封装 解锁 函数
{
    struct sembuf sembuffer1[1];
    sembuffer1[0].sem_num = semnum1;
    sembuffer1[0].sem_op = 1; 
    sembuffer1[0].sem_flg = SEM_UNDO;
    semop(semid1, sembuffer1, 1);
}

void creat_sem(int *semid,int nsems) // 创建
{
    key_t key = ftok(FILE_PATH, 'f');
    *semid = semget(key, nsems, 0777 | IPC_CREAT);
    if (semid < 0)
    {
        perror("semget error");
        exit(-1);
    }
}

void init_sem(int semid, int semnum, int val) // 初始化指定信号量的值
{
    semctl(semid, semnum, SETVAL, val);
}

sem_abcd.c:

#include "sem.h"
int semid;
int main(int argc, char **argv)
{
    creat_sem(&semid, 4);
    init_sem(semid, 0, 1);
    for (int i = 1; i < 4; ++i)
    {
        init_sem(semid, i, 0);
    }
    pid_t pid1 = fork();
    if (pid1 > 0)
    {
        pid_t pid2 = fork();
        if (pid2 > 0)
        {
            while (1)
            {
                lock(semid, 0);
                printf("A\n");
                sleep(1);
                unlock(semid, 1);
            }
        }
        if (pid2 == 0)
        {
            while (1)
            {
                lock(semid, 1);
                printf("B\n");
                sleep(1);
                unlock(semid, 2);
            }
        }
    }
    else if (pid1 == 0)
    {
        pid_t pid3 = fork();
        if (pid3 > 0)
        {
            while (1)
            {
                lock(semid, 2);
                printf("C\n");
                sleep(1);
                unlock(semid, 3);
            }
        }
        if (pid3 == 0)
        {
            while (1)
            {
                lock(semid, 3);
                printf("D\n");
                sleep(1);
                unlock(semid, 0);
            }
        }
    }
    return 0;
}

google  笔试题:

Google多线程面试题: 4个线程向4个文件里写入数据, 每个线程只能写一个值

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

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

相关文章

重拾html5

新增的position: sticky; 基于用户的滚动位置来定位&#xff0c;粘性定位的元素是依赖于用户的滚动&#xff0c;在 position:relative 与 position:fixed 定位之间切换。ie15以上的低版本不支持&#xff0c;Safari 需要使用 -webkit- prefix&#xff1b; vertical-align: midd…

ToBeWritten之数据源

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…

【你问我答】Unity实现类似DNF地下城勇士的2D人物移动跳跃

文章目录 前言人物节点创建实现简单移动实现攻击效果实现跳跃人物移动跳跃完整代码人物脚底的影子效果最终运行效果源码参考完结 前言 之前有个小伙伴微信找我&#xff0c;想做一个类似DNF地下城勇士的移动跳跃功能&#xff0c;特别是关于2d的跳跃&#xff0c;之前还不是很有头…

Lua03——开发环境搭建

1 安装开发插件 在 idea 或 vscode 中安装 lua 的开发插件 EmmyLua 2 创建工程 在 idea 中创建一个新的工程 工程的类型选择 lua 输入工程名及目标目录 在工程结构的SDK中设置lua在本地安装目录 在工程结构的modules中选择 lua 3 编写第一个lua程序 在工程下添加程序包&#…

C# OpenVinoSharp PP-TinyPose 人体姿态识别

效果 项目 部分代码 using OpenCvSharp; using OpenCvSharp.Extensions; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;name…

【代码随想录day24】不同的二叉搜索树

题目 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xf…

TOWE模块化积木式定制PDU的应用优势

随着计算机网络技术发展&#xff0c;服务器、交换机、各种电子设备等关键设备的需求也日益增加&#xff0c;其承担的业务越来越关键&#xff0c;对设备所处的环境&#xff08;如机房、机柜等&#xff09;要求也越高&#xff0c;所有参与关键设备运行的设施都必须具有高可靠性与…

给苹果手机相册上锁,有3种方法!

手机跟个人的联系越来越密切&#xff0c;总有些不想让别人看到的图片。如果你使用的恰好是苹果手机&#xff0c;想要隐藏相册里的图片&#xff0c;要怎么做&#xff1f;本篇教大家3个方法。 方法1 将iOS更新至16.0版本&#xff0c;打开苹果手机的【设置】&#xff0c;点击【照…

Stable Diffuse AI 绘画 之 ControlNet 插件及其对应模型的下载安装

Stable Diffuse AI 绘画 之 ControlNet 插件及其对应模型的下载安装 目录 Stable Diffuse AI 绘画 之 ControlNet 插件及其对应模型的下载安装 一、简单介绍 二、ControlNet 插件下载安装 三、ControlNet 插件模型下载安装 四、ControlNet 插件其他的下载安装方式 五、Co…

原来Linux这么牛:称霸全球超级电脑 500 强!

还记得之前《全球超级电脑 500 强&#xff0c;中国拿走冠亚军》新闻&#xff1f;虽然昔日超级电脑强权的美国在超级计算机竞赛中落后&#xff0c;但不管哪国打造的超级电脑&#xff0c;还有一件事情值得留意喔──几乎全部都是执行以 Linux 为基础的操作系统&#xff08;注&…

视频号小店怎么进优选联盟?聊下视频号店铺的选品细节,建议收藏

我是王路飞。 视频号小店可能还有很多人不太了解&#xff0c;但是你要知道&#xff0c;红利与机会从来不会消失。 它只会悄悄的转移&#xff0c;转移到你的认知以外&#xff0c;转移到那些新的平台&#xff0c;转移到那些被人忽略的事情里面。 而视频号小店就是目前除了抖音…

【PowerQuery】连接组的复制与粘贴

在实际的应用场景中&#xff0c;单一连接的场景非常少见。通常存在着两个或者两个以上的PowerQuery数据源。在这类场景下一个一个的复制数据源效率非常低下&#xff0c;是否存在更加有效率的数据源复制方式呢&#xff1f;接下来分享的连接组功能就是这样的概念。在PowerQuery中…

动手实践:从栈帧看字节码是如何在 JVM 中进行流转的

Java全能学习面试指南&#xff1a;https://www.javaxiaobear.cn/ 前面我们提到&#xff0c;类的初始化发生在类加载阶段&#xff0c;那对象都有哪些创建方式呢&#xff1f;除了我们常用的 new&#xff0c;还有下面这些方式&#xff1a; 使用 Class 的 newInstance 方法。使用…

【C++】—— 特殊类设计

目录 序言 &#xff08;一&#xff09;设计一个不能被拷贝的类 &#xff08;二&#xff09;设计一个只能在堆上创建对象的类 &#xff08;三&#xff09;设计一个只能在栈上创建对象的类 &#xff08;四&#xff09;设计一个不能被继承的类 总结 序言 特殊类设计是指在面…

AR产业变革中的“关键先生”和“关键力量”

今年6月的WWDC大会上&#xff0c;苹果发布了头显产品Vision Pro&#xff0c;苹果CEO库克形容它&#xff1a; 开启了空间计算时代。 AR产业曾红极一时&#xff0c;但因为一些技术硬伤又减弱了声量&#xff0c;整个产业在起伏中前行。必须承认&#xff0c;这次苹果发布Vision P…

百度文心一言可以接入微信小程序啦!

文心一言(英文名:ERNIE Bot)是百度全新一代知识增强大语言模型,文心大模型家族的新成员,能够与人对话互动,回答问题,协助创作,高效便捷地帮助人们获取信息、知识和灵感 …

python学习之【深拷贝】

#我的编程语言学习笔记# 前言 上一篇文章python学习之【浅拷贝】 学习了python中的浅拷贝相关内容&#xff0c;这篇文章接着学习深拷贝。 简单回顾 浅拷贝只拷贝浅层元素&#xff0c;深层元素的内存地址不改变 &#xff1b;当对拷贝产生的新的对象的浅层元素进行更改时&…

擎创技术流 | 深入浅出运维可观测工具(三):eBPF如何兼容多架构模式性能管理

嗨~又见面了大家&#xff01; 之前给大家分享过2篇eBPF技术干货&#xff0c;后台收到的反馈还挺好的&#xff0c;以至于总有朋友过来催更这一系列&#xff0c;这不第3篇在大家的千呼万唤下终于出来了。 新来的朋友点这里&#xff0c;键回看eBPF精彩技术贴&#xff0c;别忘了随…

Vue2安装vuex和vue-router报错处理

Vue2安装vuex和vue-router报错处理 Vue2.6安装VuexVue2.6安装vue-router Vue2.6安装Vuex 报错信息 处理方法 #查看vuex版本 npm view vuex versions --json #安装合适版本 npm install vuex3.6.2 --saveVue2.6安装vue-router 报错信息 处理方法 #查看vue-router版本 npm…