Linux进程间通信 - 信号(signal) 与 管道(pipe) 与 消息队列

用signal函数捕捉信号SIGINT 用系统调用函数fork( )创建两个子进程,再用系统调用函数signal()让父进程捕捉信号SIGINT(用kill命令来触发),当捕捉到中断信号后,父进程用系统调用函数kill()向两个子进程发出信号,子进程捕捉到父进程发来的信号后,分别输出下列信息后终止: Child process 1 is killed by parent! Child process 2 is killed by p... 阅读详情

什么是进程间通信,就是进程与进程之间进行通信,互相发送消息;可以通过 信号 或者 管道 或者 消息队列 或者 信号量 去通信!


目录

一、信号

1. 信号简介 

2. 都有那些信号?

3. 注册信号的函数

1). signal

2). sigaction (项目中强烈推荐使用)

4. 信号发送

1). kill 函数

2). alarm 函数

3). raise 函数

5. 发送多个信号的情况

6. 信号集

1). sigemptyset

2). sigfillset

3). sigdelset

4). sigaddset

5). sigismember

7. 进程的“信号屏蔽字”

1). 修改进程的“信号屏蔽字”

2). 获取未处理的信号

8. 阻塞式等待信号

1). pause

2). sigsuspend

二、管道

1. 管道简介 

2. 管道的创建

1). pipe

2). 例一 :多进程使用管道通信

3). 例二:关闭管道的读端/写端

4). 例三:父进程循环给子进程发送消息

3. popen / pclose

1). popen

2). pclose

3). 例一:读取命令返回的数据

4). 例二:把输出写到外部程序

5). popen的优缺点

三、消息队列

1. 什么是消息队列?

2. msgget 消息队列的获取

3. msgsnd 消息的发送

4. msgrcv 消息的接收

5. msgctl 消息的控制

6. 示例一:两程序间通过消息队列去通信

7. 多进程间消息队列通信

8. 练习

四、信号量

1. 什么是信号量

2. semget 信号量的获取

3. semop 信号量的操作

4. semctl 信号量的控制

5. 例

五、共享内存机制

六、总结


一、信号

1. 信号简介 

什么是信号?

信号是给程序提供一种可以处理异步事件的方法,它利用软件中断来实现。不能自定义信号,所有信号都是系统预定义的。

信号由谁产生?

1. 由shell终端根据当前发生的错误(段错误、非法指令等)Ctrl+c而产生相应的信号

     比如:

            socket通信或者管道通信,如果读端都已经关闭,执行写操作(或者发送数据),

            将导致执行写操作的进程收到SIGPIPE信号(表示管道破裂);

            该信号的默认行为:终止该进程。

                       

2. 在shell终端,使用kill或killall命令产生信号

例:注册SIGINT信号,也就是键盘按下ctrl+c后触发的信号,让其执行我们代码中自己定义的函数!

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

// 自定义信号处理函数,其有固定格式,返回值:void; 参数 int
void myHandle(int sig) {
    printf("catch a signal : %d\n", sig);
}

int main(int argc, char **argv) {
    
    // 注册SIGINT信号(ctrl+c),接收到此信号执行myHandle方法
    sighandler_t result = signal(SIGINT, myHandle);
    if (SIG_ERR	 == result) {
        printf("发生错误了!\n");
        perror("错误原因:");
        exit(-1);
    }
    
    while (1) sleep(1);
    
    return 0;
}

当然,也可以在另一个终端使用kill命令去发送信号: kill -SIGINT 进程id

2. 都有那些信号?

信号
信号名称说明
SIGABORT进程异常终止
SIGALRM超时警告
SIGFPE浮点运算异常
SIGHUP连接挂断
SIGILL非法指令
SIGINT终端中断(Ctrl+C 将产生该信号)
SIGKILL终止进程
SIGPIPE向没有读权限的进程的管道写数据
SIGQUIT终端退出(Ctrl+\ 将产生该信号)
SIGSEGV无效内存段访问
SIGTERM终止
SIGUSR1用户自定义信号1
SIGUSR2用户自定义信号2
---华丽的分割线---在此以上的信号如果不被捕获,则进程自己接收到后都会终止;
SIGCHLD子进程已停止或退出
SIGCONT让暂停的进程继续执行
SIGSTOP停止执行(即“暂停”
SIGTTIN后台进程尝试读操作
SIGTTOU后台进程尝试写操作

 信号的处理

        1. 忽略此信号,signal(SIGINT, SIG_IGN);        // 忽略SIGINT(ctrl+c)信号

        2. 捕捉信号,指定的信号处理函数进行处理,向上面👆代码那样,注册了之后,会自动捕获!

        3. 执行系统默认动作,即不用捕捉它,让系统自己处理,大多数都是终止进程的信号;

信号的捕获

        信号的捕获,是指,接收到某种信号后,去执行指定的函数;

        注意:SIGKILL 和 SIGSTOP 不能被捕获,即这两种信号的响应动作不能被改变。

3. 注册信号的函数

1). signal

#include <signal.h>

typedef void (*sighandler_t)(int);        // 信号处理函数定义格式

sighandler_t signal(int signum, sighandler_t handler);

描述:注册信号,使得收到对应注册的信号后执行指定的函数; 

参数: 

        signum

                信号;

        handler

                函数指针;

                还可以使用以下特殊值:

                        SIG_IGN     忽略信号

                        SIG_DFL     恢复默认行为

返回值:

        成功:返回上一个信号处理函数的指针,如果是第一次执行signal,则返回SIG_DFL;

        失败:返回SIG_ERR,并设置错误标志errno。

例:

注册SIGINT信号,使得其执行代码中自定义的函数,然后再函数中恢复默认行为

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

void myHandle(int sig) {
    printf("catch a signal : %d\n", sig);
    
    // 恢复默认函数行为
    signal(SIGINT, SIG_DFL);    // 等同于signal(sig, SIG_DFL);
}

int main(int argc, char **argv) {
    
    // 注册SIGINT信号(ctrl+c),接收到此信号执行myHandle方法
    sighandler_t result = signal(SIGINT, myHandle);
    if (SIG_ERR	 == result) {
        printf("发生错误了!\n");
        perror("错误原因:");
        exit(-1);
    }
    
    while (1) sleep(1);
    
    return 0;
}

第一次按下ctrl+c,执行了自定义的函数;第二次再按ctrl+c ,程序就结束了!达到预期效果!

另外,SIGUSR1 和 SIGUSR2是可以给我们程序员自己使用的,他俩没有默认绑定任何信号操作函数,可以根据自己的实际需求去注册绑定和使用!

例:

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

void myHandle1(int sig) {
    printf("catch a signal : %d\n", sig);
}

void myHandle2(int sig) {
    printf("接收到了信号 : %d\n", sig);
}

int main(int argc, char **argv) {
    
    // 注册SIGUSR1和SIGUSR2信号,接收到此信号执行myHandle方法
    signal(SIGUSR1, myHandle1);
    signal(SIGUSR2, myHandle2);
    
    while (1) sleep(1);
    
    return 0;
}

2). sigaction (项目中强烈推荐使用)

sigaction与signal的区别: sigaction比signal更“健壮”,建议使用sigaction;

因为signal是前期很早之前的函数了,会有很多欠缺的地方;所以sigaction横空出世!

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

描述:sigaction()系统调用用于改变进程在上采取的操作接收特定信号。

参数:

        sigumn

                信号;可以是除SIGKILL和SIGSTOP之外的任何有效信号;

        act

                结构体;一些对信号的设置;

        oldact

                结构体;获取上一个的信号设置act;

返回值:

        成功:返回0;

        失败:返回-1,并设置错误标志errno;

结构 struct sigaction

struct sigaction {
        void     (*sa_handler)(int);        /* 信号相应的函数 */
        void     (*sa_sigaction)(int, siginfo_t *, void *);        // 可以不管它
        sigset_t   sa_mask;                /* 屏蔽信号集 */
         int        sa_flags;                    /* 置为0即可,其他操作可以 man 2 sigaction 去查看 */
         void     (*sa_restorer)(void);        // 可以不管他
};

屏蔽信号集

        并不是说屏蔽该信号;如果sa_mask包含了信号A,在信号处理函数期间,信号A触发了,则阻塞信号A,直到信号处理函数结束,才开始处理信号A;即,信号处理函数执行完之后,再响应该信号A。(下面 多信号发送 会有例子)

例:

使用sigaction注册信号,且sa_flags设置为SA_RESETHAND,即只会调用一次指定的处理函数,之后恢复默认行为

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

