Linux C——进程间通信

通信方式

1.无名管道
2.有名管道
3.信号
4.共享内存
5.消息队列
6.信号灯集
7.套接字

无名管道

特点

1.只能在具有亲缘关系的进程间通信
2.半双工的通信方式,具有固定的读端fd[0]和写端fd[1]
3.管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数.
4.管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符,fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

函数

pipe()

定义:
int pipe(int pipefd[2]);
功能:
    创建管道,返回两个文件描述符,fd[0]用于读,fd[1]用于写。
参数:
    pipefd[2],一个整数数组,用于存放两个文件描述符。
返回值:
    成功返回0,出错返回-1,并设置errno。

读写特性

1.读端:
1)当管道中无数据时,读操作会阻塞
2)管道中无数据时,将写端关闭,读操作会立即返回
2.写端:
1)管道中装满(64k)数据时,写阻塞,一旦有4k空间,写继续
2)当读端不存在时,写操作会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号:Broken pipe。

示例代码

创建子进程,父进程循环从终端输入字符串,子进程循环打印输入的字符串,当输入quit时程序结束。

#include <stdio.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    char buf[32] = "";
    int fd[2] = {0};
    if (pipe(fd) < 0)
    {
        perror("pipe create error");
        return -1;
    }
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork error");
        return -1;
    }
    // 子进程
    else if (pid == 0)
    {
        while (1)
        {
            read(fd[0], buf, 32);
            if (strcmp(buf,"quit")==0)
                break;
            printf("%s\n", buf);
        }
    }
    // 父进程
    else if (pid > 0)
    {
        while (1)
        {
            scanf("%s", buf);
            write(fd[1], buf, 32);
            if (strcmp(buf,"quit")==0)
                break;
        }
        wait(NULL);
    }
    return 0;
}

有名管道、

特点

1.可以用于两个互不相关的进程间通信
2.有名管道是实际存在的一个文件,可以用文件路径指出,但其内容存放在内存中
3.通过文件IO来操作管道
4.有名管道遵循先进先出原则
5.有名管道不支持iseek之类的操作

函数

mkfifo()

定义:
int mkfifo(const char *pathname, mode_t mode);
功能:
    创建一个命名管道。
参数:
    pathname,管道的路径名。
    mode,管道权限。
返回值:
    成功返回0,出错返回-1,并设置errno。

读写特性

1.用只写模式打开有名管道时,写端会阻塞,直到有读端打开。
2.用只读模式打开有名管道时,读端会阻塞,直到有写端打开。
3.用可读可写模式打开有名管道时,如果管道中没有数据,读端会阻塞。

示例代码

创建两个独立的进程,实现一个进程循环输入字符串,另一个进程打印输入的字符串,当输入quit时结束。

// 输入端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    if (mkfifo("./fifo", 0666) < 0)
    {
        if (errno == EEXIST)
            printf("FILE EXIST\n");
        else
        {
            perror("mkfifo error");
            return -1;
        }
    }
    printf("mkfifo ready\n");
    char buf[32] = "";
    int fd = open("./fifo", O_WRONLY);
    if (fd < 0)
    {
        perror("open error");
        return -1;
    }
    while (1)
    {
        scanf("%s",buf);
        write(fd, buf, 32);
        if (strcmp(buf,"quit")==0)
            break;
    }
    return 0;
}
// 输出端
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char const *argv[])
{   /* 此段用于创建管道,已在输入端创建,可写可不写
    if (mkfifo("./fifo", 0666) < 0)
    {
        if (errno == EEXIST)
            printf("FILE EXIST\n");
        else
        {
            perror("mkfifo error");
            return -1;
        }
    }
    printf("mkfifo ready\n");
    */
    char buf[32] = "";
    int fd = open("./fifo", O_RDONLY);
    if (fd < 0)
    {
        perror("open error");
        return -1;
    }
    while (1)
    {
        read(fd, buf, 32);
        if (strcmp(buf,"quit")==0)
            break;
        printf("%s\n", buf);
    }
    return 0;
}

使用以下命令为两个程序生成独立的可执行文件,然后在终端分别执行两个进程,方便观察结果:

