进程间的通信

本文详细介绍了Linux下的进程间通信方式,包括信号、管道、命名管道、消息队列和共享内存。信号如SIGKILL和SIGSTOP无法被程序捕获,而管道是半双工的,适用于父子或兄弟进程。命名管道FIFO则允许任意两个进程通信。消息队列是内核中存储的消息列表,允许自定义读写。共享内存提供了快速的通信机制,无需数据复制。
摘要由CSDN通过智能技术生成

信号

信号是软件中断的模拟,可以在任何时候发给进程,如果进程处于未执行状态,该信号就由内核保存,直到进程恢复执行再传递给它。
SIGKILL和SEGSTOP是应用程序无法捕捉和忽略的。
几个常用的快捷键和信号:
ctrl + C —— SIGINT 中断信号
ctrl + \ —— SIGQUIT 退出信号
ctrl + Z —— SIGTSTP 进程挂起
functions about signals:

functionintroduction
int kill(pid_t pid, int sig)send signal to a process
int raise(int sig)send a signal to the caller
unsigned int alarm(unsigned int seconds)set an alarm clock for delivery of a signal
int pause(void)wait for signal
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);ANSI C signal handling
int sigemptyset(sigset_t *set)initializes the signal set given by set to empty, with all signals excluded from the set.
int sigfillset(sigset_t *set)initializes set to full, including all signals
int sigaddset(sigset_t *set, int signum)add respectively signal signum from set
int sigdelset(sigset_t *set, int signum)delete respectively signal signum from set
int sigismember(const sigset_t *set, int signum)tests whether signum is a member of set
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)examine and change blocked signals

下面是子进程给自己发送SIGSTOP信号,用于暂停进程。父进程中waitpid(pid,0,WNOHANG) 用于子进程没有退出立即返回(return immediately if no child has exited.)。

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

int main(){
    pid_t pid = fork();
    if(pid == 0){ // child
         printf("child id is %d\n",getpid());
         raise(SIGSTOP);
    }
    else if(pid > 0){ // father
         printf("father pid is %d\n",getpid());
         if(waitpid(pid,0,WNOHANG) == 0){ 
              int ret = kill(pid,SIGKILL);
              if(ret < 0) perror("ending child failed ");
              else printf("father process end child process.\n");
         }   
    }
    else {
         perror("fork ");
         exit(1);
    }
    return 0; 
}
/*
father pid is 6284
father process end child process.
*/

alarm用于设置多少秒后发出SIGALAR信号,时间片函数控制事件发生的时间点。

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

void handler(int sig){
    printf("hello world\n");
}
int main(){
    int i;
    alarm(3);
    signal(SIGALRM,handler);
    for(i=1;i<=6;i++){
        printf("times is %d\n",i);
        sleep(1);
    }   
    return 0; 
}
/*
times is 1
times is 2
times is 3
hello world
times is 4
times is 5
times is 6
*/

信号的阻塞:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h> 
void handler(){
    printf("signal SIGINT has been blocked for 5 seconds, now it works.\n");
}
int main(){
    signal(SIGINT,handler);
    int i;
    sigset_t set;
    if(sigemptyset(&set) == -1){
        perror("sigemptyset ");
    }
    if(sigaddset(&set,SIGINT) == -1){
        perror("sigaddset ");
    }
    if(sigprocmask(SIG_BLOCK,&set,NULL) == -1){ /* make set blocked */
        perror("sigpromask ");
    }
    raise(SIGINT);
    for(i=0;i<5;i++){
        printf("sleeps %d seconds...\n",i+1);
        sleep(1);
    }
    if(sigprocmask(SIG_UNBLOCK,&set,NULL) < 0){
        perror("sigprocmask ");
    }
    return 0;
}
/*
sleeps 1 seconds...
sleeps 2 seconds...
sleeps 3 seconds...
sleeps 4 seconds...
sleeps 5 seconds...
signal SIGINT has been blocked for 5 seconds, now it works.
*/

管道

进程通信方式中的管道是半双工的,仅用于父子进程或者兄弟进程,单独构成独立的文件系统,存在于内存之中。
相关的函数:

functionintroduction
int pipe(int pipefd[2])create pipe
int pipe2(int pipefd[2], int flags)create pipe
FILE *popen(const char *command, const char *type)opens a process by creating a pipe, forking, and invoking the shell
int pclose(FILE *stream)waits for the associated process to terminate and returns the exit status of the command as returned by wait4(2)
int mkfifo(const char *pathname, mode_t mode)make a FIFO special file (a named pipe)

例子:fork()创建进程,父进程给子进程通过管道发送消息“I’m father.”, 子进程读取;子进程给父进程发送消息“I’m child.”, 父进程再读取。

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