void myHandle(int sig) {
    printf("catch a signal : %d\n", sig);
}

int main(int argc, char **argv) {
    
    struct sigaction act;
    struct sigaction oldact;
    
    act.sa_handler = myHandle;  // 处理函数
    sigemptyset(&act.sa_mask);  // 设置为0
    //act.sa_flags = 0;
    act.sa_flags = SA_RESETHAND;    // 只会调用一次上面指定的处理函数,之后恢复默认行为
    
    // 注册SIGINT信号(ctrl+c),接收到此信息执行myHandle方法
    int ret = sigaction(SIGINT, &act, &oldact);
    if (-1 == ret) {
        printf("sigaction error!\n");
        perror("reason:");
        exit(-1);
    }
    
    while (1) sleep(1);
    
    return 0;
}

 第一次按下ctrl+c,执行了自定义的函数;第二次再按ctrl+c ,程序就结束了!达到预期效果!

4. 信号发送

在前面的例子中,使用kill可以在终端给进程发送信号;当然,kill也有函数,也可以使用函数去发送信号;

信号的发送方式:

  •         1. 在shell终端用快捷键产生信号;
    •         2. 使用kill,killall命令;
      •         3. 使用kill函数和alarm函数和raise函数。

1). kill 函数

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

int kill(pid_t pid, int sig);

描述:给指定的进程发送指定的信号;

参数:

        pid

                进程id;

        sig

                信号;

返回值:

        成功:返回0;

        失败:返回-1,并设置错误标志errno;

注意:

        给指定的进程发送信号需要权限:即普通用户只能给普通用户的进程发送信号,root用户可以给所有用户的进程发送信号;

例1:

创建一个子进程,子进程每秒中输出字符串“child process work!",父进程等待用户输入,如果用户按下字符A, 则向子进程发信号SIGUSR1, 子进程的输出字符串改为大写; 如果用户按下字符a, 则向子进程发信号SIGUSR2, 子进程的输出字符串改为小写

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

int workflag = 0;

void work_up_handle(int sig) {
    workflag = 1;
}

void work_down_handle(int sig) {
    workflag = 0;
}

int main(int argc, char **argv) {
    pid_t pd;
    char c;
    
    // 创建一个子进程
    pd = fork();
    if (-1 == pd) {
        printf("fork error!\n");
        exit(1);
    
    } else if (0 == pd) {
        char *msg;
        struct sigaction act;
        act.sa_flags = 0;
        act.sa_handler = work_up_handle;   
        sigemptyset(&act.sa_mask);
        int ret = sigaction(SIGUSR1, &act, 0);    // 注册SIGUSR1信号
        if (-1 == ret) {
            printf("sigaction SIGUSR1 error!\n");
            perror("reason:");
            exit(-1);
        }
        
        act.sa_handler = work_down_handle;
        ret = sigaction(SIGUSR2, &act, 0);    // 注册SIGUSR2信号
        if (-1 == ret) {
            printf("sigaction SIGUSR2 error!\n");
            perror("reason:");
            exit(-2);
        }
        
        while (1) {
            if (!workflag) {
                msg = "child process work!";
            } else {
                msg = "CHILD PROCESS WORK!";
            }
            
            printf("%s\n", msg);
            sleep(2);
        }
    
    } else {
        while(1) {
            c = getchar();
            if ('A' == c) {
                // 给子进程发送SIGUSR1信号
                int ret = kill(pd, SIGUSR1);
                if (-1 == ret) {
                    printf("kill SIGUSR1 error!\n");
                    perror("reason:");
                }
            } else if ('a' == c) {
                // 给子进程发送SIGUSR2信号
                int ret = kill(pd, SIGUSR2);
                if (-1 == ret) {
                    printf("kill SIGUSR2 error!\n");
                    perror("reason:");
                }
            }
        }
    }
    
    return 0;
}

例2:

父进程创建子进程后,注册一个信号后旧调用函数pause()挂起(休眠),直到父进程收到任意一个信号才会唤醒;子进程五秒后给父进程发送任意一个信号,唤醒父进程,使其结束程序!

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


int workflag = 0;

void wake_handle(int sig) {
    workflag = 1;
}

int main(int argc, char **argv) {
    pid_t pd;
    
    pd = fork();    // 创建进程
    if (-1 == pd) {
        printf("fork error!\n");
        exit(-1);
    
    } else if (0 == pd) {
        sleep(5);
        int ret = kill(getppid(), SIGALRM);   // 给父进程发送SIGALRM信号
        if (-1 == ret) {
            printf("kill SIGALRM error!\n");
            perror("reason:");
        }
        
    } else {
        struct sigaction act;
        act.sa_handler = wake_handle;
        act.sa_flags = 0;
        sigemptyset(&act.sa_mask);
        
        int ret = sigaction(SIGALRM, &act, 0);    // 注册SIGALRM信号
        if (-1 == ret) {
            printf("sigaction error!\n");
            perror("reasion:");
            exit(-1);
        }
        
        // 把当前进程挂起,直到接收到任何一个信号
        pause();    
        
        if (workflag) {
            printf("父进程结束挂起!\n");
        }
    }
    
    int status = 0;
    wait(&status);
    return 0;
}

2). alarm 函数

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

描述:在指定的时间之内给进程本身发送一个SIGALRM信号;

参数:

        seconds

                秒数;

返回值:

        成功:返回上一次alarm执行后剩余的时间(秒),如果没有,则返回0;

        失败:返回-1;

注意:

        参数单位是秒,如果参数为0,则取消已设置的闹钟;如果执行了alarm,但时间还没有到,再次调用alarm,则闹钟将重新定时,每个进程最多只能使用一个闹钟,也就是只能使用一个alarm;

也可以说alarm是一个闹钟,也可以说是定时器!

例:

进程调用pause()函数挂起,调用alarm(3),3秒后唤醒进程!

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


int workflag = 0;

void wake_handle(int sig) {
    workflag = 1;
}

int main(int argc, char **argv) {
    
    int ret = 0;
    
    struct sigaction act;
    act.sa_handler = wake_handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    
    sigaction(SIGALRM, &act, 0);    // 注册SIGALRM信号
    
    ret = alarm(3);     // 3秒后给自己发送SIGALRM信号
    if (-1 == ret) {
        printf("alarm error!\n");
        exit(-1);
    }
    
    printf("进程开始挂起(睡眠)\n");
    pause();    // 把当前进程挂起,直到接收到任何一个信号
    
    
    if (workflag) {
        printf("进程被唤醒!\n");
    }   
        
    return 0;
}

3). raise 函数

#include <signal.h>

int raise(int sig);

描述:给本进程自身发送信号;

参数:

        sig

                信号;

返回值:

        成功:返回0;

        失败:返回非0;

例:

使用raise给自身进程发送SIGUSR1信号;

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


void wake_handle(int sig) {
    printf("接收到信号:%d\n", sig);
}

int main(int argc, char **argv) {
    
    int ret = 0;
    
    struct sigaction act;
    act.sa_handler = wake_handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    
    sigaction(SIGUSR1, &act, 0);    // 注册SIGUSR1信号
    
    // 给自身进程发送SIGUSR1信号
    ret = raise(SIGUSR1);       // 相当于 kill(getpid(), SIGUSR1);
    if (0 != ret) {
        printf("raise error!\n");
        exit(-1);
    }
  
    return 0;
}

5. 发送多个信号的情况

情况一

        某进程正在执行某个信号对应的操作函数期间(该信号的安装函数),如果此时,该进程又多次收到同一个信号(同一种信号值的信号),

        则:如果该信号是不可靠信号(小于32),则只能再响应一次(即加上正在执行的一次,最多是响应两次,其他都丢弃)。

        如果该信号是可靠信号(大于32),则能再响应多次(不会遗漏)。但是,都是都必须等该次响应函数执行完之后,才能响应下一次。

情况二

        某进程正在执行某个信号对应的操作函数期间(该信号的安装函数),如果此时,该进程收到另一个信号(不同信号值的信号);

        如果该信号被包含在当前信号的signaction的sa_mask(信号屏蔽集)中,则不会立即处理该信号。直到当前的信号处理函数执行完之后,才去执行该信号的处理函数。(如果也是不可靠的信号,那么最多也只会再响应多一次)

        否则:则立即中断当前执行过程(如果处于睡眠,比如sleep, 则立即被唤醒)而去执行这个新的信号响应。新的响应执行完之后,再在返回至原来的信号处理函数继续执行。

例:

用以下代码,就可以测试上面两种情况的真实性;

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