# 编译
gcc in.c -o in
gcc out.c -o out
# 运行输入端
./in
# 运行输出端
./out

运行结果

image.png

信号

信号的概念

1.信号是一种软件中断,它是由操作系统发送给进程的一种通知消息,这是一种异步通信方式。
2.信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
3.如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号的分类

在Linux操作系统中,信号分为可靠信号和不可靠信号,一共64种,可用kill -l命令查看。
1.不可靠信号:也称为非实时信号,不支持排队,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,信号值取值区间为1~31。
2.可靠信号:也称为实时信号,支持排队,信号不会丢失,发多少次,就可以收到多少次,信号值取值区间为34~64。

信号的产生方式

1.对于前台进程,用户可以输入特殊终端字符来发送:比如:输入ctrl c
2.在终端运行kill命令或者在程序中调用 kill 函数
3.系统状态变化,比如alarm定时器到期时将引起SIGALRM信号

信号的处理方式

1.忽略信号:不做任何处理
2.捕获信号:执行自定义的信号处理函数
3.执行默认操作:Linux系统中对每种信号规定了默认操作,即执行信号默认的功能

函数

kill()

定义:
int kill(pid_t pid, int sig);
功能:
    向进程发送信号。
参数:
    pid,进程ID。
    sig,信号值。
返回值:
    成功返回0,出错返回-1,并设置errno。

raise()

定义:
int raise(int sig);
功能:
    向调用该函数的进程发送信号。
参数:
    sig,信号值。
返回值:
    成功返回0,出错返回-1,并设置errno。

alarm()

定义:
unsigned int alarm(unsigned int seconds);
功能:
    设置一个定时器,在指定的时间后向进程发送SIGALRM信号结束进程。
    如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替
参数:
    seconds,定时器时间,单位为秒。
返回值:
    成如果调用此alarm()前,进程中已经设置了闹钟时间,
则返回上一个闹钟时间的剩余时间,否则返回0

pause()

定义:
int pause(void);
功能:
    挂起进程,直到收到信号。
参数:
    无
返回值:
    无

signal()

定义:
void (*signal(int signum, void (*func)(int)))(int);
或:
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:
    signum:要处理的信号
    handler:信号处理方式
            SIG_IGN:忽略信号
            SIG_DFL:执行默认操作
            handler:捕捉信号  
返回值:
    成功:设置之前的信号处理方式
    失败:-1

示例代码

用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <stdlib.h>
pid_t pid;
void conductor(int sig)
{
    switch (sig)
    {
    case SIGINT:
        kill(getppid(), SIGUSR1);
        break;
    case SIGQUIT:
        kill(getppid(), SIGUSR2);
        break;
    case SIGUSR1:
        printf("Conductor:please get off the bus\n");
        exit(0);
        break;
    }
}
void driver(int sig)
{
    switch (sig)
    {
    case SIGUSR1:
        printf("Driver:let's gogogo\n");
        break;
    case SIGUSR2:
        printf("Driver:stop the bus\n");
        break;
    case SIGTSTP:
        kill(pid, SIGUSR1);
        wait(NULL);
        exit(0);
        break;
    }
}
int main(int argc, char const *argv[])
{
    pid = fork();
    if (pid < 0)
    {
        perror("fork error");
        return -1;
    }
    // 子进程(售票员)
    else if (0 == pid)
    {
        while(1)
        {
        signal(SIGINT, conductor);
        signal(SIGQUIT, conductor);
        signal(SIGUSR1, conductor);
        signal(SIGTSTP, SIG_IGN);
        }    
    }
    // 父进程(司机)
    else
    {
        while(1)
        {
        signal(SIGUSR1, driver);
        signal(SIGUSR2, driver);
        signal(SIGTSTP, driver);
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
        }
    }
    return 0;
}

通过题目的描述,可以得出以下结果:
司机:
忽略的信号:SIGINT、SIGQUIT
捕捉的信号:SIGUSR1、SIGUSR2、SIGTSTP
售票员:
忽略的信号:SIGTSTP
捕捉的信号:SIGINT、SIGQUIT、SIGUSR1
分别在父、子进程用signal函数捕捉相应的函数,然后再将信号传入对应的函数用switch语句进行处理。

