2019年8月9日星期五
一. linux多进程编程 - 通信方式。
1. 为什么进程之间要进行数据通信?
例子:
./test -> 开启一个名字为test的进程。
./project -> 开启一个名字为project的进程。
通过学习通信方式,使得不同进程之间进行数据交换,例如test进程发送数据给project进程,从而控制project进程运行状态。
2. 在linux下,通信方式有哪些?各自有什么特点?
以下几种方式属于系统编程的通信方式,只能同一台主机内部进程通信,不能跨主机。
1)管道通信
管道通信分为有名管道与无名管道通信,管道也是linux的一种特殊文件,进程可以写入数据到管道中,从而实现通信。
2)信号
在linux下,有非常多信号,例如暂停,继续,停止...,某一个进程通过发送信号给另外一个进程,从进行通信。
3)消息队列
消息队列可以读取另外一个进程发送来的数据,而且可以读取特定的数据。
4)共享内存
多个进程同时访问同一片内存空间。
能够跨主机通信的,只有网络编程才能实现。
1)套接字编程
可以实现不同的主机之间的通信。
二. 进程之间通信 - 无名管道
1. 什么是无名管道?作用机制如何?
无名管道只能作用于亲缘关系的进程,例如父子进程。无名管道其实是数组来的,里面有读端与写端,进程只需要将数据写入/读取到管道中,就可以实现通信。
2. 如何初始化无名管道中的读写端文件描述符?
1)申请数组 -> int fd[2]; -> 里面的数据不是文件描述符
2)调用API函数初始化数组 -> pipe() -> man 2 pipe
功能: create pipe
使用格式:
#include <unistd.h>
int pipe(int pipefd[2]);
pipefd: 一个具有2个int类型数据的数组
返回值:
成功:0
失败:-1
3)初始化完毕
pipefd[0] -> 读端
pipefd[1] -> 写端
例子1:初始化无名管道,文件描述符是多少?
#include <stdio.h>
#include <unistd.h>
int main()
{
//1. 申请数组
int fd[2] = {0};
printf("fd[0] = %d ",fd[0]); //0
printf("fd[1] = %d ",fd[1]); //0
//2. 初始化数组
int ret;
ret = pipe(fd);
printf("ret = %d ",ret);
//3. 读端与写端是多少
printf("fd[0] = %d ",fd[0]); //3
printf("fd[1] = %d ",fd[1]); //4
return 0;
}
例子2:实现使用无名管道,让父子进程之间通信。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2] = {0}; //资源,会继承给子进程
pipe(fd);
pid_t x;
x = fork();
if(x > 0)
{
char buf[10] = {0};
read(fd[0],buf,sizeof(buf));
printf("child to parent:%s ",buf);
}
if(x == 0)
{
char buf[10] = "hello";
write(fd[1],buf,strlen(buf));
}
return 0;
}
练习1:父进程发送数据给子进程,子进程收到数据后,就播放一首歌曲。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2] = {0}; //资源,会继承给子进程
pipe(fd);
pid_t x;
x = fork();
if(x > 0)
{
char buf[10] = {0};
fgets(buf,10,stdin); //包含 在内!
write(fd[1],buf,strlen(buf)); //playmp3
}
if(x == 0)
{
char buf[10] = {0};
read(fd[0],buf,sizeof(buf));
if(strncmp(buf,"playmp3",7) == 0)
{
system("madplay jay.mp3");
}
}
return 0;
}
三. 进程之间通信 - 有名管道
1. 什么是有名管道,机制如何?
无名管道是一个数组来的,而有名管道是文件来的,因为在整个linux系统下,都可以看到这个文件,所以有名管道作用范围是整个linux系统的任意两个进程。
2. 如何创建有名管道? -> mkfifo() -> man 3 mkfifo
功能: make a FIFO special file (a named pipe) -> 创建有名管道
使用格式:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname:有名管道的路径
mode:八进制权限 0777
返回值:
成功:0
失败:-1
例子1:尝试在家目录创建一个有名管道文件,名字为fifo_test。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int ret;
ret = mkfifo("/home/gec/fifo_test",0777);
printf("mkfifo ret = %d ",ret);
return 0;
}
练习2:使用有名管道实现两个进程之间的通信。
写端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
int main(int argc,char *argv[])
{
//在 -> 0 -> if(0) -> 不执行mkfifo
//不在 -> -1 -> if(-1) -> 执行mkfifo
//1. 在linux下,创建管道文件
if(access("/home/gec/fifo",F_OK))
{
int ret = mkfifo("/home/gec/fifo",0777);
if(ret == -1)
printf("mkfifo error! ");
}
//2. 访问管道文件
int fd;
fd = open("/home/gec/fifo",O_RDWR); //一定要给可读可写权限,否则管道就会阻塞。
if(fd < 0)
printf("open error! ");
//3. 不断读取有名管道中的数据
char buf[100];
while(1)
{
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin); //包含 在内。
write(fd,buf,strlen(buf));
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
close(fd);
return 0;
}
读端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
int main(int argc,char *argv[])
{
//1. 在linux下,创建管道文件
if(access("/home/gec/fifo",F_OK))
{
int ret = mkfifo("/home/gec/fifo",0777);
if(ret == -1)
printf("mkfifo error! ");
}
//2. 访问管道文件
int fd;
fd = open("/home/gec/fifo",O_RDWR); //一定要给可读可写权限,否则管道就会阻塞。
if(fd < 0)
printf("open error! ");
//3. 不断读取有名管道中的数据
char buf[100];
while(1)
{
bzero(buf,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("from fifo:%s",buf);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
}
close(fd);
return 0;
}
四. 进程之间通信方式 - 信号
1. 在linux下,有哪些信号?
gec@ubuntu:~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
例子:19) SIGSTOP
19 -> 信号值
SIGSTOP -> 信号的名字
其实信号名字与信号值是等价的,它们都是一些宏定义来的,被定义在一个头文件中:/usr/include/asm-generic/signal.h
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
2. 常用信号?
2) SIGINT -> "ctrl + C"
9) SIGKILL -> 杀死进程
10) SIGUSR1、12) SIGUSR2 -> 提供给用户使用两个信号
18) SIGCONT -> 继续
19) SIGSTOP -> 暂停
3. 在linux下,信号由谁发出?
1)信号由系统产生。
例子: 当子进程退出时,自动发出一个信号SIGCHLD给父进程。
2)信号也可以是由用户发出。
例子: 就必须使用到kill/killall等命令。
4. 用户怎么发送信号给进程?
1)首先查看该进程的PID号,通过kill命令进行发送。
gec@ubuntu:~$ ps -ef
gec 4023 3854 0 23:56 pts/1 00:00:00 ./2 -> PID号为4023的进程正在运行。
以下两个命令都可以杀死该进程。
gec@ubuntu:~$ kill -9 4023
gec@ubuntu:~$ kill -SIGKILL 4023
2)直接通过killall命令给进程名字发送信号。
只要系统中有名字为2的进程,都会被杀死
gec@ubuntu:~$ killall -9 2
gec@ubuntu:~$ killall -SIGKILL 2
5. 如何在后台运行程序?
只需要在命令后面加"&"
例如: system("madplay jay.mp3 &"); -> 在后台播放歌曲
练习2:首先在后台播放歌曲,播放一段时间,通过发送信号给歌曲实现暂停,继续,停止...
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
//1. 在后台播放一首歌曲
system("madplay jay.mp3 &");
printf("playing..... ");
sleep(10);
//2. 发送暂停信号给播放器
system("killall -SIGSTOP madplay");
printf("pause... ");
sleep(5);
//3. 发送继续信号给播放器
system("killall -SIGCONT madplay");
printf("continue... ");
sleep(5);
//4. 停止播放
system("killall -9 madplay");
printf("kill... ");
return 0;
}
五. 关于信号API函数。
1. 如何在工程中发送信号给另外一个进程? -> kill() -> man 2 kill
功能:send signal to a process
使用格式:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid:需要发送信号的进程的PID号 sig:信号值
返回值:
成功:0
失败:-1
2. 如何在进程中捕捉信号? -> signal() -> man 2 signal
功能: signal - ANSI C signal handling -> 信号的处理函数(将来收到某个信号,我需要处理什么事情?)
使用格式:
#include <signal.h>
typedef void (*sighandler_t)(int); // sighandler_t 等价于 void(*)(int)
sighandler_t signal(int signum, sighandler_t handler);
signum: 需要捕捉的信号值
handler: 信号处理函数 void fun(int sig) -> 收到这个信号后,需要做什么事情,就把这些事情写到函数里面就可以了。
返回值:
成功: 信号处理函数的地址
失败: SIG_ERR
例子:
void fun(int sig) -> sig就是捕捉到的信号值 sig=10
{
/* 捕捉到信号之后,需要做事情就写在这里 */
}
signal(SIGUSR1,fun); -> 将来收到SIGUSR1这个信号,就执行fun这个函数。
例子1:子进程发送SIGUSR1信号给父进程,父进程收到该信号后,就打印收到的信号值。
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
void fun(int sig)
{
printf("sig = %d ",sig);
}
int main()
{
pid_t x;
x = fork();
if(x > 0)
{
signal(SIGUSR1,fun); //提前声明
wait(NULL); //等待子进程发送完信号,再退出!
exit(0);
}
if(x == 0)
{
usleep(500000); //确保父进程先执行
kill(getppid(),SIGUSR1);
exit(0);
}
return 0;
}
练习3:父进程发送信号给子进程,子进程收到信号后,控制板子音乐、图片..。
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
void fun(int sig)
{
system("madplay jay.mp3");
}
int main()
{
pid_t x;
x = fork();
if(x > 0) //父进程返回子进程的ID号
{
usleep(500000); //确保子进程先执行
kill(x,SIGUSR1);
exit(0);
}
if(x == 0)
{
signal(SIGUSR1,fun); //提前声明
pause(); ->就一直等待一个信号的到达!
}
return 0;
}
3. 挂起进程,直到收到信号为止。 -> pause() -> man 2 pause
功能: wait for signal -> 进程等待一个信号
使用格式:
#include <unistd.h>
int pause(void);
参数:无
返回值:
成功: 收到信号了,返回-1
失败: 收到致命信号,就不返回。
4. 自己给自己发送信号 -> raise() -> man 3 raise
功能: send a signal to the caller -> 给发送者发送信号
使用格式:
#include <signal.h>
int raise(int sig);
sig: 需要发送的信号
返回值:
成功:0
失败:非0
raise(SIGUSR1); 等价于 kill(getpid(),SIGUSR1);
例子:自己给自己发送一个SIGUSR1的信号。
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
void fun(int sig)
{
printf("sig = %d ",sig);
}
int main()
{
signal(SIGUSR1,fun);
sleep(1);
raise(SIGUSR1);
return 0;
}
六. 研究signal函数的第二个参数。
现实例子:
听到"吃饭",就动身去网吧 -> 自定义动作。
听到"吃饭", 就说"好" -> 默认动作。
听到"吃饭", 我理都不理你 -> 忽略
1. 写自定义函数( the address of a programmer-defined function) -> 收到信号后,想做什么事情,是由自己来决定。
信号处理函数: void fun(int sig)
例子:
void fun(int sig)
{
show_bmp("xxx.bmp"); //显示图片
}
signal(SIGUSR1,fun); //收到信号后,就执行fun()
2. 忽略(SIG_IGN) -> 将来收到这个信号后,不会相应该信号。
例子:
signal(SIGUSR1,SIG_IGN); -> 将来进程收到SIGUSR1这个信号,不会响应。
3. 默认动作(SIG_DFL) -> 本来这个信号是做什么事情的,就去做什么事情。
signal(SIGINT,DFL); -> 将来收到SIGINT,就等于执行"ctrl + C"的动作。
注意:
The signals SIGKILL and SIGSTOP cannot be caught or ignored. -> SIGKILL与SIGSTOP这两个信号不能被捕捉。
别人发了SIGKILL信号给你,你就一定要死,不可以拒绝!
别人发了SIGSTOP信号给你,你就一定要进入暂停态,不可以拒绝!
typedef int a;
typedef void (*)(int) sighandler_t
void fun(int sig) //函数
void(*p)(int); //函数指针变量
void(*)(int); -> 函数指针变量的类型 等价于 sighandler_t
void(*p)(int); 等价于 sighandler_t p