void myhandle1(int sig) {
    printf("myhandle1收到了信号:%d\n", sig);
    int i = 0;
    for (; i < 10; i++) {
        sleep(1);
    }
    
    printf("myhandle1信号处理函数结束:%d\n", sig);
}

void myhandle2(int sig) {
    printf("myhandle2 Catch a signal:%d\n", sig);
    int i = 0;
    for (; i < 10; i++) {
        sleep(1);
    }
    
    printf("myhandle2 Catch end!:%d\n", sig);
}

int main(int argc, char **argv) {
    
    int ret = 0;
    
    struct sigaction act, act2;
    act.sa_handler = myhandle1;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGUSR2);   // 信号集SIGUSR2
    sigaction(SIGUSR1, &act, 0);    // 注册SIGUSR1信号
    
    
    act2.sa_handler = myhandle2;
    sigemptyset(&act2.sa_mask);
    act2.sa_flags = 0;
    sigaction(SIGUSR2, &act2, 0);   // 注册SIGUSR2信号
    
    while (1) { sleep(1); }
     
    return 0;
}

测试情况一,使用信号SIIGUSR2去测试,连续发送多条SIGUSR2信号,测试到底是不是像上面说的那样!

 可以看到,发送了多条SIGUSR2之后,程序也只是做了两次响应,且是排队响应的!测试达到预期!

测试情况二,包含信号屏蔽集;首先发送SIGUSR1信号后,接连发送多条SIGSUR2信号,因为SIGSUR2信号在信号集中,测试是否等待排队响应!

可以看到,发送了多条SIGUSR2之后,程序也只是做了两次响应,且是排队响应的!测试达到预期!

测试情况二,不包含信号屏蔽集;现在将代码中sigaddset(&act.sa_mask, SIGUSR2);这句代码给注释掉,首先发送SIGUSR1信号后,接连发送多条SIGSUR2信号,因为SIGSUR2信号在信号集中,测试是否会中断当前的执行,而去执行新的信号响应;

当发送SIGUSR1信号后,右边程序立即大于“收到了信号:10”,当发送SIGUSR2信号后,立即中断SIGUSR1信号的响应操作,转而执行SIGUSR2信号的响应函数,执行完毕之后,才回来继续执行SIGUSR1信号的响应函数!测试达到预期!

注意:以上三个测试都是使用小于32的信号进行测试,所以即使发送了多个相同的信号,也只是执行了一次而已!

6. 信号集

什么是信号集?

信号集,用sigset_t类型表示,实质是一个无符号长整形;用来表示包含多个信号的集合。

信号集的基本操作

1). sigemptyset

#include <signal.h>

int sigemptyset(sigset_t *set);

描述:把信号集清空;

参数:

        set

                &act.sa_mask

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

例:

struct sigaction act;
int ret = sigemptyset(&act.sa_mask);
if (-1 == ret) {
    printf("sigemptyset error!\n");
    perror("reason:");    // # include <errno.h>
}

2). sigfillset

#include <signal.h> 

int sigfillset(sigset_t *set);

描述:把所有已经定义的全部信号填充到指定信号集;

参数:

        set

                &act.sa_mask

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

例:

struct sigaction act;
int ret = sigfillset(&act.sa_mask);
if (-1 == ret) {
    printf("sigfillset error!\n");
    perror("reason:");    // # include <errno.h>
}

3). sigdelset

#include <signal.h> 

int sigdelset(sigset_t *set, int signum);

描述:从指定的信号集中删除指定的信号;

参数:

        set

                &act.sa_mask

        signum

                信号;

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

例:

struct sigaction act;
int ret = sigdelset(&act.sa_mask, SIGUSR2);
if (-1 == ret) {
    printf("sigdelset error!\n");
    perror("reason:");    // # include <errno.h>
}

4). sigaddset

#include <signal.h> 

int sigaddset(sigset_t *set, int signum);

描述:从指定的信号集中添加指定的信号;

参数:

        set

                &act.sa_mask

        signum

                信号;

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

例:

struct sigaction act;
int ret = sigaddset(&act.sa_mask, SIGUSR2);
if (-1 == ret) {
    printf("sigaddset error!\n");
    perror("reason:");    // # include <errno.h>
}

5). sigismember

#include <signal.h> 

int sigismember(const sigset_t *set, int signum);

描述:判断指定的信号是否在指定的信号集中;

参数:

        set

                &act.sa_mask

        signum

                信号;

返回值:

        成功:如果是,返回1;如果不是,返回0;

        失败:返回-1,并设置erron错误标志;

例:

struct sigaction act;
int ret = sigismember(&act.sa_mask, SIGUSR1);
if (-1 == ret) {
    printf("sigismember error!\n");
    perror("reason:");    // # include <errno.h>
}

7. 进程的“信号屏蔽字”

进程的“信号屏蔽字”是一个信号集;

向目标进程发送某信号时,如果这个信号在目标进程的信号屏蔽字中,则目标进程将不会捕获到该信号,即不会执行该信号的处理函数。

当该进程的信号屏蔽字不在包含该信号时,则会捕获这个早已收到的信号(执行对应的函数)

1). 修改进程的“信号屏蔽字”

使用 sigprocmask

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

描述:修改和设置信号屏蔽字

参数:

        how

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

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

                SIG_SETMASK       把参数set中的信号设置为信号屏蔽字,之前设置的作废;

        set

                信号屏蔽字集,&set

        oldset

                返回之前设置的信号屏蔽字(备份之前的),&oldset

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

例:

SIGINT信号注册后,再定义信号屏蔽字,设置SIGINT信号屏蔽字,休眠5秒,这5秒内,狂按Ctrl+c都是没反应的,因为这个信号被设置了信号屏蔽字;5秒后,删除信号屏蔽字,则会捕获这个早已收到的信号(执行对应的函数),但只会执行一次(猜测是信号小于32的原因)

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

void myhandle(int sig) 
{
	printf("Catch a signal : %d\n", sig);
	printf("Catch end.%d\n", sig);
}

int main(void) 
{
	struct sigaction act;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, 0);
    

    
	sigset_t proc_sig_msk, old_mask;
	sigemptyset(&proc_sig_msk);
	sigaddset(&proc_sig_msk, SIGINT);
    // 设置信号屏蔽字
	sigprocmask(SIG_BLOCK, &proc_sig_msk, &old_mask);
    
	sleep(5);
    
	printf("had delete SIGINT from process sig mask\n");
    // 删除信号屏蔽字
	sigprocmask(SIG_UNBLOCK, &proc_sig_msk, &old_mask);
		
	while (1) {
		sleep(1);
	}

	return 0;
}


可以看到,程序运行后,按ctrl+c已经没有反应了,5秒后,之前积累的信号只响应了一次;再按ctrl+c,就正常响应了;测试符合预期!

2). 获取未处理的信号

当进程的信号屏蔽字中信号发生时,这些信号不会被该进程响应,可通过sigpending函数获取这些已经发生了但是没有被处理的信号;

#include <signal.h>

int sigpending(sigset_t *set);

描述:获取被屏蔽未处理的信号

参数:

        set

                获取未被处理的信号返回,&set

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

例:

sigset_t proc_sig_msk;
int ret = sigpending(&proc_sig_msk);
if (0 != ret) {
    printf("sigpending error!\n");
    perror("reason:");  // #include <errno.h>
}

8. 阻塞式等待信号

1). pause

#include <unistd.h>

int pause(void);

描述:阻塞进程,直到发生任一信号为止;

返回值:

        当接收到任一信号后,返回-1,且errno被设置为EINTR;

例:

pause();

2). sigsuspend

#include <signal.h>

int sigsuspend(const sigset_t *mask);

描述:用指定的参数设置信号屏蔽字,然后阻塞时等待信号的发生。即,只等待信号屏蔽字之外的信号;

参数:

        mask

                信号屏蔽字集,&mask

返回值:

        总是返回-1;

例:

设置信号屏蔽字,屏蔽SIGINT信号,然后注册SIGUSR1信号,调用sigsuspend函数阻塞,最后在另一个终端kill -SIGUSR1 

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

void myhandle(int sig) 
{
	printf("Catch a signal : %d\n", sig);
	printf("Catch end.%d\n", sig);
}

void myhandle2(int sig) 
{
	printf("信号 : %d\n", sig);
}