运行结果

image.png

共享内存

概念

共享内存指的是操作系统在物理内存中申请一块空间,应用程序可以映射到这块空间,进行直接读写操作。

特点

1.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2.为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
3.进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率
4.由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

操作步骤

1.创建唯一key值
2.创建或打开共享内存
3.映射到进程地址空间
4.读写共享内存
5.解除映射
6.删除共享内存

函数

创建key值:ftok()

定义:
key_t ftok(const char *pathname, int proj_id);
功能:
    创建一个唯一的key值。
参数:
    pathname,共享内存的路径名。
    proj_id,项目ID。
返回值:
    成功返回key值,出错返回-1,并设置errno。

创建共享内存:shmget()

定义:
int shmget(key_t key, size_t size, int flag);
功能:
    创建一个共享内存,并返回一个唯一的key值。
参数:
    key,共享内存的key值。
    size,共享内存的大小。
    flag,共享内存的访问权限。
返回值:
    成功返回共享内存的ID,出错返回-1,并设置errno。

映射共享内存:shmat()

定义:
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
    映射共享内存到进程地址空间。
参数:
    shmid,共享内存的ID。
    shmaddr,映射到进程地址空间的起始地址,一般为NULL,表示由系统自动完成映射。
    shmflg,映射标志,SHM_RDONLY就是对该共享内存只进行读操作。
返回值:
    成功返回映射到进程地址空间的起始地址,出错返回(void *)-1,并设置errno。

取消映射:shmdt()

定义:
int shmdt(const void *shmaddr);
功能:
    取消映射共享内存。
参数:
    shmaddr,映射到进程地址空间的起始地址。
返回值:
    成功返回0,出错返回-1,并设置errno。

删除共享内存:shmctl()

定义:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
    删除共享内存。
参数:
    shmid,共享内存的ID。
    cmd,操作命令
        IPC_STAT 获得shmid属性信息,存放在第三参数
        IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
        IPC_RMID:删除共享内存,此时第三个参数为NULL即可
    buf,共享内存的相关信息。
返回值:
    成功返回0,出错返回-1,并设置errno。

相关命令

ipcs -m

查看系统中创建的共享内存

ipcrm -m [shmid]

删除创建的共享内存

示例代码

通过共享内存实现进程间通信,一个进程从终端输入数据,另一个进程打印数据,循环执行,当输入quit时循环结束

// 输入端
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
typedef struct Data
{
    int flag;
    char buf[32];
} data;
int main(int argc, char const *argv[])
{
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok error");
        return -1;
    }
    int shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid <= 0)
    {
        if (errno == EEXIST)
        {
            printf("file exist\n");
            shmid = shmget(key, 64, 0666);
        }
        else
        {
            perror("shmget error");
            return -1;
        }
    }
    data *p = shmat(shmid, NULL, 0);
    if (p == (void *)-1)
    {
        perror("shmat error");
        return -1;
    }
    while (1)
    {
        if (p->flag == 0)
        {
            scanf("%s", p->buf);
            p->flag = 1;
            if (strcmp(p->buf, "quit") == 0)
                break;
        }
    }
    return 0;
}
// 输出端
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
typedef struct Data
{
    int flag;
    char buf[32];
} data;
int main(int argc, char const *argv[])
{
    key_t key = ftok("./app", 'a');
    if (key < 0)
    {
        perror("ftok error");
        return -1;
    }
    int shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid <= 0)
    {
        if (errno == EEXIST)
        {
            printf("file exist\n");
            shmid = shmget(key, 64, 0666);
        }
        else
        {
            perror("shmget error");
            return -1;
        }
    }
    data *p = shmat(shmid, NULL, 0);
    if (p == (void *)-1)
    {
        perror("shmat error");
        return -1;
    }
    while (1)
    {
        if (p->flag == 1)
        {
            if (strcmp(p->buf, "quit") == 0)
                break;
            printf("%s\n", p->buf);
            p->flag = 0;
        }
    }
    shmdt(p);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

信号灯集

概念

信号灯(semaphore),也就是信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制;
System V信号灯集是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯。
通过信号灯集实现共享内存的同步操作。