int main(){
    int pipe_fd[2];
    if(pipe(pipe_fd) < 0){
        perror("pipe ");
        exit(1);
    }
    int bytes;
    char str[105] = {0};
    int pid = fork();
    if(pid < 0) perror("fork ");
    else if(pid == 0){ // child
        bytes = read(pipe_fd[0],str,104);
        if(bytes > 0){
             printf("child read from pipe: %s\n",str);
        }
        else perror("child read ");
        //fdopen(pipe_fd[1],"w");
        bytes = write(pipe_fd[1],"I'm child.",104);
        if(bytes <= 0){
             perror("child write ");
             printf("child write %d\n",errno);
             exit(1);
        }
    }
    else {
        bytes = write(pipe_fd[1],"I'm father.",104);
        if(bytes <= 0){
             perror("father write ");
             exit(1);
        }
        waitpid(pid,NULL,0);  // wait for child process.
        bytes = read(pipe_fd[0],str,104);
        if(bytes > 0){
             printf("father read from pipe: %s\n",str);
        }
        else perror("father read ");
    }
    return 0;
}
/*
works:
child read from pipe: I'm father.
father read from pipe: I'm child.

if we put "waitpid(pid,NULL,0);" after "else perror("father read ");",
we would find read is blocked:
father read from pipe: I'm father.
^C

we can also get that process has individual pipe_fd[] for himself.
*/

使用管道实现ls |grep .c程序:

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

int main(){
    FILE *fp = popen("ls","r"); /* read pipe */
    if(fp == NULL){
         perror("popen ");
         exit(1);
    }
    char buf[1005]={0};
    if(fread(buf,1,1004,fp) <= 0){ /* store contents in buf */
         perror("fread ");
         exit(1);
    }
    pclose(fp);
    fp = popen("grep -a .c","w"); /* write pipe */
    if(fwrite(buf,1,1004,fp) <= 0){ /* write contents from buf to file stream fp */
         perror("fwrite ");
         exit(1);    
    }
    while(fgets(buf,1004,fp) != NULL){
        printf("%s\n",buf);
    }
    pclose(fp);
    return 0;
}
/*
it's equal to shell command `ls |grep .c`
*/

popen()中的shell命令是在fork出来的子进程中执行的。

命名管道

命名管道FIFO是一种特殊的pipe,可以使用在任何两个进程中通信。
相关函数:

int mkfifo(const char *pathname, mode_t mode);
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
int close(int fd);

下面实现两个进程的命名管道通信。
(open操作管道,以读或者写的方式打开,如果没有另一个进程进行写或读,是会一直阻塞的,fifo存在自动阻塞的特性。可以使用读写的方式打开避免这样的尴尬。)
processA.c:

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

int main(){
    mkfifo("fifo1",0644);
    mkfifo("fifo2",0644);
    int wfd = open("fifo1",O_RDWR);
    int rfd = open("fifo2",O_RDWR);
    struct timeval timer = {0,0};
    fd_set wfd_set, rfd_set;

    if(wfd == -1 || rfd == -1){
        perror("open ");
        exit(1);
    }
    printf("process A:\n");
    while(1){
        FD_ZERO(&rfd_set);
        FD_SET(rfd,&rfd_set);
        FD_SET(fileno(stdin),&rfd_set);
        if(select(rfd+1,&rfd_set,NULL,NULL,&timer) <= 0) continue;
        if(FD_ISSET(rfd,&rfd_set)){
            char str[105] = {0};
            read(rfd,str,104);
            printf("message from process B: %s\n",str);
        }
        if(FD_ISSET(fileno(stdin),&rfd_set)){
            char str[105] = {0};
            fgets(str,104,stdin);
            if(write(wfd,str,strlen(str)) <= 0){
                perror("write ");
                exit(1);
            }       
        }
    }
    close(rfd);
    close(wfd);
    return 0;
}
/*
process A:
hello, I'm process A
message from process B: I get it

I'm glad to heard from you.
message from process B: see you later.


*/

processB.c

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

int main(){
    mkfifo("fifo1",0644);
    mkfifo("fifo2",0644);
    int rfd = open("fifo1",O_RDWR);
    int wfd = open("fifo2",O_RDWR);
    struct timeval timer = {0,0};
    fd_set wfd_set, rfd_set;

    if(wfd == -1 || rfd == -1){
        perror("open ");
        exit(1);
    }
    printf("process B:\n");
    while(1){
        FD_ZERO(&rfd_set);
        FD_SET(rfd,&rfd_set);
        FD_SET(fileno(stdin),&rfd_set);
        if(select(rfd+1,&rfd_set,NULL,NULL,&timer) <= 0) continue;
        if(FD_ISSET(rfd,&rfd_set)){
            char str[105] = {0};
            read(rfd,str,104);
            printf("message from process A: %s\n",str);
        }
        if(FD_ISSET(fileno(stdin),&rfd_set)){
            char str[105] = {0};
            fgets(str,104,stdin);
            if(write(wfd,str,strlen(str)) <= 0){
                perror("write ");
                exit(1);
            }       
        }
    }
    close(rfd);
    close(wfd);
    return 0;
}
/*
process B:
message from process A: hello, I'm process A

I get it
message from process A: I'm glad to heard from you.

see you later.

*/