int main(void) 
{
	struct sigaction act, act2;

	act.sa_handler = myhandle;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, 0);
    
    // 防止触发SIGUSR1时程序中断结束,而不是自然结束
    act2.sa_handler = myhandle2;
	sigemptyset(&act2.sa_mask);
	act2.sa_flags = 0;
	sigaction(SIGUSR1, &act2, 0);
    

    
	sigset_t proc_sig_msk, old_mask;
	sigemptyset(&proc_sig_msk);
	sigaddset(&proc_sig_msk, SIGINT);
    // 设置信号屏蔽字
	sigprocmask(SIG_BLOCK, &proc_sig_msk, &old_mask);
    
	
    // 阻塞,等待信号屏蔽集以外的信号触发,才能唤醒
    sigsuspend(&proc_sig_msk);
    

	return 0;
}

当接收到SIGINT以外的信号SIGUSR1时,唤醒进程,然后响应信号操作函数,最后结束进程;测试符合预期效果! 


二、管道

1. 管道简介 

管道,就是进程与进程互相发送和接收消息的媒介!

管道是“半双工”的,即是单向的。

管道是FIFO(先进先出)的。

单进程中的管道:

int fd[2]

使用文件描述符fd[1], 向管道写数据

使用文件描述符fd[0], 从管道读数据

注:单进程中的管道无实际用处;管道用于多进程间通信。

2. 管道的创建

1). pipe

#include <unistd.h>

int pipe(int pipefd[2]);

描述:pipe()创建一个管道,这是一个单向数据通道,可用于进程间通信。数组pipefd用于返回两个文件指管道两端的描述符。pipefd[0]指的是管道的读端;pipefd[1]指的是管道的写端。

参数:

        pipefd

                int型数组,文件描述符,用于操作读写,即数据发送和接收;0数据接收,1数据发送;

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

注意:获取两个“文件描述符”;分别对应管道的读端和写端。

        fd[0]: 是管道的读端

        fd[1]: 是管道的写端

        如果对fd[0]进行写操作,对fd[1]进行读操作,可能导致不可预期的错误。

例:

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

int main(int argc, char **argv) {
    int fd[2];      // 定义管道fd数组
    int ret = 0;
    char buff1[1024];
    char buff2[1024];
    
    ret = pipe(fd); // 创建一个管道,一个单向数据通道,可用于进程间通信
    if (0 != ret) {
        printf("create pipe failed!\n");
        exit(1);
    }
    
    strcpy(buff1, "Hello World!");
    write(fd[1], buff1, strlen(buff1)); // 发送信息
    printf("send information:%s\n", buff1);
    
    bzero(buff2, sizeof(buff2));        // 清零
    // 如果没有消息,会阻塞
    read(fd[0], buff2, sizeof(buff2));  // 读取消息
    printf("recived information:%s\n", buff2);
    
    return 0;
}

2). 例一 :多进程使用管道通信

创建子进程,父进程给子进程发送消息,子进程接收后,给父进程发送消息,父进程接收消息!

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

int main(int argc, char **argv) {
    int fd1[2];      // 父进程:fd1[1]   子进程:fd1[0]
    int fd2[2];      // 父进程:fd2[0]   子进程:fd2[1]
    int ret = 0;
    char buff1[1024];
    char buff2[1024];
    pid_t pd;       // 进程id
    
    ret = pipe(fd1);    // 创建一个管道,一个单向数据通道,可用于进程间通信
    if (0 != ret) {
        printf("create pipe1 failed!\n");
        exit(1);
    }
    ret = pipe(fd2);
    if (0 != ret) {
        printf("create pipe2 failed!\n");
        exit(2);
    }
    
    
    pd = fork();    
    if (-1 == pd) {
        printf("fork error!\n");
        exit(3);
    
    } else if (0 == pd) {
        /* 收消息 */
        bzero(buff2, sizeof(buff2));        // 清零
        read(fd1[0], buff2, sizeof(buff2));  // 读取消息
        printf("%d[Child] 进程收到消息:%s\n", getpid(), buff2);
        
        /* 发消息 */
        strcpy(buff1, "Hello parent!");
        write(fd2[1], buff1, sizeof(buff1));    // 发送消息
        printf("%d[Child] 进程发送消息:%s\n", getpid(), buff1);
    
    } else {
        /* 发消息 */
        strcpy(buff1, "Hello child!");
        write(fd1[1], buff1, sizeof(buff1));
        printf("%d[Parent] 进程发送消息:%s\n", getpid(), buff1);
        
        /* 收消息 */
        bzero(buff2, sizeof(buff2));
        read(fd2[0], buff2, sizeof(buff2));
        printf("%d[Parent] 进程收到消息:%s\n", getpid(), buff2);
    }
    
    wait();
    
    return 0;
}

3). 例二:关闭管道的读端/写端

管道关闭后的读操作:

问题:

对管道进行read时,如果管道中已经没有数据了,此时读操作将被“阻塞”。

如果此时管道的写端已经被close了,则读操作将可能被一直阻塞!

而此时的阻塞已经没有任何意义了。(因为管道的写端已经被关闭,即不会再写入数据了)

解决方案:

如果不准备再向管道写入数据,则把该管道的所有写端都关闭,

则,此时再对该管道read时,就会返回0,而不再阻塞该读操作。(管道的特性)

注意,这是管道的特性。

      如果有多个写端口,而只关闭了一个写端,那么无数据时读操作仍将被阻塞。

实际实现方式:

父子进程各有一个管道的读端和写端;

把父进程的读端(或写端)关闭;

把子进程的写端(或读端)关闭;

使这个“4端口”管道变成单向的“2端口”管道,如图:

 例:

子进程没有发送消息的需求,关闭发送端口;然后开始接收消息,收到消息后休眠一秒后再进行一次接收消息;

父进程没有接收消息的希求,关闭接收端口;然后给子进程发送一条消息后休眠三秒,然后关闭发送端口;

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



int main(int argc, char **argv) {
    int fd[2]; 
    int ret = 0;
    char buff1[1024];
    char buff2[1024];
    pid_t pd;       // 进程id
    
    ret = pipe(fd); // 创建一个管道,一个单向数据通道,可用于进程间通信
    if (0 != ret) {
        printf("create pipe1 failed!\n");
        exit(1);
    }
    
    
    pd = fork();
    if (-1 == pd) {
        printf("fork error!\n");
        exit(3);
    
    } else if (0 == pd) {
        close(fd[1]);   // 如果子进程没有发送信息需求,关闭子进程的发送消息管道
        
        /* 收消息 */
        bzero(buff2, sizeof(buff2));        // 清零
        read(fd[0], buff2, sizeof(buff2));  // 读取消息
        printf("%d 进程收到消息:%s\n", getpid(), buff2);
        
        sleep(1);
              
        /* 收消息 */
        bzero(buff2, sizeof(buff2));        // 清零
        ret = read(fd[0], buff2, sizeof(buff2));  // 此时这里会阻塞,直到收到消息,或者发送端被关闭
        if (ret > 0) {
            printf("%d[Child] 进程收到消息:%s\n", getpid(), buff2);
        } else if (0 == ret) {
            printf("[%d] 发送消息端口已经被关闭!\n", ret);
        }
        
        close(fd[0]);
    
    } else {
        close(fd[0]);    // 父进程不需要收消息,关闭收消息管道

        /* 发消息 */
        strcpy(buff1, "Hello child!");
        write(fd[1], buff1, sizeof(buff1));
        printf("%d[Parent] 进程发送消息:%s\n", getpid(), buff1);
        
        sleep(3);
        
        close(fd[1]);  // 关闭父进程的发消息管道

    }
    
    wait(NULL);
    
    return 0;
}

4). 例三:父进程循环给子进程发送消息

创建一个子进程,父进程通过管道向子进程发送数据(字符串),该字符串由用户输入。

当用户输入”exit”时, 就不再向子进程发送数据,并关闭该端的管道。

子进程从管道读取数据,并输出。

直到父进程关闭了管道的写端。

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