操作步骤

1.创建key值
2.创建或打开信号灯集
3.初始化信号灯集
4.P、V操作
5.删除信号灯集

函数

创建信号灯集:semget()

定义:
int semget(key_t key, int nsems, int semflg);
功能:
    创建一个信号灯集,并返回一个唯一的key值。
参数:
    key,信号灯集的key值。
    nsems,信号灯集中信号灯的个数。
    semflg,信号灯集的访问权限,通常为IPC_CREAT |0666
返回值:
    成功返回信号灯集的ID,出错返回-1,并设置errno。

在创建信号灯集后,如果进行了容错判断,即判断semget函数的返回值,并且设置了perror错误提示,运行后如果出现以下提示:

错误提示:Success

这说明此时的信号灯集ID等于0,可以用以下命令删除信号灯集重新创建:

ipcs -s

查看信号灯集

ipcrm -m [semid]

删除信号灯集

初始化或删除信号灯集:semctl()

定义:
int semctl(int semid, int semnum, int cmd, ... /* arg */ );
功能:
    初始化或删除信号灯集。
参数:
    semid,信号灯集的ID。
    semnum,信号灯的编号,从0开始。
    cmd,操作命令
        GETVAL:获取信号灯的值,返回值是获得值
        SETVAL:设置信号灯的值
        IPC_RMID:删除信号灯集,此时第三个参数为NULL即可
    arg,信号灯属性,具体取决于cmd的值,当cmd为SETVAL时,需要传递共用体
返回值:
    成功返回0,出错返回-1,并设置errno。

P、V操作:semop()

定义:
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:
    P、V操作。
参数:
    semid,信号灯集的ID。
    sops,操作方式
    nsops,要操作的信号灯的个数。
返回值:
    成功返回0,出错返回-1,并设置errno。
struct sembuf {
    short  sem_num;   // 要操作的信号灯的编号
    short  sem_op;    // 0 :  等待,直到信号灯的值变成0
                      // 1  :  释放资源,V操作
                      // -1 :  分配资源,P操作                    
    short  sem_flg;   // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};

消息队列

概念和特点

1.消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
2.消息队列可以按照类型来发送/接收消息
3.在linux下消息队列的大小有限制:
1)消息队列个数最多为16个;
2)每个消息队列总容量最多为16384字节;
3)每个消息内容最多为8192字节

操作步骤

1.创建key值
2.创建或打开消息队列
3.添加消息
4.读取消息
5.删除消息队列

函数

创建消息队列:msgget()

定义:
int msgget(key_t key, int msgflg);
功能:
    创建一个消息队列,并返回一个唯一的key值。
参数:
    key,消息队列的key值。
    msgflg,消息队列的访问权限,通常为IPC_CREAT |0666
返回值:
    成功返回消息队列的ID,出错返回-1,并设置errno。

添加消息:msgsnd()

定义:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:
    向消息队列中添加消息。
参数:
    msqid,消息队列的ID。
    msgp,消息内容的起始地址。
    msgsz,消息内容的大小。
    msgflg,消息发送标志,通常为0。
返回值:
    成功返回0,出错返回-1,并设置errno。

读取消息:msgrcv()

定义:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:
    从消息队列中读取消息。
参数:
    msqid,消息队列的ID。
    msgp,消息内容的起始地址。
    msgsz,消息内容的大小。
    msgtyp,消息类型,0表示接收所有类型消息。
    msgflg,消息接收标志,通常为0。
返回值:
    成功返回消息的大小,出错返回-1,并设置errno。

删除消息队列:msgctl()

定义:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:
    删除消息队列。
参数:
    msqid,消息队列的ID。
    cmd,操作命令
        IPC_STAT 获得msqid属性信息,存放在第三参数
        IPC_SET 设置msqid属性信息,要设置的属性放在第三参数
        IPC_RMID:删除消息队列,此时第三个参数为NULL即可
    buf,消息队列的相关信息。
返回值:
    成功返回0,出错返回-1,并设置errno。

相关命令

ipcs -q

查看系统中创建的消息队列

ipcrm -q [msqid]

删除创建的消息队列

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