嵌入式成长轨迹20 【Linux应用编程强化】【Linux下的C编程 下】【进程间通信】
一想到硬盘里还有10篇日志有时候就懒得发、、、
进程间通信有如下一些目的:
数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
一 管道
管道是用来进行进程间通信的一块内核缓冲区,它按照先进先出的方式进行数据传输。管道的两端都是进程,进程从一端往管道里写入数据,其它进程就可以从另一端将数据读出,进而实现了进程间通信的功能。
1 匿名管道
匿名管道只能用于有亲缘关系的进程,如父进程和子进程,以及兄弟进程之间的通信。
1).匿名管道的创建
int pipe(int fd[2]);
2).匿名管道的读写
3)匿名管道的阻塞
1 /* example1.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <fcntl.h> 6 #include <sys/wait.h> 7 #define BUFSIZE 256 8 int main() 9 { 10 pid_t pid; 11 int fd[2]; /* 定义管道描述符 */ 12 int status; 13 char buf[BUFSIZE] = "Hello World!\n"; 14 if(pipe(fd) < 0) /* 创建匿名管道 */ 15 { 16 printf("pipe error.\n"); 17 exit(1); 18 } 19 pid = fork(); /* 创建子进程 */ 20 if(pid < 0) /* 如果子进程创建失败,输出错误信息并退出 */ 21 { 22 printf("fork error.\n"); 23 exit(1); 24 } 25 if(pid == 0) /* 子进程 */ 26 { 27 close(fd[0]); /* 关闭管道的读端 */ 28 write(fd[1], buf, sizeof(buf)); /* 向管道中写入数据 */ 29 } 30 else /* 父进程 */ 31 { 32 close(fd[1]); /* 关闭管道的写端 */ 33 read(fd[0], buf, sizeof(buf)); /* 从管道中读取数据 */ 34 printf("Received message from child process:\n%s", buf); 35 if(pid != wait(&status)) /* 等待子进程结束 */ 36 { 37 printf("wait error.\n"); 38 exit(1); 39 } 40 } 41 return 0; 42 }
1 /* example2.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <fcntl.h> 6 #include <string.h> 7 #include <sys/wait.h> 8 #define BUFSIZE 256 9 int main(int argc, char **argv) 10 { 11 pid_t pid; 12 FILE *fp; 13 int fd[2]; /* 定义管道描述符 */ 14 int status; 15 char buf[BUFSIZE]; /* 定义缓冲区 */ 16 if(pipe(fd) < 0) /* 创建匿名管道 */ 17 { 18 printf("pipe error.\n"); 19 exit(1); 20 } 21 pid = fork(); /* 创建子进程 */ 22 if(pid < 0) /* 如果子进程创建失败,输出错误信息并退出 */ 23 { 24 printf("fork error.\n"); 25 exit(1); 26 } 27 if(pid == 0) /* 子进程 */ 28 { 29 fp = fopen(argv[1], "r"); /* 打开源文件 */ 30 if(fp == NULL) /* 如果源文件打开失败,输出错误信息并退出 */ 31 { 32 perror("open source file failed"); 33 exit(1); 34 } 35 while (fgets(buf, sizeof(buf), fp) != NULL) /* 逐行读取源文件内容 */ 36 { 37 close(fd[0]); /* 关闭管道的读端 */ 38 write(fd[1], buf, sizeof(buf)); /* 将源文件内容写入到管道中 */ 39 } 40 fclose(fp); /* 关闭源文件 */ 41 strcpy(buf, "`"); /* 设置特殊字符,以表示源文件内容传送完毕 */ 42 close(fd[0]); 43 write(fd[1], buf, sizeof(buf)); 44 } 45 else /* 父进程 */ 46 { 47 fp = fopen(argv[2], "w"); /* 打开目标文件 */ 48 if(fp == NULL) /* 如果目标文件打开失败,输出错误信息并退出 */ 49 { 50 perror("open destination file failed"); 51 exit(1); 52 } 53 close(fd[1]); /* 关闭管道的写端 */ 54 read(fd[0], buf, sizeof(buf)); /* 从管道中读取数据 */ 55 while('`' != buf[0]) /* 测试是否读到特殊字符 */ 56 { 57 fputs(buf, fp); /* 逐行写入到目标文件之中 */ 58 close(fd[1]); 59 read(fd[0], buf, sizeof(buf)); 60 } 61 fclose(fp); /* 关闭目标文件 */ 62 if(pid != wait(&status)) /* 等待子进程结束 */ 63 { 64 printf("wait error.\n"); 65 exit(1); 66 } 67 printf("Done!\n"); 68 } 69 return 0; 70 }
2 命名管道
与匿名管道不同,命名管道在文件系统中是可见的,创建时需要指定具体的路径和文件名,创建后可以使用ls命令来查看。
1).命名管道的创建
int mkfifo(const char *pathname, mode_t mode);
1 /* example3.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 int main(int argc, char **argv) 8 { 9 mode_t mode=0750; /* 定义文件的访问权限 */ 10 int status; 11 if(argc != 2) /* 检查命令行参数个数是否正确 */ 12 { 13 printf("arguments error.\n"); 14 exit(1); 15 } 16 status = mkfifo(argv[1], mode); /* 创建命名管道 */ 17 if(status < 0){ /* 如果管道创建失败,输出错误信息并退出 */ 18 perror("mkfifo error"); 19 exit(1); 20 } 21 else 22 { 23 printf("FIFO create success.\n"); /* 如果管道创建成功,输出相关信息 */ 24 } 25 return 0; 26 }
2).命名管道的删除
int unlink(const char *pathname);
3).命名管道的打开
4).命名管道的读写
1 /* server.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <fcntl.h> 7 #include <sys/types.h> 8 #include <sys/stat.h> 9 #define BUFSIZE 256 10 int main(int argc, char **argv) 11 { 12 int status; 13 int fd; 14 char buf[BUFSIZE]; /* 定义缓冲区 */ 15 if(argc != 2) /* 检查命令行参数个数是否正确 */ 16 { 17 printf("arguments error.\n"); 18 exit(1); 19 } 20 status = mkfifo(argv[1], 0750); /* 创建命名管道 */ 21 if(status < 0){ /* 如果管道创建失败,输出错误信息并退出 */ 22 perror("mkfifo error"); 23 exit(1); 24 } 25 fd = open(argv[1], O_WRONLY); /* 打开命名管道,默认为阻塞方式 */ 26 if(fd < 0) /* 如果管道打开失败,输出错误信息并退出 */ 27 { 28 perror("open error"); 29 exit(1); 30 } 31 printf("Server:\n"); 32 printf("Input the massage : "); 33 fgets(buf, sizeof(buf), stdin); /* 从键盘输入要发送的消息 */ 34 write(fd, buf, sizeof(buf)); /* 将消息写入命名管道之中 */ 35 printf("Send!\n"); 36 unlink(argv[1]); /* 删除命名管道 */ 37 return 0; 38 }
1 /* client.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <fcntl.h> 7 #define BUFSIZE 256 8 int main(int argc, char **argv) 9 { 10 int fd; 11 char buf[BUFSIZE]; /* 定义缓冲区 */ 12 if(argc != 2) /* 检查命令行参数个数是否正确 */ 13 { 14 printf("arguments error.\n"); 15 exit(1); 16 } 17 fd = open(argv[1], O_RDONLY); /* 打开命名管道,默认为阻塞方式 */ 18 if(fd < 0) /* 如果管道打开失败,输出错误信息并退出 */ 19 { 20 perror("open error"); 21 exit(1); 22 } 23 printf("Client:\n"); 24 read(fd, buf, sizeof(buf)); /* 从命名管道中读取消息 */ 25 printf("Received message : %s", buf); /* 输出接收到的消息 */ 26 return 0; 27 }
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <fcntl.h> 6 #define BUFSIZE 256 7 int main(int argc, char **argv) 8 { 9 int fd; 10 int num; 11 char buf[BUFSIZE]; /* 定义缓冲区 */ 12 if(argc != 2) /* 检查命令行参数个数是否正确 */ 13 { 14 printf("arguments error.\n"); 15 exit(1); 16 } 17 fd = open(argv[1], O_RDONLY | O_NONBLOCK); /* 以非阻塞方式打开命名管道 */ 18 if(fd < 0) /* 如果管道打开失败,输出错误信息并退出 */ 19 { 20 perror("open error"); 21 exit(1); 22 } 23 printf("Client:\n"); 24 while(1) 25 { 26 num = read(fd, buf, sizeof(buf)); /* 从命名管道中读取消息 */ 27 if(num == -1) /* 查看消息读取失败是否是由于当前命名管道中没有数据 */ 28 { 29 if(errno == EAGAIN) 30 { 31 printf("No data avlaible.\n"); 32 } 33 } 34 else 35 { 36 printf("Real read bytes : %d\n", num); /* 输出实际读取的字节数 */ 37 printf("Received message : %s", buf); /* 输出接收到的消息 */ 38 break; /* 接收到了消息,跳出while循环 */ 39 } 40 sleep(1); 41 } 42 return 0; 43 }
二 信号
通信是一个广义上的概念,它既包括大量数据的传送,也包括控制信息的传送。信号(Signal)用于通知一个或多个接收进程有某种事件发生,除了进程间通信外,还可以发送信号给进程本身。信号是Unix系统中使用的最古老的进程间通讯方法之一,目前经过POSIX的扩展,功能更为强大,除了基本的通知功能外,还可以传递附加信息。
1 信号的基本原理
信号一般是由系统中一些特定事件引起的,主要包括:
硬件故障;
程序运行中的错误,例如除数为零,或访问进程以外的内存区域;进程的子进程终止;用户从终端向进程发送终止、终止等信号;进程调用kill、raise、以及alarm等函数向其他进程发送信号。
进程收到信号后,对于某些特定的信号,例如SIGKILL和SIGSTOP信号,处理方式是确定的。对于大部分信号,进程可以选择不同的响应方式:捕获信号,这类似于中断处理程序,对于需要处理的信号,进程可以指定相应的函数来进行处理;
忽略信号,对信号不进行任何处理,就像未收到一样,有两个信号不能忽略,即SIGKILL和SIGSTOP信号;
让Linux内核执行与信号对应的默认动作,对于大部分信号来说,默认的处理方式是终止相应的进程。
1 /* example5.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <signal.h> /* 注意包含该头文件 */ 6 void sig_handler(int sig) /* 信号处理函数 */ 7 { 8 switch(sig) 9 { 10 case 2: /* 处理SIGINT信号 */ 11 printf("Received signal : SIGINT\n"); 12 break; 13 case 3: /* 处理SIGQUIT信号 */ 14 printf("Received signal : SIGQUIT\n"); 15 break; 16 default: 17 ; 18 } 19 return; 20 } 21 int main() 22 { 23 printf("PID : %d\n", getpid()); /* 输出进程的标识符 */ 24 signal(SIGINT, sig_handler); /* 设置SIGINT信号的处理函数 */ 25 signal(SIGQUIT, sig_handler); /* 设置SIGQUIT信号的处理函数 */ 26 for(;;); /* 无穷循环 */ 27 return 0; 28 }
2 信号的类型
Linux系统中,可以使用kill -l命令来列出系统中所有的信号。
3 信号处理函数
进程如果要处理某一个信号,就必须在信号与处理函数之间建立对应的关系。Linux系统中实现这一功能的函数有两个,下面分别介绍。
1).signal函数
void (*signal(int signum, void (*handler))(int)))(int);
一般这样来简化:
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler)); SIG_IGN、SIG_DFL
2).sigaction函数
int sigaction(int sig, const struct sigaction *act,struct sigaction *oact);
登记信号处理机主要用于决定进程如何处理信号。首先要判断出当前进程阻塞能不能传递给该信号的信号集。这首先使用sigprocmask函数(后面再介绍)判断检测或更改信号屏蔽字,然后使用sigaction函数改变进程接受到特定信号之后的行为
struct sigaction
{
union
{
sighandler_t sa_handler;
void (*sa_sigaction)(int, struct siginfo *, void *);
} _u;
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
};
或者
struct sigaction {
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore);
} ;
sa_handler是一个函数指针,指定信号关联函数,可以是自定义处理函数,还可以SIG_DFL或 SIG_IGN。
sa_mask是一个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被阻塞。
sa_flags中包含许多标志位,是对信号进行处理的各种选项。具体如下:
SA_NODEFER\SA_NOMASK: 当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动阻塞此信号。
SA_NOCLDSTOP: 进程忽略子进程产生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTTOU信号
SA_RESTART: 可让重启的系统调用重新起作用。
SA_ONESHOT\SA_RESETHAND: 自定义信号只执行一次,在执行完毕后恢复信号的系统默认动作。
1 /* example6.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <signal.h> 6 void sig_handler(int sig, siginfo_t *info, void *t) /* 信号处理函数 */ 7 { 8 printf("Receive signal : %d\n", sig); 9 return; 10 } 11 int main() 12 { 13 int status; 14 struct sigaction act; /* 定义sigaction结构 */ 15 act.sa_sigaction = sig_handler; /* 使用sa_sigaction来设定处理函数 */ 16 sigemptyset(&act.sa_mask); /* 清空信号集中的所有信号,后面介绍该函数 */ 17 act.sa_flags = SA_SIGINFO; /* 设置SA_SIGINFO标志位 */ 18 status = sigaction(SIGINT, &act, NULL); /* 设置SIGINT信号的处理函数 */ 19 if(status < 0) 20 { 21 printf("sigaction error.\n"); 22 } 23 for(;;); /* 无穷循环 */ 24 return 0; 25 }
4 信号发送函数
Linux系统中最常用的信号发送函数主要有:kill、raise、alarm以及setitimer等
1).kill函数
int kill(pid_t pid, int signo)
2).raise函数
int raise(int signo);3).abort函数 void abort(void);
4).sigqueue函数
int sigqueue(pid_t pid, int sig,const union sigval val)
typedef union sigval
{
int sival_int;
void *sival_ptr;
} sigval_t;
5).alarm函数
unsigned int alarm(unsigned int seconds);
1 /* example8.c */ 2 #include <stdio.h> 3 #include <signal.h> 4 int main() 5 { 6 int i; 7 alarm(1); /* 设置定时器 */ 8 i=0; 9 while(1) 10 { 11 printf("i = %d\n", i); 12 i++; 13 } 14 return 0; 15 }
6).setitimer和getitimer函数
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value,struct itimerval *ovalue);
ITIMER_REAL:按实际时间计时,计时到达后向进程发送SIGALRM信号;
ITIMER_VIRTUAL:仅在进程执行时计时,计时到达后向进程发送SIGVTALRM信号;ITIMER_PROF:在进程执行和调用系统时计时,计时到达后向进程发送SIGPROF信号。
5 信号集和信号集操作函数
我们需要有一个能表示多个信号——信号集(signal set)的数据类型。将在sigprocmask()这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。信号集函数组包含几大模块: 创建函数集、登记信号集、检测信号集
#include <signal.h>
int sigemptyset(sigset_t * set) ;
int sigfillset(sigset_t * set) ;
int sigaddset(sigset_t * set,int signo) ;
int sigdelset(sigset_t * set,int signo) ;
四个函数返回:若成功则为0,若出错则为-1
int sigismember(const sigset_t * set, int signo) ;
返回:若真则为1,若假则为0。
sigemptyset: 初始化信号集合为空。
sigfillset: 初始化信号集合为所有信号的集合。
sigaddset: 将指定信号添加到现存集中。
sigdelset: 从信号集中删除指定信号。
sigismember: 查询指定信号是否在信号集合中。
6、信号屏蔽
如果在程序运行过程中不希望收到其他信号,这时就需要进行信号屏蔽。
int sigprocmask(int how, const sigset_t *set,sigset_t *oset);
SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK
一个进程的信号屏蔽字可以规定当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改(或两者)进程的信号屏蔽字。
# include <signal.h>
int sigprocmask(int how, const sigset_t * set, sigset_t * oset) ;
返回:若成功则为0,若出错则为-1
oset是非空指针,进程的当前信号屏蔽字通过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
用sigprocmask更改当前信号屏蔽字的方法,how参数设定:
SIG_BLOCK该该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。
SIG_UNBLOCK该该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望解除阻塞的信号。
SIG_SETMASK该该进程新的信号屏蔽是set指向的值。
如果set是个空指针,则不改变该进程的信号屏蔽字, how的值也无意义。
1 /* example9.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <signal.h> 6 #define TIME_OUT 5 7 void sig_handler(int sig) /* 信号处理函数 */ 8 { 9 printf("Receive signal : SIGINT\n"); /* 输出收到的信号 */ 10 return; 11 } 12 int main() 13 { 14 sigset_t set; /* 定义信号集 */ 15 sigemptyset(&set); /* 初始化信号集,清空所有信号 */ 16 sigaddset(&set, SIGINT); /* 将SIGINT信号添加到信号集中 */ 17 signal(SIGINT, sig_handler); /* 设置SIGINT信号的处理函数 */ 18 while(1) 19 { 20 sigprocmask(SIG_BLOCK, &set, NULL); /* 阻塞信号 */ 21 printf("SIGINT is blocked.\n"); 22 sleep(TIME_OUT); 23 sigprocmask(SIG_UNBLOCK, &set, NULL); /* 解除阻塞 */ 24 printf("SIGINT is unblocked.\n"); 25 sleep(TIME_OUT); 26 } 27 return 0; 28 }
1 /* send.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <signal.h> 6 int main(int argc, char **argv) 7 { 8 int status; 9 pid_t pid; 10 union sigval sg; 11 if(argc != 2) /* 检查命令行参数个数是否正确,这里需要输入接收进程的标识符 */ 12 { 13 printf("arguments error.\n"); 14 exit(1); 15 } 16 pid = atoi(argv[1]); /* 获取信号接收进程标识符 */ 17 sg.sival_int = getpid(); /* 获取当前进程标识符,之后作为附加信息发送出去 */ 18 status = sigqueue(pid, SIGUSR1, sg); /* 发送信号 */ 19 if(status < 0) 20 printf("send error.\n"); 21 else 22 printf("Done!\n"); 23 return 0; 24 }
1 /* receive.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <signal.h> 6 void sig_handler(int sig, siginfo_t *info, void *t) /* 信号处理函数 */ 7 { 8 printf("Receive signal : %d\n", sig); /* 输出接收到的信号值 */ 9 printf("Receive message : %d\n", info->si_int); /* 输出接收到的附加信息,这里为发送进程的标志符 */ 10 return; 11 } 12 int main() 13 { 14 int status; 15 pid_t pid; 16 struct sigaction act; /* 定义sigaction结构 */ 17 pid = getpid(); /* 获取当前进程标识符 */ 18 act.sa_sigaction = sig_handler; /* 使用sa_sigaction来设定处理函数 */ 19 sigemptyset(&act.sa_mask); /* 清空信号集中的所有信号 */ 20 act.sa_flags = SA_SIGINFO; /* 设置SA_SIGINFO标志位 */ 21 status = sigaction(SIGUSR1, &act, NULL); /* 设置SIGUSR1信号的处理函数 */ 22 if(status < 0) 23 { 24 printf("sigaction error.\n"); 25 } 26 printf("Receiver:\n"); 27 printf("PID : %d\n", pid); /* 输出当前进程标识符 */ 28 for(;;); /* 无穷循环 */ 29 return 0; 30 }
三 消息队列
消息队列是一种比较高级的进程间通信方法,能够将格式化的数据单元传送给任意的进程,它与命名管道十分类似。由于Linux内核处理了大部分数据流的控制工作,所以消息队列使用起来要比命名管道简单很多,而且它还解决了使用管道进行通信时缓冲区受限的问题。当然,消息队列也是有缺点的,例如它的系统开销比较大,数据读写操作也更复杂一些。
1 消息队列的创建
msgget函数用来创建或打开一个消息队列。
int msgget(key_t key, int msgflg);
key_t ftok(const char *pathname, int proj_id);
1 /* example10.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/ipc.h> 7 #include <sys/msg.h> 8 int main() 9 { 10 int qid; 11 key_t key; 12 key = ftok("/home/yanyb/LinuxC", 'a'); /* 生成消息队列的键值 */ 13 if(key < 0) /* 如果ftok函数调用失败,输出错误信息并退出*/ 14 { 15 perror("ftok error"); 16 exit(1) ; 17 } 18 qid = msgget(key, IPC_CREAT | 0666); /* 创建一个消息队列 */ 19 if(qid < 0) 20 { 21 perror("msgget error"); /* 如果消息队列创建失败,输出错误信息并退出 */ 22 exit(1) ; 23 } 24 else 25 { 26 printf("Done!\n"); /* 如果消息队列创建成功,输出Done! */ 27 } 28 return 0; 29 }
2 消息队列的控制
msgctl函数用来对消息队列进行各种操作,例如修改消息队列的属性、清除队列中的所有消息等。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid是消息队列的引用标识符;
cmd是执行命令;
buf是一个缓冲区。
cmd参数指定对于由msqid规定的队列要执行的命令:
IPC_STAT 取此队列的msqid_ds结构,并将其存放在buf指向的结构中。
IPC_SET 按由buf指向的结构中的值,设置与此队列相关的结构中的下列四个字段:
msg_perm.uid、msg_perm.gid、msg_perm;mode和msg_qbytes。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。只有超级用户才能增加msg_qbytes的值。
IPC_RMID 从系统中删除该消息队列以及仍在该队列上的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错返回EIDRM。
此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。
1 /* example11.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/ipc.h> 7 #include <sys/msg.h> 8 int main(int argc, char **argv) 9 { 10 int qid; 11 int status; 12 if(argc != 2) /* 检查命令行参数个数是否正确 */ 13 { 14 printf("arguments error.\n"); 15 exit(1); 16 } 17 qid = atoi(argv[1]); /* 获取要删除的消息队列的标识符 */ 18 status = msgctl(qid, IPC_RMID, NULL); /* 删除指定的消息队列 */ 19 if(status < 0) /* 如果消息队列删除失败,输出错误信息并退出 */ 20 { 21 perror("msgctl error"); 22 exit(1); 23 } 24 printf("Removed!\n"); /* 消息队列删除成功 */ 25 return 0; 26 }
3 消息队列的读写
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
其中:msqid是消息队列的队列ID;
msgp是消息内容所在的缓冲区;
msgsz是消息的大小;
msgflg是标志,IPC_NOWAIT若消息并没有立交发送而调用进程会立即返回。
int msgrcv(int msqid, struct msgbuf *msgp,int msgsz, long msgtyp, int msgflg);
msqid是消息队列的引用标识符;
msgp是接收到的消息将要存放的缓冲区;
msgsz是消息的大小;
msgtyp是期望接收的消息类型;
msgflg是标志
struct msgbuf
{
long mtype; /* type of message */
char mtext[1]; /* message text */
};
1 /* msgsend.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <sys/types.h> 7 #include <sys/ipc.h> 8 #include <sys/msg.h> 9 #define MSG_SZ 128 10 struct msgbuf /* 定义消息结构 */ 11 { 12 long mtype; /* 消息的类型 */ 13 char mtext[MSG_SZ]; /* 消息的内容 */ 14 }; 15 int main() 16 { 17 int qid; 18 key_t key; 19 int ret; 20 struct msgbuf buf; /* 定义消息缓冲区 */ 21 key = ftok("/home/yanyb", 'a'); /* 生成消息队列的键值 */ 22 if(key < 0) 23 { 24 perror("ftok error"); 25 exit(1) ; 26 } 27 qid = msgget(key, IPC_CREAT | 0666); /* 创建一个消息队列 */ 28 if(qid < 0) 29 { 30 perror("msgget error"); 31 exit(1) ; 32 } 33 while(1) 34 { 35 printf("Input the message : "); 36 fgets(buf.mtext, MSG_SZ, stdin); /* 从键盘输入消息的内容 */ 37 if(strncmp(buf.mtext, "exit", 4) == 0) /* 如果从键盘输入exit,退出循环 */ 38 break; 39 buf.mtype = getpid(); /* 消息的类型,这里设置为当前进程的标识符 */ 40 ret = msgsnd(qid, &buf, MSG_SZ, 0); /* 向消息队列中写入一个消息 */ 41 if(ret < 0) 42 { 43 perror("msgsnd error"); /* 如果消息写入失败,输出错误信息 */ 44 exit(1) ; 45 } 46 else 47 { 48 printf("Send!\n"); /* 消息发送成功 */ 49 } 50 } 51 return 0; 52 }
1 /* msgreceive.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <sys/types.h> 7 #include <sys/ipc.h> 8 #include <sys/msg.h> 9 #define MSG_SZ 128 10 struct msgbuf /* 定义消息结构 */ 11 { 12 long mtype; /* 消息的类型 */ 13 char mtext[MSG_SZ]; /* 消息的内容 */ 14 }; 15 int main() 16 { 17 int qid; 18 key_t key; 19 int ret; 20 struct msgbuf buf; /* 定义消息缓冲区 */ 21 key = ftok("/home/yanyb", 'a'); /* 生成消息队列的键值 */ 22 if(key < 0) 23 { 24 perror("ftok error"); 25 exit(1) ; 26 } 27 qid = msgget(key, IPC_CREAT | IPC_EXCL | 0666); /* 打开消息队列 */ 28 if(qid < 0) 29 { 30 perror("msgget error"); 31 exit(1) ; 32 } 33 while(1) 34 { 35 memset(&buf, 0, sizeof(buf)); /* 清空消息缓冲区 */ 36 ret = msgrcv(qid, &buf, MSG_SZ, 0, 0); /* 从消息队列中读取一个消息 */ 37 if(ret < 0) 38 { 39 perror("msgsnd error"); /* 如果消息读取失败,输出错误信息 */ 40 exit(1) ; 41 } 42 else 43 { 44 printf("Received message :\n"); 45 /* 输出接收到的消息,包括消息的类型、长度、以及消息的内容 */ 46 printf("Type=%d, Length=%d, Text:%s\n", buf.mtype, ret, buf.mtext); 47 } 48 } 49 return 0; 50 }
1 #include <sys/types.h> 2 #include <sys/ipc.h> 3 #include <sys/msg.h> 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <unistd.h> 7 #include <string.h> 8 #define BUFSZ 512 9 struct message{ 10 long msg_type; 11 char msg_text[BUFSZ]; 12 }; 13 14 int main() 15 { 16 int qid; 17 key_t key; 18 int len; 19 struct message msg; 20 21 if((key=ftok(".",'a'))==-1) 22 { 23 perror("ftok"); 24 exit(1); 25 } 26 if((qid=msgget(key,IPC_CREAT|0666))==-1){ 27 perror("msgget"); 28 exit(1); 29 } 30 printf("opened queue %d\n",qid); 31 puts("Please enter the message to queue:"); 32 if((fgets(msg.msg_text,BUFSZ,stdin))==NULL) 33 { 34 puts("no message"); 35 exit(1); 36 } 37 msg.msg_type = getpid(); 38 len = strlen(msg.msg_text); 39 if((msgsnd(qid,&msg,len,0))<0){ 40 perror("message posted"); 41 exit(1); 42 } 43 if(msgrcv(qid,&msg,BUFSZ,0,0)<0){ 44 perror("msgrcv"); 45 exit(1); 46 } 47 printf("message is:%s\n",(&msg)->msg_text); 48 if((msgctl(qid,IPC_RMID,NULL))<0){ 49 perror("msgctl"); 50 exit(1); 51 } 52 exit(0); 53 }
四 信号量
信号量(Semaphore),也称为信号灯,主要用来控制多个进程对共享资源的访问。信号量是进程间通信的一种重要方法,但它本身并不进行数据交换,这点与前面介绍的管道和消息队列不同。
信号量是一个整型数,只能通过P、V原语进行操作。信号量大于或等于0时表示可供并发进程使用的资源数;小于0时表示正在等待使用资源的进程数。
1 信号量的创建
semget函数用来创建或打开一个信号量集。
int semget(key_t key, int nsems, int semflg);
1 /* example13.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/ipc.h> 7 #include <sys/sem.h> 8 int main() 9 { 10 int semid; 11 key_t key; 12 key = ftok("/home/yanyb", 'a'); /* 生成信号量的键值 */ 13 if(key < 0) /* 如果ftok函数调用失败,输出错误信息并退出*/ 14 { 15 perror("ftok error"); 16 exit(1) ; 17 } 18 semid = semget(key, 1, IPC_CREAT | 0666); /* 创建一块信号量集 */ 19 if(semid < 0) 20 { 21 perror("semget error"); /* 如果信号量创建失败,输出错误信息并退出 */ 22 exit(1) ; 23 } 24 printf("Done!\n"); 25 return 0; 26 }
2 信号量的控制
int semctl(int semid, int semnum, int cmd,
union semun arg);
IPC_RMID:删除一个信号量;
IPC_EXCL:只有在信号量集不存在时创建;
IPC_SET:设置信号量的访问权限;SETVAL:设置指定信号量的值;
GETVAL:获取指定信号量的值;GETPID:获取最后操作信号量进程的标识符;
GETNCNT:获取等待信号量变为1的进程数;
GETZCNT:获取等待信号量变为0的进程数。
3 信号量的操作
int semop(int semid, struct sembuf *sops, unsigned short nsops);
struct sembuf {
short int sem_num;
short int sem_op;
short int sem_flg;}
1 /* example14.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <error.h> 6 #include <sys/types.h> 7 #include <sys/ipc.h> 8 #include <sys/sem.h> 9 #include <sys/wait.h> 10 int main() 11 { 12 int semid; 13 pid_t pid; 14 key_t key; 15 struct sembuf lock = {0, -1, SEM_UNDO}; 16 struct sembuf unlock = {0, 1, SEM_UNDO | IPC_NOWAIT}; 17 int i, ret, status; 18 key = ftok("/home/yanyb", 'b'); /* 生成信号量的键值 */ 19 semid = semget(key, 1, IPC_CREAT | 0666); /* 创建一个信号量集 */ 20 if(semid < 0) /* 如果信号量创建失败,输出错误信息并退出 */ 21 { 22 perror("semget error"); 23 exit(1) ; 24 } 25 ret = semctl(semid, 0, SETVAL, 1); /* 将信号量的值设为1 */ 26 if(ret == -1) /* 如果操作失败,输出错误信息并退出 */ 27 { 28 perror("semctl error"); 29 exit(1); 30 } 31 pid = fork(); /* 创建子进程 */ 32 if(pid < 0) /* 如果进程创建失败,输出错误信息并退出 */ 33 { 34 printf("fork error"); 35 exit(1); 36 } 37 if(pid == 0) /* 子进程 */ 38 { 39 for(i=0; i<3; i++) 40 { 41 sleep(abs((int)(3.0*rand()/(RAND_MAX+1)))); /* 休眠0~3秒 */ 42 ret = semop(semid, &lock, 1); /* 申请访问共享资源 */ 43 if(ret == -1) 44 { 45 perror("lock error"); 46 exit(1); 47 } 48 printf("Child process access the resource.\n"); /* 开始访问共享资源 */ 49 sleep(abs((int)(3.0*rand()/(RAND_MAX+1)))); 50 printf("Complete!\n"); 51 ret = semop(semid, &unlock, 1); /* 共享资源访问完毕 */ 52 if(ret == -1) 53 { 54 perror("unlock error"); 55 exit(1); 56 } 57 } 58 } 59 else /* 父进程 */ 60 { 61 for(i=0; i<3 ;i++) 62 { 63 sleep(abs((int)(3.0*rand()/(RAND_MAX+1)))); 64 ret = semop(semid, &lock, 1); /* 申请访问共享资源 */ 65 if(ret == -1) 66 { 67 perror("lock error"); 68 exit(1); 69 } 70 printf("Parent process access the resource.\n"); /* 开始访问共享资源 */ 71 sleep(abs((int)(3.0*rand()/(RAND_MAX+1)))); 72 printf("Complete!\n"); 73 ret = semop(semid, &unlock, 1); /* 共享资源访问完毕 */ 74 if(ret == -1) 75 { 76 perror("unlock error"); 77 exit(1); 78 } 79 } 80 if(pid != wait(&status)) /* 等待子进程结束 */ 81 { 82 printf("wait error"); 83 exit(1); 84 } 85 ret = semctl(semid, 0, IPC_RMID, 0); /* 删除信号量 */ 86 if(ret == -1) /* 如果信号量删除失败,输出错误信息并退出 */ 87 { 88 perror("semctl error"); 89 exit(1); 90 } 91 } 92 return 0; 93 }
五 共享内存
共享内存就是多个进程将同一块内存区域映射到自己的进程空间之中,以此来实现数据的共享和传输,它是进程间通信方式中最快的一种。由于多个进程共享同一块内存区域,在程序设计过程中应注意进程访问的同步问题,可以与上面介绍的信号量结合使用。
共享内存有多种实现机制,这里介绍的是System V共享内存。
1 共享内存的创建
int shmget(key_t key, int size, int shmflg);
1 /* example15.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <sys/ipc.h> 6 #include <sys/shm.h> 7 #define SHM_SZ 1024 8 int main() 9 { 10 int shmid; 11 key_t key; 12 key = ftok("/home/yanyb", 'a'); /* 生成共享内存的键值 */ 13 if(key < 0) /* 如果ftok函数调用失败,输出错误信息并退出*/ 14 { 15 perror("ftok error"); 16 exit(1) ; 17 } 18 shmid = shmget(key, SHM_SZ, IPC_CREAT | 0666); /* 创建一块共享内存 */ 19 if(shmid < 0) 20 { 21 perror("shmget error"); /* 如果共享内存创建失败,输出错误信息并退出 */ 22 exit(1) ; 23 } 24 else 25 { 26 printf("Done!\n"); 27 } 28 return 0; 29 }
2 共享内存的读写
void *shmat(int shm_id, void *shm_addr, int shmflg);
int shmdt(void *shmaddr);
1 /* example16.c */ 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <sys/ipc.h> 7 #include <sys/shm.h> 8 #define SHM_SZ 1024 /* 共享内存的大小 */ 9 #define TIME_OUT 2 10 int main(int argc, char **argv) 11 { 12 int shmid; 13 key_t key; 14 pid_t pid; 15 int psm; 16 struct shmid_ds dsbuf; 17 if(argc != 2) /* 检查命令行参数个数是否正确 */ 18 { 19 printf("arguments error.\n"); 20 exit(1); 21 } 22 key = ftok("/home/yanyb", 'a'); /* 生成共享内存的键值 */ 23 if(key < 0) /* 如果ftok函数调用失败,输出错误信息并退出*/ 24 { 25 perror("ftok error"); 26 exit(1) ; 27 } 28 shmid = shmget(key, SHM_SZ, IPC_CREAT | 0666); /* 创建一块共享内存 */ 29 if(shmid < 0) /* 如果共享内存创建失败,输出错误信息并退出 */ 30 { 31 perror("shmget error"); 32 exit(1) ; 33 } 34 pid = fork(); /* 创建子进程 */ 35 if(pid<0) /* 如果进程创建失败,输出错误信息并退出 */ 36 { 37 printf("fork error.\n"); 38 exit(1); 39 } 40 if(pid==0) /* 子进程,向共享内存中写入数据 */ 41 { 42 printf("Child process:\n"); 43 printf("PID : %d\n", getpid()); /* 输出子进程的标志符 */ 44 psm = shmat(shmid, NULL, 0); /* 将共享内存映射到进程的地址空间中 */ 45 if(psm == -1) /* 如果映射失败,输出错误信息并退出 */ 46 { 47 perror("shmat error\n"); 48 exit(1); 49 } 50 else /* 共享内存映射成功 */ 51 { 52 strcpy((char *)psm, argv[1]); /* 向共享内存中写入数据,这里传入为命令行参数 */ 53 printf("Send message : %s\n", (char *)psm); 54 if((shmdt((void *)psm)) < 0) /* 使共享内存脱离进程的地址空间 */ 55 perror("shmdt error\n"); 56 sleep(TIME_OUT); 57 } 58 } 59 else /* 父进程,从共享内存中读取数据 */ 60 { 61 sleep(TIME_OUT); 62 printf("Parent process:\n"); 63 printf("PID : %d\n", getpid()); /* 输出父进程的标志符 */ 64 if((shmctl(shmid, IPC_STAT, &dsbuf)) < 0) /* 获取共享内存的状态信息 */ 65 { 66 perror("shmctl error\n"); 67 exit(1); 68 } 69 else /* 共享内存的状态信息获取成功 */ 70 { 71 printf("Shared Memory Information:\n"); 72 printf("\tCreator PID: %d\n", dsbuf.shm_cpid); /* 输出创建共享内存进程的标识符 */ 73 printf("\tSize(bytes): %d\n",dsbuf.shm_segsz); /* 输出共享内存的大小 */ 74 printf("\tLast Operator PID: %d\n",dsbuf.shm_lpid); /* 输出上一次操作共享内存进程的标识符 */ 75 psm = shmat(shmid, NULL, 0); /* 将共享内存映射到进程的地址空间中 */ 76 if(psm == -1) /* 如果映射失败,输出错误信息并退出 */ 77 { 78 perror("shmat error\n"); 79 exit(1); 80 } 81 else /* 共享内存映射成功 */ 82 { 83 printf("Received message : %s\n", (char *)psm); /* 从共享内存中读取数据 */ 84 if(shmdt((void *)psm) < 0) /* 使共享内存脱离进程的地址空间 */ 85 perror("shmdt error\n"); 86 } 87 } 88 if(shmctl(shmid, IPC_RMID, NULL) < 0) /* 删除前面创建的共享内存 */ 89 { 90 perror("shmctl error\n"); 91 exit(1); 92 } 93 } 94 return 0; 95 }
六 常见面试题
常见面试题1:Linux系统中进程间通信的方法主要有哪几种?
常见面试题2:一般情况下,进程收到信号后如何进行响应?
七 小结
进程间通信就是在不同进程之间传送或交换信息。这里介绍了Linux系统下进程间通信的几种主要方法,包括匿名管道、命名管道、信号、消息队列、信号量以及共享内存等。管道和信号属于传统的Unix IPC机制,消息队列、信号量以及共享内存则属于System V的IPC机制。
本次内容是Linux系统下进行程序设计的重要基础之一,读者应该全面、系统地学习掌握,并在编程练习中体会每种方法的优缺点、所适用的范围等。