int main(int argc, char **argv) {
    int fd[2]; 
    int ret = 0;
    char buff1[1024];
    char buff2[1024];
    pid_t pd;       // 进程id
    
    ret = pipe(fd); // 创建一个管道,一个单向数据通道,可用于进程间通信
    if (0 != ret) {
        printf("create pipe1 failed!\n");
        exit(1);
    }
    
    
    pd = fork();
    if (-1 == pd) {
        printf("fork error!\n");
        exit(3);
    
    } else if (0 == pd) {
        close(fd[1]);   // 如果子进程没有发送信息需求,关闭子进程的发送消息管道
        
        while (1) {                    
            /* 收消息 */
            bzero(buff2, sizeof(buff2));        // 清零
            ret = read(fd[0], buff2, sizeof(buff2));  // 此时这里会阻塞,直到收到消息,或者发送端被关闭
            if (ret > 0) {
                printf("%d[Child] 进程收到消息:%s\n", getpid(), buff2);
            } else if (0 == ret) {
                printf("[%d] 发送消息端口已经被关闭!\n", ret);
                break;
            }
        }
        
    
    } else {
        
        while (1) {
            // 父进程不需要收消息,关闭收消息管道
            close(fd[0]);
            
            bzero(buff1, sizeof(buff1));    // 清零
            scanf("%s", buff1);     
            if (0 == strcmp(buff1, "exit")) {
                close(fd[1]);  // 关闭父进程的发消息管道
                break;
            } else {
                /* 发消息 */
                write(fd[1], buff1, sizeof(buff1));
                printf("%d[Parent] 进程发送消息:%s\n", getpid(), buff1); 
            }
        }      
        
    }
    
    wait(NULL);
    
    return 0;
}

3. popen / pclose

popen的作用:

用来在两个程序之间传递数据:

在程序A中使用popen调用程序B时,有两种用法:

  1. 程序A读取程序B的输出(使用fread读取)
  2. 程序A发送数据给程序B,以作为程序B的标准输入。(使用fwrite写入)

1). popen

#include <stdio.h>

FILE *popen(const char *command, const char *type);

描述:创建一个管道流;

参数:

        command

                可以是shell命令或者程序的名字;

        type

                可以是r表示读,w表示写;

返回值:

        如果内存分配失败,popen()函数不会设置errno。如果底层进程或管道失败,errno被适当设置。如果类型参数无效,当检测到此条件时,errno被设置为EINVAL;其他失败返回空;成功返回文件指针!

2). pclose

#include <stdio.h>

int pclose(FILE *stream);

描述:关闭管道流;

参数:

        stream

                管道(文件)流;

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

3). 例一:读取命令返回的数据

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

#define BUFF_SIZE   1024

int main(void) {
    FILE *file;
    char buff[BUFF_SIZE + 1] = { '\0' };
    int cnt;
    
    // 管道方式以读的方式打开这条命令
    file = popen("ls -l", "r");
    if (!file) {
        printf("popen failed!\n");
        exit(1);
    }
    
    // 读取"ls -l"显示的所有数据
    cnt = fread(buff, sizeof(char), BUFF_SIZE, file);
    if (cnt > 0) {
        buff[cnt] = '\0';
        printf("%s", buff);
    }
    
    // 关闭
    int ret = pclose(file);
    printf("ret = %d\n", ret);
    
    return 0;
}

4). 例二:把输出写到外部程序

pipe_6.cpp

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

#define BUFF_SIZE   1024

int main(void) {
    FILE *file;
    char buff[BUFF_SIZE + 1] = { '\0' };
    int cnt;
    
    // 管道方式以写的方式运行p2程序
    file = popen("./p2", "w");
    if (!file) {
        printf("popen failed!\n");
        exit(1);
    }
    
    strcpy(buff, "hello world!");
    cnt = fwrite(buff, sizeof(char), strlen(buff), file);
    if (cnt > 0) {
        printf("send \"%s\" successed!\n", buff);
    }
    
    // 关闭
    pclose(file);
    
    return 0;
}

p2.cpp

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

#define BUFF_SIZE   1024

int main(void) {
    char buff[BUFF_SIZE] = { '\0' };
    int ret = 0;
    
    ret = read(0, buff, sizeof(buff));
    if (ret > 0) {
        buff[ret] = '\0';
        printf("buff = %s\n", buff);
    }
    
    
    return 0;
}

编译命令:

gcc pipe_6.cpp
gcc p2.cpp -o p2

 popen的原理

先使用fork创建一个子进程,

然后在子进程中使用exec执行指定外部程序,并返回一个文件指针FILE*给父进程。

当使用”r”时,该FILE指向外部程序的标准输出

当使用”w”时,该FILE指向外部程序的标准输入。

5). popen的优缺点

优点:可以使用shell扩展(比如命令中可以使用通配符),使用方便。

缺点:每调用一次popen, 将要启动两个进程(shell和被指定的程序), 资源消耗大。

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE


三、消息队列

1. 什么是消息队列?

        消息队列,用于从一个进程向另一个进程发送数据。

        但仅把数据发送到一个“队列”中,而不指定由哪个进程来接受。

        消息队列,独立于发送消息的进程和接收消息的进程。

        (信号、管道、命名管道都不独立于发送和接收进程)

   

        消息队列,有最大长度限制:MSGMNB

        消息队列中的单条消息,也有最大长度限制:MSGMAX

        简单来讲,就是有一条队列,进程A把消息发送到队列中,进程B就可以在这个队列中去获取接收这条消息!

2. msgget 消息队列的获取

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

int msgget(key_t key, int msgflg);

描述:通过系统调用 创建 | 获取 消息队列;

参数:

        key

                指定创建或获取消息队列的key,唯一;

        msgflag

                IPC_CREAT 或者 IPC_CREAT | IPC_EXCL

                IPC_CREAT 消息队列不存在则创建,存在则返回;

                IPC_CREAT | IPC_EXCL 消息队列不存在则创建,存在则报错返回; 

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

        具体错误原因可以使用命令:man 2 msgget 去查看.

3. msgsnd 消息的发送

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

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

描述:发送一个消息,即把消息添加到消息队列中;

参数:

        msqid

                消息队列标识符;

        msgp

                消息指针,自己定义的结构体;

                注:消息的类型需要自己定义。但要求其第一个结构成员为 long int , 例:

struct  msgbuf {

    long mtype;    /* 消息的类型,取值大于0, 接收消息时可使用该值 */

    /* 其他数据变量 */     
    char mtext[1]; 
}

        msgsz

                消息的长度(不包含第一个成员msg_type);

        msgflg

                如果包含:  IPC_NOWAIT,则消息队列满时,不发送该消息,而立即返回-1;

                如果不包含:IPC_NOWAIT,则消息队列满时,挂起本进程,直到消息队列有空间可用;

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

        具体错误原因可以使用命令:man 2 msgsnd 去查看.

4. msgrcv 消息的接收

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

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

描述:从消息队列中接收一条消息;

参数:

        msqid

                消息队列标识符;

        msgp

                消息指针,自己定义的结构体,用于接收消息的缓存;

        msgsz

                消息的长度(不包含第一个成员msg_type);

        msgtyp

                指定接收消息的类型:

                        0:从消息队列中获取第一个消息,以实现顺序接受(先发先收);

                        >0:从消队列中获取相同类型的第一个消息;

                        <0:从消息队列中获取消息类型<=(msgtyep的绝对值)的第一个消息;

        msgflg

                如果包含:IPC_NOWAIT,则当消息队列中没有指定类型的消息时,立即返回-1;

                如果不包含:IPC_NOWAIT,则当消息队列中没有指定类型的消息时,挂起本进程,直到收到指定类型的消息;

返回值:

        成功:返回接收到的消息的长度(不包含第一个成员msg_type)

        失败:返回-1,并设置erron错误标志;

        具体错误原因可以使用命令:man 2 msgrcv 去查看.

5. msgctl 消息的控制

一般用来关闭消息队列!

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

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

描述:控制消息队列,例如关闭消息队列;

参数:

        msqid

                消息队列标识符;

        cmd

                IPC_RMID   删除消息队列;具体其他的可以使用命令:man 2 msgctl 去查看;

        buf

                结构体,存储消息队列的一些信息,使用IPC_RMID传0即可;

返回值:

        成功:返回0;

        失败:返回-1,并设置erron错误标志;

        具体错误原因可以使用命令:man 2 msgctl 去查看.

6. 示例一:两程序间通过消息队列去通信

msg1.cpp

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

#define MSG_SIZE    80
#define MSG_TYPE    long


typedef struct MY_MSG_ST {
    MSG_TYPE msg_type;      // 必须定义的一个变量,用于辨认类型
    char msg[MSG_SIZE];     // 数据
}my_msg_st;

int main(int argc, char **argv) {
    int msgId;  // 消息队列id
    int ret;
    my_msg_st msg;
    
    // IPC_CREAT 消息队列不存在则创建,存在则返回;
    // IPC_CREAT | IPC_EXCL 消息队列不存在则创建,存在则报错返回; 
    msgId = msgget((key_t)12356, 0666 | IPC_CREAT);
    if (-1 == msgId) {
        printf("msgget failed!\n");
        exit(1);
    }
    
    msg.msg_type = 1;   // 设置要发送的消息的类型
    strcpy(msg.msg, "Hello World!");
    // 发送一条消息
    ret = msgsnd(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0);
    if (-1 == ret) {
        printf("msgsnd failed!\n");
        exit(2);
    }
    
    return 0;
}