我们可以在当前目录下发现多了两个fifo文件:

$ ls -l fifo*
prw-r--r-- 1 edemon edemon 0  1月  7 16:26 fifo1
prw-r--r-- 1 edemon edemon 0  1月  7 16:26 fifo2

消息队列

消息队列是保存在内核中的消息列表。用户进程可以向消息队列中添加消息,也可以从消息队列中(自定义)读取消息。通信的进程没有关系上的 限制。
消息队列的相关函数
IPC: inter-process communication

functionsintroduction
key_t ftok(const char *pathname, int proj_id)convert a pathname and a project identifier to a System V IPC key
int msgget(key_t key, int msgflg)get a System V message queue identifier
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)send messages to a System V message queue
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)receive messages from a System V message queue
int msgctl(int msqid, int cmd, struct msqid_ds *buf)System V message control operations

例子:使用消息队列,父进程给子进程发送消息。

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

int main(){
    key_t key = ftok(".",0);
    int msg_id = msgget(key,IPC_CREAT|0644);
    if(msg_id == -1){
        perror("msgget ");
        exit(1);
    }   
    printf("please enter message to transform: \n");
    char message[105] = {0};  
    fgets(message,104,stdin);
    int pid = fork();
    if(pid == -1){
        perror("fork ");
        exit(1);
    }
    else if(pid == 0){
        if(msgsnd(msg_id,message,strlen(message),0) == -1) {
            perror("msgsnd ");
            exit(1);
        }
    }
    else {
        waitpid(pid,NULL,0);
        memset(message,0,sizeof(message));
        if(msgrcv(msg_id,message,104,0,0) <= 0){
            perror("msgrcv ");
            exit(1);
        }
        printf("father process receive message from child process:\n%s",message);
        if(msgctl(msg_id,IPC_RMID,NULL) == -1){
            perror("msgctl ");
            exit(1);
        }
    }
    return 0;
}
/*
please enter message to transform: 
hello world
father process receive message from child process:
hello world
*/

共享内存

共享内存用于多个进程之间的通信,因为不需要数据的来回复制,所以是最快的进程之间通信的机制。

functionintroduction
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)map files or devices into memory
int munmap(void *addr, size_t length)unmap files or devices into memory
int shmget(key_t key, size_t size, int shmflg)allocates a shared memory segment
void *shmat(int shmid, const void *shmaddr, int shmflg)shared memory operations
int shmdt(const void *shmaddr)detaches the shared memory segment

系统V共享内存的方式(The POSIX shared memory object),通过映射特殊文件系统shm中的文件实现共享内存的通信。
例子:利用系统V共享内存的通信方法,子进程给父进程发送信息。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */

int main(){
    int fd = shm_open("test",O_CREAT|O_RDWR,0644);
    if(fd == -1){
        perror("shm_open ");
        exit(1);
    }
    ftruncate(fd,105);
    char *str = (char *)mmap(NULL,105,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if((void*)-1 == str){
        perror("mmap ");
        exit(1);
    }
    int pid = fork();
    if(pid == -1)   perror("fork ");
    else if(pid == 0){
        printf("child process %d set message str.\n",getpid());
        strcpy(str,"hello world, I'm child.");
        exit(0);
    }
    else {
        waitpid(pid,0,0);
        printf("father process get share memory message: %s\n",str);
        if(munmap(str,105)<0) perror("munmap ");
        str = NULL;
    }
    return 0;
}
/*
shm_open, shm_unlink:  Link with -lrt.

[edemon@CentOS workspace]$ gcc shm_com.c -lrt
[edemon@CentOS workspace]$ ./a.out 
child process 8049 set message str.
father process get share memory message: hello world, I'm child.

we can find a new file test in /dev/shm/
[edemon@CentOS workspace]$ ls /dev/shm
pulse-shm-1359020668  pulse-shm-2129357730  pulse-shm-3531010208  test
pulse-shm-1643945986  pulse-shm-3326717209  pulse-shm-3944084079
*/

如果使用普通文件映射,那么改变open的方式即可。int fd = open("test",O_CREAT|O_RDWR|O_TRUNC,0644);
如果使用匿名映射(没有文件和内存对应),那么不使用shm_open和open函数产生文件描述符,使用mmap(): char *str = (char *)mmap(NULL,105,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
在路径/proc/sys/kernel中存在共享内存最大字节数的限制文件shmnmax, 最大标识数的限制文件shmmni

[edemon@CentOS kernel]$ cat shmmax
4294967295
[edemon@CentOS kernel]$ cat shmmni
4096

文件系统shm安装点在交换分区上,系统V共享内存随着内核持续,系统重新引导(内核重新引导)后,所有的内存都将丢失。

[edemon@CentOS workspace]$ ls /dev/shm 
pulse-shm-1812859712  pulse-shm-2430381189  pulse-shm-264722293  pulse-shm-4074297125  pulse-shm-94801651
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值