msg2.cpp

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

#define MSG_SIZE    80
#define MSG_TYPE    long


typedef struct MY_MSG_ST {
    MSG_TYPE msg_type;      // 必须定义的一个变量,用于辨认类型
    char msg[MSG_SIZE];     // 数据
}my_msg_st;

int main(int argc, char **argv) {
    int msgId;  // 消息队列id
    int ret;
    my_msg_st msg;
    
    // IPC_CREAT 消息队列不存在则创建,存在则返回;
    // IPC_CREAT | IPC_EXCL 消息队列不存在则创建,存在则报错返回; 
    msgId = msgget((key_t)12356, 0666 | IPC_CREAT);
    if (-1 == msgId) {
        printf("msgget failed!\n");
        exit(1);
    }
    
    msg.msg_type = 0;   // 设置要接收的消息的类型
    // 接收一条消息
    ret = msgrcv(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0, 0);
    if (-1 == ret) {
        printf("msgrcv failed!\n");
        exit(2);
    }
    
    printf("recived:%s\n", msg.msg);
    
    // 删除消息队列
    ret = msgctl(msgId, IPC_RMID, 0);
    if (-1 == ret) {
        printf("msgctl(IPC_RMID) failed!\n");
        exit(3);
    }
    
    return 0;
}

7. 多进程间消息队列通信

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

#define MSG_SIZE    80
#define MSG_TYPE    long


typedef struct MY_MSG_ST {
    MSG_TYPE msg_type;      // 必须定义的一个变量,用于辨认类型
    char msg[MSG_SIZE];     // 数据
}my_msg_st;

int main(int argc, char **argv) {
    int msgId;  // 消息队列id
    int ret;
    my_msg_st snd_msg;  // 发送数据
    my_msg_st rcv_msg;  // 接收数据
    pid_t pd;
    
    // IPC_CREAT 消息队列不存在则创建,存在则返回;
    // IPC_CREAT | IPC_EXCL 消息队列不存在则创建,存在则报错返回; 
    msgId = msgget((key_t)987, 0666 | IPC_CREAT | IPC_EXCL);
    if (-1 == msgId) {
        printf("msgget failed!\n");
        exit(1);
    }
    
    // 创建子进程
    pd = fork();
    if (-1 == pd) {
        printf("fork error!\n");
        exit(2);
    
    } else if (0 == pd) {
        rcv_msg.msg_type = 0;   // 设置要接收的消息的类型
        // 接收一条消息
        ret = msgrcv(msgId, &rcv_msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0, 0);
        if (-1 == ret) {
            printf("msgrcv failed!\n");
            exit(2);
        }
        
        printf("recived:%s\n", rcv_msg.msg);
    
    } else {
        snd_msg.msg_type = 999;   // 设置要发送的消息的类型
        strcpy(snd_msg.msg, "This is Message!");
        // 发送一条消息
        ret = msgsnd(msgId, &snd_msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0);
        if (-1 == ret) {
            printf("msgsnd failed!\n");
            exit(3);
        }
        
        if (0 < pd) wait(NULL);
        
        // 删除消息队列
        ret = msgctl(msgId, IPC_RMID, NULL);
        if (-1 == ret) {
            printf("msgctl(IPC_RMID) failed!\n");
            exit(4);
        }
    }

    
    return 0;
}

8. 练习

程序1, 循环等待用户输入字符串,每收到一个字符串,就把它发送给进程2,直到用户输入exit;

程序2, 接受进程1发过来的信息,并打印输出;直到接受到exit。

程序一

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

#define MSG_SIZE    80
#define MSG_TYPE    long


typedef struct MY_MSG_ST {
    MSG_TYPE msg_type;      // 必须定义的一个变量,用于辨认类型
    char msg[MSG_SIZE];     // 数据
}my_msg_st;

int main(int argc, char **argv) {
    int msgId;  // 消息队列id
    int ret;
    my_msg_st msg;
    
    // IPC_CREAT 消息队列不存在则创建,存在则返回;
    // IPC_CREAT | IPC_EXCL 消息队列不存在则创建,存在则报错返回; 
    msgId = msgget((key_t)258, 0666 | IPC_CREAT);
    if (-1 == msgId) {
        printf("msgget failed!\n");
        exit(1);
    }
    
    msg.msg_type = 66;   // 设置要发送的消息的类型
    
    while (1) {
        fgets(msg.msg, sizeof(msg.msg), stdin);
        
        // 发送一条消息
        ret = msgsnd(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0);
        if (-1 == ret) {
            printf("msgsnd failed!\n");
            exit(2);
        }
        
        if (0 == strncmp(msg.msg, "exit", 4)) break;
    }
    
    // 删除消息队列
    ret = msgctl(msgId, IPC_RMID, NULL);
    if (-1 == ret) {
        printf("msgctl(IPC_RMID) failed!\n");
        exit(3);
    }
    
    return 0;
}

程序二

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

#define MSG_SIZE    80
#define MSG_TYPE    long


typedef struct MY_MSG_ST {
    MSG_TYPE msg_type;      // 必须定义的一个变量,用于辨认类型
    char msg[MSG_SIZE];     // 数据
}my_msg_st;

int main(int argc, char **argv) {
    int msgId;  // 消息队列id
    int ret;
    my_msg_st msg;
    
    // IPC_CREAT 消息队列不存在则创建,存在则返回;
    // IPC_CREAT | IPC_EXCL 消息队列不存在则创建,存在则报错返回; 
    msgId = msgget((key_t)258, 0666 | IPC_CREAT);
    if (-1 == msgId) {
        printf("msgget failed!\n");
        exit(1);
    }
    
    msg.msg_type = 66;   // 指定接收的数据类型是66的
    
    while (1) {
        // 接收一条消息
        ret = msgrcv(msgId, &msg, sizeof(my_msg_st) - sizeof(MSG_TYPE), 0, 0);
        if (-1 == ret) {
            printf("msgrcv failed!\n");
            exit(2);
        }
        
        if (0 == strncmp(msg.msg, "exit", 4)) break;
        printf("recived:%s", msg.msg);
    }
    
    
    return 0;
}


四、信号量

问题:

        程序中,有时存在一种特殊代码,最多只允许一个进程执行该部分代码。

        这部分区域,称为“临界区”;

        然而在多进程并发执行时,当一个进程进入临界区,因某种原因被挂起时,其他进程就有可能也进入该区域。

解决办法:使用信号量。

1. 什么是信号量

信号量,是一种特殊的变量。

只能对信号量执行P操作和V操作;

   

    P操作, 如果信号量的值 > 0,    则把该信号量减1;

               如果信号量的值  == 0,  则挂起该进程。

              

    V操作:  如果有进程因该信号量而被挂起,则恢复该进程运行;

               如果没有进程因该信号量而挂起,则把该信号量加1。

              

    注意:P操作、V操作都是原子操作,即其在执行时,不会被中断。

   

注意:此指的“信号量”是指System V  IPC的信号量,与线程所使用的信号量不同。该信号量,用于进程间通信。

2. semget 信号量的获取

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

描述:获取System V信号量集标识符;

参数:

        key

                键值,该键值对应一个唯一的信号量。类似于共享内存的键值;

                不同的进程可通过该键值和semget获取唯一的信号量;

                特殊键值:IPC_PRIVAT该信号量只允许创建者本身, 可用于父子进程间通信;

        nsems

                需要的信号量数目,一般取1;

        semflg

                IPC_CREAT 或者 IPC_CREAT | IPC_EXCL

                IPC_CREAT 消息队列不存在则创建,存在则返回;

                IPC_CREAT | IPC_EXCL 消息队列不存在则创建,存在则报错返回;

返回值:

        成功,返回也给非负整数;

        失败,返回-1,并设置错误标志errno;

3. semop 信号量的操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

描述:改变信号量的值,即对信号量执行 P操作 或 V操作;

参数:

        semid

                信号量标识符,即semget的返回值;

        sops

                是一个数组,元素类型为struct sembuf;

struct sembuf {
    short  sem_num;  // 信号量组中的编号(即指定对哪个信号量操作)
                     // semget实际是获取一组信号量
                     // 如果只获取了一个信号量,则该成员取0
    short  sem_op;   //   -1,  表示P操作
                     //   1,  表示V操作
    short  sem_flg;  // SEM_UNDO : 如果进程在终止时,没有释放信号量;
                     // 如果不设置指定标志,应该设置为0;则,自动释放该信号量                     
}        

        nsops

                表示第二个参数sops所表示的数组的大小,即表示有几个struct sembuf;

返回值:

        成功:返回0;

        失败:返回-1,并设置错误标志errno;

4. semctl 信号量的控制

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

描述:对信号量进行控制;

参数:

        semid

                信号量标识符;

        semnum

                信号量组中的编号,如果只有一个信号量,则取值0;

        cmd

                SETVAL,把信号量初始化为指定的值,具体的值由第四个参数确定;

                注意:只能对信号量初始化一次,如果在个进程中,分别对该信号量进行初始化,则可能会导致错误!

                IPC_RMID,删除信号量;

        参数四类型为:union semun {

                                        int        val;                // SETVAL 命令要设置的值

                                        struct  semid_ds        *buf;

                                        unsigned  short          *array;

                                }

                注意:union semun 类型要求自己定义有些Linux发行版在sys/sem.h中定义,有些发行版则没有定义。

                可自己定义如下:

        #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)                                     

        #else

                        union semun {

                                int val;                            

                                struct semid_ds *buf;   

                                unsigned short int *array;

                                 struct seminfo *__buf; 

                        };

        #endif    

返回值:

        成功:大部分返回0;

        失败:返回-1,并设置错误标志;

5. 例

首先看下面的代码,父进程创建一个子进程后,一起运行for循环...

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

int main(void) {
    
    int i = 0;
    
    pid_t pd = fork();
    
    for (i = 0; i < 5; i++) {
        
        // 模拟临界区 - begin
        printf("Process(%d) In\n", getpid());
        sleep(1);
        printf("Process(%d) Out\n", getpid());
        // 模拟临界区 - end
        
        sleep(1);
    }
    
    return 0;
}

假设把for循环中sleep(1)比作是厕所的话,父进程进去了,肯定不希望子进程也进去,要等父进程出来,子进程才能进去才对;但是上面的程序是都挤进去了,所以下面使用信号量改良一下!

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

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)						   
#else
    union semun {
        int val;                             
        struct semid_ds *buf;    
        unsigned short int *array; 
        struct seminfo *__buf;  
    };
#endif


static int sem_initial(int semid) {
    int ret;
    
    union semun semun;
    semun.val = 1;      // 信号量置为1
    ret = semctl(semid, 0, SETVAL, semun);
    if (-1 == ret) {
        fprintf(stderr, "sectl failed!\n");
    }
    
    return ret;
}

static int sem_p(int semid) {
    int ret;
    
    struct sembuf sembuf;
    sembuf.sem_op = -1;     // 信号量减一
    sembuf.sem_num = 0;
    sembuf.sem_flg = SEM_UNDO;
    
    ret = semop(semid, &sembuf, 1);
    if (-1 == ret) {
        fprintf(stderr, "sem_p failed!\n");
    }
    
    return ret;
}

static int sem_v(int semid) {
    int ret;
    
    struct sembuf sembuf;
    sembuf.sem_op = 1;      // 信号量加一
    sembuf.sem_num = 0;
    sembuf.sem_flg = SEM_UNDO;
    
    ret = semop(semid, &sembuf, 1);
    if (-1 == ret) {
        fprintf(stderr, "sem_v failed!\n");
    }
    
    return ret;
}


int main(int argc, char **argv) {
    int i;
    int ret;
    int semid;
    
    /* 获取信号量 */
    semid = semget((key_t)1234, 1, 0666|IPC_CREAT);
    if (-1 == ret) {
        fprintf(stderr, "semget failed!\n");
        exit(1);
    }
    
    /* 初始化信号量 */
    if (argc > 1) {
        ret = sem_initial(semid);
        if (-1 == ret) {
            exit(2);
        }
    }
    
    for (i = 0; i < 5; i++) {
        
        if (-1 == sem_p(semid)) {
            exit(3);
        }
        
        // 进入临界区
        printf("Process(%d) In\n", getpid());
        sleep(3);
        printf("Process(%d) Out\n", getpid());
        // 退出临界区
        
        if (-1 == sem_v(semid)) {
            exit(4);
        }
        
        //sleep(1);
    }
    
    
    sleep(4);
    
    
    
    if (argc > 1) {
        /* 删除信号量 */
        union semun semun;
        ret = semctl(semid, 0, IPC_RMID, 0);
        if (-1 == ret) {
            fprintf(stderr, "semctl failed! reason: %s\n", strerror(errno));
            
            exit(5);
        }
    }

    
    return 0;
}

 父进程In后,子进程只能等待,等到父进程Out之后,子进程才能In;子进程In后,父进程也只能等待,等到子进程Out之后,父进程才能In;

这就是信号量设置的临界区的效果,阻止两个进程同时访问同一段代码!

有点像线程加锁和解锁!


五、共享内存机制

由于篇幅太大了,这部分留到下一篇博客中介绍;共享内存这效率挺高的,项目中也常用!


六、总结

信号 与 管道 与 消息队列 与信号量 的基本用法已经整理完毕,实际用法也在代码中表现出来了!

这里只是将一些入门用法写下来,具体还得自己取深入研究!

SIGPIPE 原因和解决办法和信号处理 SIGPIPE 阅读详情

相关推荐

Linux进程间通信IPC的几种方式简介

Linux进程通信的源头       linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T(原为American Telephone & Telegraph的缩写,也是中文译名美国电话电报公司由来)的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段

Sunshine的专栏 7577

信号发送:Linux中发送信号的四种方式(系统调用、定时器)

进程发送信号有四种方式,分别是键盘发送、进程异常发送、系统调用发送、软条件发送

challenglistic的博客 5442

Linux 进程间通信(IPC)方式详解:从管道到 Socket 的全面指南

本文系统介绍了Linux下七种主要进程间通信(IPC)方式:管道、命名管道、信号、消息队列、信号量、共享内存与套接字,涵盖其原理、特点、核心函数及示例代码。管道和FIFO适用于简单数据传输,信号实现异步通知,消息队列支持按类型通信,信号量保障同步与互斥,共享内存效率最高但需配合同步机制,套接字则兼具本地与网络通信能力。文章还针对常见问题如阻塞、死锁、资源泄漏等提供排查方法与解决方案,强调合理选择IPC方式并注重资源管理的重要性。

AwesoneZh的博客 531

进程间通信(IPC)---- 命名管道以及信号SIGPIPE的捕捉

命名管道的特点 命名管道也叫有名管道(FIFO),他有以下特点: 能够用于非亲缘进程之间的通信,当然亲缘进程肯定也可以; 命名管道和无名管道原理类似,都是通过在进程之间开辟一点中间介质,不同的进程通过往这个介质中读写内容,即完成了信息的收发; 顾名思义,命名管道同无名管道相比就是因为他有名,那就是文件名,文件件都可以使用open() 函数打开并返回一个文件描述符,所以,我们就不用指定...

weixin_45121946的博客 1156

Linux下处理SIGPIPE信号的方法及示例代码

在Linux系统中,SIGPIPE信号是一种特殊的信号,它在进程尝试向已关闭的管道或Socket连接写入数据时触发。然后,我们尝试向已关闭的管道写入数据,但由于我们忽略了SIGPIPE信号,进程不会终止。这种方法适用于某些场景,例如我们希望在发生SIGPIPE信号时不终止进程,而是在后续的代码中处理相关错误。在C语言中,可以使用signal()函数来注册一个信号处理函数,用于处理SIGPIPE信号。接着,我们尝试向已关闭的管道写入数据,这将导致SIGPIPE信号被触发,从而调用我们定义的信号处理函数。

2301_79331328的博客 466

微信聊天记录丢失原因与恢复方案详解

SQLite数据库作为移动应用常见的本地存储方案,其加密机制和存储结构直接影响数据安全性。以微信为例,聊天记录存储在EnMicroMsg.db等SQLite文件中,采用AES加密保护。当手机清理工具误删这些文件时,会导致特定时间段记录丢失。理解数据恢复原理需要掌握文件系统删除机制和闪存特性,其中关键点包括TRIM指令的影响和数据块覆盖风险。通过专业工具如DiskDigger或iMyFone D-Back,配合ADB调试和SQLite数据重组技术,可在黄金时间窗口内恢复记录。日常维护建议结合自动化备份方案和存

weixin_32612263的博客 386

linux进程间通信IPC、管道无名pipe、有名FIFO、消息队列msgget、共享内存shmget/at/dt/ctl、信号signal/action/queue信号量semget/ctl/op

无名管道pipe(int fd[2]),成功返回0,失败返回-1,fd01 读写用exit和exec无法完全进程间通信进程间通信IPC有:无名管道pipe、有名管道、消息队列、信号量、共享存储、socket。strams流 两个支持不同主机上两个进程ipc无名管道pipe(int fd[2]),成功返回0,失败返回-1,fd01 读写:特点 半双工 、用于亲缘间通信、内在内存中 进程结束即消失。

prppr_的博客 243

PX4无人机实战:手把手教你用MAVROS实现Offboard模式(附避坑指南)

本文提供了一份详尽的PX4无人机Offboard模式实战指南,重点解析了如何通过MAVROS实现从仿真到实机的控制。内容涵盖硬件连接、串口权限设置、MAVROS配置、核心代码编写以及实机操作全流程,并附有详细的避坑指南和故障排查手册,帮助开发者快速掌握ROS与PX4飞控的集成开发。

weixin_29172201的博客 266

传统的进程间通信方式 1.无名管道(pipe) 2.有名管道(fifo) 3.信号(signal)...

linux下进程间的通信方式:传统的进程间通信方式 1.无名管道(pipe) 2.有名管道(fifo) 3.信号(signal)System V IPC 对象 1.共享内存(share memory) 2.消息队列(message queue ) 3.信号灯 (semaphore)BSD套接字(socket)下面进行逐一介绍:无名管道:这里所说的主要只无名管道,它具有如下特点,只能用于具...

azqsnegi868990的博客 220

告别Qweb!用ReportBro在Odoo 13-19版本中5步搞定动态报表

本文介绍了如何在Odoo 13-19版本中使用ReportBro替代Qweb快速实现动态报表设计。通过5步集成指南,包括环境准备、前端集成、后端服务搭建等,展示了ReportBro在可视化设计、即时预览和多格式输出(PDF/XLSX)方面的优势,显著提升开发效率和报表灵活性。

mac99的博客 799

Anasys Workbanch实战解析:齿轮接触强度有限元分析全流程

本文详细解析了在Anasys Workbanch中进行齿轮接触强度有限元分析的全流程。从模型简化、材料赋予、接触对设置,到网格划分艺术、非线性接触求解调试,再到结果后处理与工程解读,提供了避开常见陷阱的实战经验,旨在帮助工程师精准评估齿轮强度与疲劳寿命。

weixin_29209805的博客 344

进程间通信的方式——信号、管道、消息队列、共享内存

多进程: 首先,先来讲一下fork之后,发生了什么事情。 由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id。对子进程来说,之所以fork返回0...

weixin_34006468的博客 1886

基于Sentinel-2(哨兵2)遥感卫星的植被覆盖度提取

基于Sentinel-2(哨兵2)遥感卫星的植被覆盖度提取 一、Sentinel-2简单介绍 哨兵-2系列卫星的主要载荷多光谱成像仪拥有13个谱段,从可见光到近红外再到短波红外,空间分辨率从10m到60m,能实现全所未有的陆地海洋监测水平。其幅宽高达290KM,且A、B双星组合,覆盖能力比美国Landsat8卫星强很多。目前哨兵-2A和哨兵-2B已经实现了5天的重访时间,即使在之前仅有单颗卫星时,...

weixin_44143671的博客 2万+

IPC进程间通信函数原型总结(方便查询)

进程间通信,函数原型,信号,管道,命名管道,unixsocket,信息量,共享存储,消息队列

2301_80899620的博客 1348

模型性能评估之 Friedman 检验与 Nemenyi 后续检验

前言 当我们提出一种算法,需要知道我们的算法和现在已有的算法相比,性能是否更优的时候,就需要用到模型性能评估的方法。本篇博客介绍一种模型性能评估的方法:Friedman 检验与 Nemenyi 后续检验。 该方法的特点是:可以进行多个算法的比较。下面看看具体的使用: 具体使用 1. 计算序值 假定我们用 D1D_1D1​、D2D_2D2​ 、D3D_3D3​ 和 D4D_4D4​ 四个数据集对算法 AAA、BBB、CCC 进行比较。首先需要得到每个算法在每个数据集上的测试结果,可以是准确率,也可以是均方误差

蕾欧娜与26的博客 1万+

innovus:如何在init design之后新增参考库(lib/lef)

如题,在PR过程中经常会遇到这种问题,时序无法收敛,需要新增一套lvt或者hpc的库,怎么办?

m0_61544122的博客 5001

操作系统-Linux中的IPC

Linux中的IPC管道pipe信号(Signal)Socket消息队列信号量(semaphore)共享内存(share memory) 管道pipe 管道本质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。 信号(Signal) 信号是

qq_43775976的博客 156

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Linux软件编程学习笔记(八):进程间通信详解(1)

进程间通信(Inter-Process Communication,IPC)是指在操作系统中,不同进程之间交换数据和信息的技术。由于进程空间是独立的,每个进程独享自己的0-4G虚拟内存空间,因此需要借助特定的机制来实现进程间的数据交换。本文将详细介绍Linux下常用的进程间通信方式。进程空间是独立的,无法直接访问其他进程的内存空间多个进程协同工作需要进行数据交换进程间通信是构建复杂应用程序的基础信号是内核层与应用层通信的一种机制,用于通知进程发生了某种事件。无名管道:适用于有亲缘关系的进程间通信。

weixin_53745973的博客 244

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

进程间通信 IPC 概述

Linux进程间通信,大致有五种 原始的信号和管道,还有共享内存,消息队列和信号。 1. 管道 管道分为两种 pipe和fifo pipe可用有亲缘关系的进程,比如父子进程,兄弟进程 fifo可用不同进程之间,不限于亲缘关系 pipe 半双工,一端读一端写,数据在一个方向上流动。可以看成是一种特殊的文件,只不过它只在内存里,不落地到文件系统。 管道的缓冲区有限制,PIPE_BUF 一般是一个页面的...

不才的专栏 248

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

进程间通信(IPC)

1、创建映射区的过程中,隐含着一次对映射文件的【读操作】2、当MAP_SHARED映射区的权限应该

BSY丶的博客 1163

进程间通信知识点整理

进程间通信

2201_76064374的博客 183

Linux进程间通信类型 含示例

1:Linux进程间通信类型 2: 管道(pipe)和有名管道(FIFO)。 3: 信号(signal),见signal函数、sigaction函数及信号集操作函数和信号的发送和捕捉函数(alarm、kill、raise、pause、 sleep、abort)。 4: 共享内存,见共享内存函数(shmget、shmat、shmdt、shmctl)及其范例。 5: 消息队列,见消息队列函数(msgget、msgctl、msgsnd、msgrcv)及其范例。 6: 信号量,见信号量函数(s...

sy_123a的专栏 313

Linux 进程通信:管道、信号、共享内存、消息队列详解

本文详细介绍了Linux系统中四种常用的进程间通信(IPC)机制:管道、信号、共享内存和消息队列。通过对比表格展示了它们的通信模式、数据量、实时性等特性差异。针对每种机制提供了可运行的C语言代码示例,并指出关键注意事项: 管道 - 半双工通信,适用于有亲缘关系的进程 信号 - 异步通知机制,仅传递信号编号 共享内存 - 速度最快的IPC,需配合同步机制使用 消息队列 - 以消息块形式传递,支持选择性接收 文章强调实际应用中需要考虑进程关系、数据量大小和同步需求,选择合适的IPC方式。每种方法都有其适用场景:

爱片闲串,爱钓鱼,爱中医,爱分享 149

Linux 进程间通信学习笔记

无名管道只能亲缘进程;有名管道 FIFO 任意进程都能用。写管道,但是所有读端全部关闭 → SIGPIPE 信号,进程直接死掉。写代码必须注意。信号是异步!进程不知道信号什么时候来。9、19 号信号不受程序控制。pipe 创建出来两个 fd,fork 之后子进程会复制文件描述符,父子各持有读写两端,记得不用的一端 close 关闭。pause 会一直睡,只有信号到来才会解除阻塞。

weixin_48445537的博客 626

进程间通信

进程间通信 多进程: 首先,先来讲一下fork之后,发生了什么事情。 由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id。对子进程来说,之所以for...

weixin_42306440的博客 157
上一篇: Linux 进程通讯 - 共享内存机制
下一篇: Linux pthread线程操作 和 线程同步与互斥操作
cpp_learners
博客等级 码龄7年 1526粉丝 274原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

cpp_learners

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

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

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

打赏作者

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

抵扣说明:

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

余额充值