多进程理论基础 引入目的 1> 实现多任务并发执行 的一种途径
2> 可以实现数据在多个进程之间进行通信,共同处理整个程序的相关数据,提供工作效率
多进程相关概念 1> 进程是程序的一次执行过程 ,有一定的生命周期,包含了创建态、就绪态、执行态、挂起态、死亡态
2> 进程是计算机资源分配的基本单位 ,系统会给每个进程分配0–4G的虚拟内存,其中0–3G是用户空间,3–4G是内核空间
其中多个进程中0–3G的用户空间是相互独立的,但是,3–4G的内核空间是相互共享的
用户空间细分为:栈区、堆区、静态区
3> 进程的调度机制:时间片轮询上下文切换机制,即给每个进程单次分配极小的时间片,在多个进程间快速轮换
4> 并发和并行的区别:
进程的内存管理
进程和程序的区别
**进程:**是动态的,进程是程序的一次执行过程,是有生命周期的,进程会被分配0–3G的用户空间,进程是在内存上存着的
**程序:**是静态的,没有所谓的生命周期,程序存储在磁盘设备上的二进制文件
hello.cpp —> g++ —-> a.out
进程的种类 进程一共有三种:交互进程、批处理进程、守护进程
交互进程 :它是由shell控制,可以直接和用户进行交互的,例如文本编辑器
批处理进程 :内部维护了一个队列,被放入该队列中的进程,会被统一处理。例如 g++编译器的一步到位的编译
守护进程 :脱离了终端而存在,随着系统的启动而运行,随着系统的退出而停止。例如:操作系统的服务进程
进程PID 1> PID(Process ID):进程号 ,进程号是一个大于等于0的整数值,是进程的唯一标识,不可能重复。
2> PPID(Parent Process ID):父进程号 ,系统中允许的每个进程,都是拷贝父进程资源得到的
3> 在linux系统中的 /proc目录下的数字命名的目录其实都是一个进程
特殊的进程
0号进程(idel) :是由linux操作系统启动后运行的第一个进程,也叫空闲进程,当没有其他进程运行时,会运行该进程。他也是1号进程和2号进程的父进程
1号进程(init) :是由0号进程创建出来的,这个进程会完成一些硬件的必要初始化工作,除此之外,还会收养孤儿进程
2号进程(kthreadd) :也称调度进程,这个进程也是由0号进程创建出来的,主要完成任务调度问题
孤儿进程 :当前进程还正在运行,其父进程已经退出了。
说明:每个进程退出后,其分配的系统资源应该由其父进程进行回收,否则会造成资源的浪费
僵尸进程 :当前进程已经退出了,但是其父进程没有为其回收资源
进程操作的指令
ps指令:能够查看当前运行的进程相关属性
ps -ef :能够显示进程之间的关系
UID:用户ID号
PID:进程号
PPID:父进程号
C:用处不大
STIME:开始运行的时间
TTY:如果是问号表示这个进程不依赖于终端而存在
CDM:名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 UID PID PPID C STIME TTY TIME CMD root 1 0 0 03:49 ? 00:00:04 /usr/lib/systemd/systemd --switched-root --system --deserialize 16 root 2 0 0 03:49 ? 00:00:00 [kthreadd] root 3 2 0 03:49 ? 00:00:00 [rcu_gp] root 4 2 0 03:49 ? 00:00:00 [rcu_par_gp] root 6 2 0 03:49 ? 00:00:00 [kworker/0:0H-kblockd] root 9 2 0 03:49 ? 00:00:00 [mm_percpu_wq] root 10 2 0 03:49 ? 00:00:02 [ksoftirqd/0] root 11 2 0 03:49 ? 00:00:04 [rcu_sched] root 12 2 0 03:49 ? 00:00:00 [migration/0] root 13 2 0 03:49 ? 00:00:00 [watchdog/0] root 14 2 0 03:49 ? 00:00:00 [cpuhp/0] root 16 2 0 03:49 ? 00:00:00 [kdevtmpfs] root 17 2 0 03:49 ? 00:00:00 [netns] root 18 2 0 03:49 ? 00:00:00 [kauditd] root 19 2 0 03:49 ? 00:00:00 [khungtaskd] root 20 2 0 03:49 ? 00:00:00 [oom_reaper] root 21 2 0 03:49 ? 00:00:00 [writeback] root 22 2 0 03:49 ? 00:00:00 [kcompactd0] root 23 2 0 03:49 ? 00:00:00 [ksmd] root 24 2 0 03:49 ? 00:00:00 [khugepaged]
ps -ajx:能够显示当前进程的状态
PGID:进程组ID,多个关联进程组成一个组
SID:会话组ID,多个关联进程组组成一个会话
STAT:进程的状态
1 2 3 4 5 6 7 8 9 PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 0 1 1 1 ? -1 Ss 0 0:04 /usr/lib/systemd/systemd --switched-root --system --deserialize 16 0 2 0 0 ? -1 S 0 0:00 [kthreadd] 2 3 0 0 ? -1 I< 0 0:00 [rcu_gp] 2 4 0 0 ? -1 I< 0 0:00 [rcu_par_gp] 2 6 0 0 ? -1 I< 0 0:00 [kworker/0:0H-kblockd] 2 9 0 0 ? -1 I< 0 0:00 [mm_percpu_wq] 2 10 0 0 ? -1 S 0 0:02 [ksoftirqd/0] 2 11 0 0 ? -1 R 0 0:04 [rcu_sched]
ps -aux:可以查看当前进程对CPU和内存的占用率
1 2 3 4 5 6 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.8 253396 6744 ? Ss 03:49 0:04 /usr/lib/systemd/systemd --switched-root --system --deserialize 16 root 2 0.0 0.0 0 0 ? S 03:49 0:00 [kthreadd] root 3 0.0 0.0 0 0 ? I< 03:49 0:00 [rcu_gp] root 4 0.0 0.0 0 0 ? I< 03:49 0:00 [rcu_par_gp] root 6 0.0 0.0 0 0 ? I< 03:49 0:00 [kworker/0:0H-kblockd]
top:动态查看进程的相关属性
kill指令:发送信号的指令
使用方式:kill -信号号 进程号
可以通过指令:kill -l查看能够发送的信号有哪些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX SIGHUP:当进程所在的终端被关闭后,终端会给运行在当前终端的每个进程发送该信号,默认结束进程 SIGINT:中断信号,当用户键入ctrl + c时发射出来 SIGQUIT:退出信号,当用户键入ctrl + /是发送,退出进程 SIGKILL:杀死指定的进程 SIGSEGV:当指针出现越界访问时,会发射,表示段错误 SIGPIPE:当管道破裂时会发送该信号 SIGALRM:当定时器超时后,会发送该信号 SIGSTOP:暂停进程,当用户键入ctrl+z时发射 SIGTSTP:也是暂停进程 SIGUSR1、SIGUSR2 :留给用户自定义的信号,没有默认操作 SIGCHLD:当子进程退出后,会向父进程发送该信号
pidof:查看进程的进程号
使用方式:pidof 进程名
进程状态及切换
可以通过 man ps进行查看进程的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 进程主状态: D uninterruptible sleep (usually IO) R running or runnable (on run queue) S interruptible sleep (waiting for an event to complete) T stopped by job control signal t stopped by debugger during the tracing W paging (not valid since the 2.6.xx kernel) X dead (should never be seen) Z defunct ("zombie" ) process, terminated but not reaped by its parent 附加态: < high-priority (not nice to other users ) N low-priority (nice to other users ) L has pages locked into memory (for real-time and custom IO) s is a session leader l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do ) + is in the foreground process group
状态切换的实例
1 2 3 4 1、如果有停止的进程,可以在终端输入指令:jobs -l查看停止进程的作业号 2、通过使用指令:bg 作业号 实现将停止的进程进入后台运行状态,如果只有一个停止的进程,输入bg不加作业号也可以 3、对后台运行的进程,输入 fg 作业号 实现将后台运行的进程切换到前台运行 4、直接将可执行程序后台运行: ./可执行程序 &
进程主要状态的转换(五态图)
多进程实现 进程的创建过程,是子进程通过拷贝父进程得到的,新进程的创建直接拷贝父进程的资源,只需改变很少部分的数据即可,保留了父进程的大部分的数据信息(遗传基因),所以这个拷贝过程,系统通过一个函数fork来自动完成
进程的创建:fork 原型:
1 2 3 4 5 #include <unistd.h> pid_t fork (void ) ;功能:通过拷贝父进程得到一个子进程 参数:无 返回值:成功在父进程中得到子进程的pid,在子进程中的到0 ,失败在父进程中返回-1 并置位错误码,子进程无返回值
1> 不关注返回值的案例
1 2 3 4 5 6 7 8 9 #include <myhead.h> int main (int argc, const char *argv[]) { printf ("ni hao xingqiu\n" ); fork(); printf ("hello world\n" ); while (1 ); return 0 ; }
2> 多个fork创建进程
如果不关注返回值的话,有n个fork,会产生2^n个进程
3> 关注返回值的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = -1 ; pid = fork(); printf ("pid = %d\n" , pid); if (pid > 0 ) { printf ("我是父进程\n" ); } else if (pid == 0 ) { printf ("我是子进程\n" ); } else { perror ("fork error" ); return -1 ; } while (1 ); return 0 ; }
4> 父子进程并发执行的案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = -1 ; pid = fork(); if (pid > 0 ) { while (1 ) { printf ("我是父进程1111\n" ); sleep (1 ); } } else if (pid == 0 ) { while (1 ) { printf ("我是子进程\n" ); sleep (1 ); } } else { perror ("fork error" ); return -1 ; } return 0 ; }
父子进程号的获取:getpid、getppid 原型:
1 2 3 4 5 6 7 8 9 10 11 #include <sys/types.h> #include <unistd.h> pid_t getpid (void ) ;功能:获取当前进程的进程号 参数:无 返回值:当前进程的进程号 pid_t getppid (void ) ;功能:获取当前进程的父进程pid号 参数:无 返回值:当前进程的父进程pid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = -1 ; pid = fork(); if (pid > 0 ) { printf ("self pid = %d,child pid = %d,parent pid = %d\n" ,getpid (),pid,getpid ()); } return 0 ; }
进程退出:exit/_exit 上述两个函数都可以完成进程的退出,区别是在退出进程时,是否刷新标准IO的缓冲区
exit属于库函数,使用该函数退出进程时,会刷新标准IO的缓冲区 后退出
_exit属于系统调用(内核提供的函数),使用该函数退出进程时,不会刷新标准IO的缓冲区
原型:
1 2 3 4 5 6 7 8 9 10 11 #include <stdlib.h> void exit (int status) ;功能:退出当前进程,并刷新当前进程打开的标准IO的缓冲区 参数:进程退出时的状态,会将改制 与 0377 进行位与运算后,返回给回收资源的进程 返回值:无 #include <unistd.h> void _exit(int status);功能:退出当前进程,不刷新当前进程打开的标准IO的缓冲区 参数:进程退出时的状态,会将改制 与 0377 进行位与运算后,返回给回收资源的进程 返回值:无
status 可输入的值(分两类)
类型
常用值
含义
标准宏定义
EXIT_SUCCESS
表示进程正常退出 (等价于 0),跨平台兼容
EXIT_FAILURE
表示进程异常退出 (等价于 1),跨平台兼容
自定义数值
0
等同于 EXIT_SUCCESS,最常用的 “正常退出” 标识
非 0 整数(1/2/127 等)
自定义异常码,比如:1 = 文件不存在、2 = 参数错误、3 = 权限不足(自己约定)
进程资源回收:wait、waitpid 有两个函数可以完成对进程资源的回收
原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <sys/types.h> #include <sys/wait.h> pid_t wait (int *status) ;功能:阻塞回收子进程的资源 参数:接收子进程退出时的状态,获取子进程退出时的状态与0377 进行位与后的结果,如果不愿意接收,可以填NULL 返回值:成功返回回收资源的那个进程的pid号,失败返回-1 并置位错误码 pid_t waitpid (pid_t pid, int *status, int options) ;功能:可以阻塞也可以非阻塞回收指定进程的资源 参数1 :进程号 >0 :表示回收指定的进程,进程号位pid(常用) =0 :表示回收当前进程所在进程组中的任意一个子进程(已退出的) =-1 :表示回收任意一个子进程(已退出)(常用) <-1 :表示回收指定进程组中的任意一个子进程,进程组id为给定的pid的绝对值 参数2 :接收子进程退出时的状态,获取子进程退出时的状态与0377 进行位与后的结果,如果不愿意接收,可以填NULL 参数3 :是否阻塞 0 :表示阻塞等待 WNOHANG:表示非阻塞 返回值: >0 : 返回的是成功回收的子进程pid号 =0 :表示本次没有回收到子进程 =-1 :出错并置位错误码
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = -1 ; pid = fork(); if (pid > 0 ) { printf ("self pid=%d, child pid = %d, parent pid = %d\n" , getpid (), pid, getppid ()); sleep (8 ); waitpid (-1 , NULL , WNOHANG); printf ("子进程资源已经回收\n" ); }else if (pid == 0 ) { printf ("self pid = %d, parent pid = %d\n" , getpid (), getppid ()); printf ("11111111111111111111111111111111111" ); sleep (3 ); exit (EXIT_SUCCESS); }else { perror ("fork error" ); return -1 ; } while (1 ); return 0 ; }
拷贝案例练习 使用多进程完成两个文件的拷贝工作,父进程拷贝前一半内容,子进程拷贝后一半内容,父进程要回收子进程的资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 #include <myhead.h> int get_file_size (const char *srcfile, const char *destfile) { int srcfd, destfd; if ((srcfd = open (srcfile, O_RDONLY)) == -1 ) { perror ("open secfile error" ); return -1 ; } if ((destfd = open (destfile, O_WRONLY|O_CREAT|O_TRUNC, 0664 )) == -1 ) { perror ("open destfile error" ); return -1 ; } int len = lseek (srcfd, 0 , SEEK_END); close (srcfd); close (destfd); return len; } int copy_file (const char *srcfile, const char *destfile, int start, int len) { int srcfd, destfd; if ((srcfd = open (srcfile, O_RDONLY)) == -1 ) { perror ("open secfile error" ); return -1 ; } if ((destfd = open (destfile, O_WRONLY)) == -1 ) { perror ("open destfile error" ); return -1 ; } lseek (srcfd, start, SEEK_SET); lseek (destfd, start, SEEK_SET); char buf[128 ] = "" ; int sum = 0 ; while (1 ) { int res = read (srcfd, buf, sizeof (buf)); sum += res; if (res==0 || sum>=len) { write (destfd, buf, res-(sum-len)); break ; } write (destfd, buf, res); 少 } return 0 ; } int main (int argc, const char *argv[]) { if (argc != 3 ) { printf ("input file error\n" ); printf ("usage:./a.out srcfile destfile\n" ); return -1 ; } int len = get_file_size (argv[1 ], argv[2 ]); pid_t pid = fork(); if (pid > 0 ) { copy_file (argv[1 ], argv[2 ], 0 , len/2 ); wait (NULL ); }else if (pid == 0 ) { copy_file (argv[1 ], argv[2 ], len/2 , len-len/2 ); exit (EXIT_SUCCESS); }else { perror ("fork error" ); return -1 ; } printf ("拷贝成功\n" ); return 0 ; }
父进程创建两个进程并为其收尸 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = fork(); if (pid > 0 ) { pid_t pid_2 = fork(); if (pid_2 > 0 ) { printf ("我是父进程\n" ); wait (NULL ); wait (NULL ); }else if (pid_2 == 0 ) { sleep (3 ); printf ("我是进程2\n" ); exit (EXIT_SUCCESS); } }else if (pid == 0 ) { sleep (3 ); printf ("我是进程1\n" ); exit (EXIT_SUCCESS); }else { perror ("fork error" ); return -1 ; } return 0 ; }
僵尸进程和孤儿进程 1> 孤儿进程 :当前进程还正在运行,其父进程已经退出了。
说明:每个进程退出后,其分配的系统资源应该由其父进程进行回收 ,否则会造成资源的浪费
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = -1 ; pid = fork(); if (pid > 0 ) { sleep (5 ); exit (EXIT_SUCCESS); }else if (pid == 0 ) { while (1 ) { printf ("我是子进程\n" ); sleep (1 ); } }else { perror ("fork error" ); return -1 ; } while (1 ); return 0 ; }
2> 僵尸进程 :当前进程已经退出了,但是其父进程没有为其回收资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = -1 ; pid = fork(); if (pid > 0 ) { while (1 ) { printf ("我是父进程\n" ); sleep (1 ); } }else if (pid == 0 ) { sleep (5 ); exit (EXIT_SUCCESS); }else { perror ("fork error" ); return -1 ; } while (1 ); return 0 ; }
进程间通信 IPC 1> 由于多个进程的用户空间是相互独立的 ,其栈区、堆区、静态区的数据都是彼此私有的 ,所以不可能通过用户空间中的区域完成多个进程之间数据的通信
2> 可以使用外部文件来完成多个进程之间数据的传递 ,一个进程向文件中写入数据,另一个进程从文件中读取数据。该方式要必须保证写进程先执行,然后再执行读进程 ,要保证进程执行的同步性 (不推荐,实践中相对麻烦)
3> 我们可以利用内核空间来完成对数据的通信工作 ,本质上,在内核空间创建一个特殊的区域,一个进程向该区域中存放数据,另一个进程可以从该区域中读取数据
4> 引入原因:用户空间中的数据,不能作为多个进程之间数据交换的容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <myhead.h> int num = 520 ; int main (int argc, const char *argv[]) { pid_t pid = fork(); if (pid > 0 ) { num = 1314 ; printf ("父进程中:num = %d\n" , num); wait (NULL ); }else if (pid == 0 ) { sleep (3 ); printf ("子进程中:num = %d\n" , num); }else { perror ("fork error" ); return -1 ; } return 0 ; }
进程间通信的基础概念 1> IPC :interprocess communication 进程间通信
2> 使用内核空间来完成多个进程间相互通信,根据使用的容器或方式不同,分为三类通信机制
3> 进程间通信方式分类
1 2 3 4 5 6 7 8 9 1、内核提供的通信方式(传统的通信方式,效率较低) 无名管道 有名管道 信号 2、system V提供的通信方式(同主机多进程) 消息队列 共享内存 信号量(信号灯集) 3、套接字通信:socket 网络通信(跨主机通信)
无名管道(亲缘进程间) 1> 管道 的原理:管道是一种特殊的文件,该文件不用于存储数据,只用于进程间通信 。管道分为有名管道和无名管道 。
字母
英文全称
中文含义
b
block device
块设备文件
c
character device
字符设备文件
d
directory
目录文件(文件夹)
-
regular file
普通文件
l
symbolic link / soft link
链接文件(软链接)
s
socket
套接字文件(网络编程)
p
pipe
管道文件
2> 在内核空间创建出一个管道通信,一个进程可以将数据写入管道,经由管道缓冲到另一个进程中读取
3> 无名管道:顾名思义就是没有名字的管道 ,会在内存中创建出该管道,不存在于文件系统,随着进程结束而消失
4> 无名管道仅适用于亲缘进程间 通信,不适用于非亲缘进程间通信
5> 无名管道的API
1 2 3 4 5 6 #include <unistd.h> int pipe (int fildes[2 ]) ;功能:创建一个无名管道,并返回该管道的两个文件描述符 参数:是一个整型数组,用于返回打开的管道的两端的文件描述符 fildes[0 ]表示读端,fildes[1 ]表示写端 返回值:成功返回0 ,失败返回-1 并置位错误码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <myhead.h> int main (int argc, const char *argv[]) { int fildes[2 ]; if (pipe (fildes) == -1 ) { perror ("pipe error" ); return -1 ; printf ("fildes[0] = %d, fildes[1] = %d\n" , fildes[0 ], fildes[1 ]); pid_t pid = fork(); if (pid > 0 ) { close (fildes[0 ]); char wbuf[128 ] = "hello world" ; write (fildes[1 ], wbuf, strlen (wbuf)); close (fildes[1 ]); wait (NULL ); }else if (pid == 0 ) { close (fildes[1 ]); char rbuf[128 ] = "" ; read (fildes[0 ], rbuf, sizeof (rbuf)); printf ("收到父进程的数据为:%s\n" , rbuf); close (fildes[0 ]); exit (EXIT_SUCCESS); }else { perror ("fork error" ); return -1 ; } return 0 ; }
6> 管道通信特点
管道可以实现自己给自己发消息
对管道中数据的操作是一次性的,当管道中的数据被读取后,就从管道中消失了,再读取时会被阻塞
管道文件的大小:64K
由于返回的是管道文件的文件描述符,所以对管道的操作只能是文件IO相关函数,但是,不可以使用lseek对光标进行偏移,必须做到先进先出
管道的读写特点: 当读端存在时:写端有多少写多少,直到写满64k后,在write处阻塞 当读端不存在时:写端再向管道中写入数据时,会发生管道破裂 ,内核空间会向用户空间发射一个SIGPIPE 信号,进程收到该信号后,会直接退出 当写端存在时:读端有多少读多少,没有数据,会在read出阻塞 当写端不存在时:读端有多少读多少,没有数据,不会在read处阻塞了,也不修改原变量内容
管道通信是半双工通信方式 单工:只能进程A向B发送消息 半双工:同一时刻只能A向B发消息 全双工:任意时刻,AB可以互相通信
验证自己跟自己通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <myhead.h> int main (int argc, const char *argv[]) { int fildes[2 ]; if (pipe (fildes) == -1 ) { perror ("pipe error" ); return -1 ; } printf ("fildes[0] = %d, fildes[1] = %d\n" , fildes[0 ], fildes[1 ]); char wbuf[128 ] = "ni hao xingqiu" ; char rbuf[128 ] = "" ; write (fildes[1 ], wbuf, strlen (wbuf)); read (fildes[0 ], rbuf, sizeof (rbuf)); printf ("rbuf = %s\n" , rbuf); close (fildes[0 ]); close (fildes[1 ]); return 0 ; }
对管道文件大小的验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <myhead.h> int main (int argc, const char *argv[]) { int fildes[2 ]; if (pipe (fildes) == -1 ) { perror ("pipe error" ); return -1 ; } printf ("fildes[0] = %d, fildes[1] = %d\n" , fildes[0 ], fildes[1 ]); char buf = 'A' ; int count = 0 ; while (1 ) { write (fildes[1 ], &buf, 1 ); count++; printf ("count = %d\n" , count); } close (fildes[0 ]); close (fildes[1 ]); return 0 ; }
有名管道 1> 顾名思义就是有名字的管道文件,会在文件系统中创建一个真实存在的管道文件
2> 既可以完成亲缘进程间通信,也可以完成非亲缘进程间通信
3> 有名管道的API
原型:
1 2 3 4 5 6 7 8 #include <sys/types.h> #include <sys/stat.h> int mkfifo (const char *pathname, mode_t mode) ;功能:创建一个管道文件,并存在于文件系统中 参数1 :管道文件的名称 参数2 :管道文件的权限,内容详见open函数的mode参数 返回值:成功返回0 ,失败返回-1 并置位错误码 注意:管道文件被创建后,其他进程就可以进行打开读写操作了,但是,必须要保证当前管道文件的两端都打开后,才能进行读写操作,否则函数会在open处阻塞
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <myhead.h> int main (int argc, const char *argv[]) { if (mkfifo ("./myfifo" , 0664 ) == -1 ) { perror ("mkfifo error" ); return -1 ; } printf ("管道创建成功\n" ); return 0 ; } #include <myhead.h> int main (int argc, const char *argv[]) { int sfd = -1 ; if ((sfd = open ("./myfifo" , O_WRONLY)) == -1 ) { perror ("open error" ); return -1 ; } char wbuf[128 ] = "" ; while (1 ) { printf ("请输入>>>" ); fgets (wbuf, sizeof (wbuf), stdin); wbuf[strlen (wbuf)-1 ] = 0 ; write (sfd, wbuf, strlen (wbuf)); if (strcmp (wbuf, "quit" ) == 0 ) { break ; } } close (sfd); return 0 ; } #include <myhead.h> int main (int argc, const char *argv[]) { int rfd = -1 ; if ((rfd = open ("./myfifo" , O_RDONLY)) == -1 ) { perror ("open error" ); return -1 ; } char rbuf[128 ] = "" ; while (1 ) { bzero (rbuf, sizeof (rbuf)); read (rfd, rbuf, sizeof (rbuf)); printf ("收到数据为:%s\n" , rbuf); if (strcmp (rbuf, "quit" ) == 0 ) { break ; } } close (rfd); return 0 ; }
通信练习 使用有名管道实现,两个进程之间相互通信(全双工),可以使用多进程或多线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 #include <myhead.h> int main (int argc, const char *argv[]) { if (mkfifo ("./myfifo1" , 0664 ) == -1 ) { perror ("mkfifo error" ); return -1 ; } if (mkfifo ("./myfifo2" , 0664 ) == -1 ) { perror ("mkfifo error" ); return -1 ; } printf ("管道创建成功\n" ); return 0 ; } #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = fork(); if (pid > 0 ) { int sfd = -1 ; if ((sfd = open ("./myfifo1" , O_WRONLY)) == -1 ) { perror ("open error" ); return -1 ; } char wbuf[128 ] = "" ; while (1 ) { fgets (wbuf, sizeof (wbuf), stdin); wbuf[strlen (wbuf)-1 ] = 0 ; write (sfd, wbuf, strlen (wbuf)); if (strcmp (wbuf, "quit" ) == 0 ) { break ; } } close (sfd); wait (NULL ); }else if (pid == 0 ) { int rfd = -1 ; if ((rfd = open ("./myfifo2" , O_RDONLY)) == -1 ) { perror ("open error" ); return -1 ; } char rbuf[128 ] = "" ; while (1 ) { bzero (rbuf, sizeof (rbuf)); read (rfd, rbuf, sizeof (rbuf)); printf ("收到数据为:%s\n" , rbuf); if (strcmp (rbuf, "quit" ) == 0 ) { break ; } } close (rfd); exit (EXIT_SUCCESS); }else { perror ("fork error" ); return -1 ; } return 0 ; } #include <myhead.h> int main (int argc, const char *argv[]) { pid_t pid = fork(); if (pid > 0 ) { int rfd = -1 ; if ((rfd = open ("./myfifo1" , O_RDONLY)) == -1 ) { perror ("open error" ); return -1 ; } char rbuf[128 ] = "" ; while (1 ) { bzero (rbuf, sizeof (rbuf)); read (rfd, rbuf, sizeof (rbuf)); printf ("收到数据为:%s\n" , rbuf); if (strcmp (rbuf, "quit" ) == 0 ) { break ; } } close (rfd); wait (NULL ); }else if (pid == 0 ) { int sfd = -1 ; if ((sfd = open ("./myfifo2" , O_WRONLY)) == -1 ) { perror ("open error" ); return -1 ; } char wbuf[128 ] = "" ; while (1 ) { fgets (wbuf, sizeof (wbuf), stdin); wbuf[strlen (wbuf)-1 ] = 0 ; write (sfd, wbuf, strlen (wbuf)); if (strcmp (wbuf, "quit" ) == 0 ) { break ; } } close (sfd); exit (EXIT_SUCCESS); }else { perror ("fork error" ); return -1 ; } return 0 ; }
信号 概念
信号是软件模拟硬件的中断功能 ,信号是软件实现的,中断是硬件实现的
中断:停止当前正在执行的事情,去做另一件事情
信号是linux内核实现的,没有内核就没有信号的概念
用户 可以给进程发信号:例如键入ctrl+c
内核 可以向进程发送信号:例如SIGPIPE
一个进程 可以给另一个进程发送信号,需要通过相关函数来完成
信号通信是属于异步通信工作
同步:表示多个任务有先后顺序的执行,例如去银行办理业务
异步:表示多个任务没有先后顺序执行,例如你在敲代码,你妈妈在做饭
通信原理图
信号的种类及功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX 1、一共可以发射62个信号,前32个是稳定信号,后面是不稳定信号 2、常用的信号 SIGHUP:当进程所在的终端被关闭后,终端会给运行在当前终端的每个进程发送该信号,默认结束进程 SIGINT:中断信号,当用户键入ctrl + c时发射出来 SIGQUIT:退出信号,当用户键入ctrl + /是发送,退出进程 SIGKILL:杀死指定的进程 SIGSEGV:当指针出现越界访问时,会发射,表示段错误 SIGPIPE:当管道破裂时会发送该信号 SIGALRM:当定时器超时后,会发送该信号 SIGSTOP:暂停进程,当用户键入ctrl+z时发射 SIGTSTP:也是暂停进程 SIGTSTP、SIGUSR2 :留给用户自定义的信号,没有默认操作 SIGCHLD:当子进程退出后,会向父进程发送该信号 3、有两个特殊信号:SIGKILL和SIGSTOP,这两个信号既不能被捕获,也不能被忽略
对应信号的处理方式有三种:捕获、忽略、默认
对信号的处理函数:signal 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <signal.h> typedef void (*sighandler_t ) (int ) ;sighandler_t signal (int signum, sighandler_t handler) ;功能:将信号与信号处理方式绑定到一起 参数1 :要处理的信号 参数2 :处理方式 SIG_IGN:忽略 SIG_DFL:默认,一般信号的默认操作都是杀死进程 typedef void (*sighandler_t ) (int ) :用户自定义的函数 返回值:成功返回处理方式的起始地址,失败返回SIG_ERR并置位错误码 注意:只要程序与信号绑定一次,后续但凡程序收到该信号,对应的处理方式就会立即响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <myhead.h> void handler (int signo) { if (signo == SIGINT) { printf ("用户键入了ctrl + c\n" ); } } int main (int argc, const char *argv[]) { if (signal (SIGINT, SIG_DFL) == SIG_ERR) { perror ("signal error" ); return -1 ; } while (1 ) { printf ("我真的还想再活五百年\n" ); sleep (1 ); } return 0 ; }
尝试捕获和忽略SIGKILL信号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <myhead.h> void handler (int signo) { if (signo == SIGINT) { printf ("用户键入了ctrl + c\n" ); } } int main (int argc, const char *argv[]) { while (1 ) { printf ("我真的还想再活五百年\n" ); sleep (1 ); } return 0 ; }
使用信号的方式完成对僵尸进程的回收 当子进程退出后,会向父进程发送一个SIGCHLD的信号,当父进程收到该信号后,可以将其进行捕获,在信号处理函数中,可以以非阻塞的方式回收僵尸进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <myhead.h> void handler (int signo) { if (signo == SIGCHLD) { while (waitpid (-1 , NULL , WNOHANG) > 0 ); } } int main (int argc, const char *argv[]) { if (signal (SIGCHLD, handler) == SIG_ERR) { perror ("signal error" ); return -1 ; } for (int i=0 ; i<10 ; i++) { if (fork() == 0 ) { exit (EXIT_SUCCESS); } } while (1 ); return 0 ; }
信号发送函数:kill、raise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <signal.h> int kill (pid_t pid, int sig) ;功能:向指定进程或进程组发送信号 参数1 :进程号或进程组号 >0 :表示向执行进程发送信号 =0 :向当前进程所在的进程组中的所有进程发送信号 =-1 :向所有进程发送信号 <-1 :向指定进程组发送信号,进程组的ID号为给定pid的绝对值 参数2 :要发送的信号 返回值:成功返回0 ,失败返回-1 并置位错误码 #include <signal.h> int raise (int sig);功能:向自己发送信号 等价于:kill (getpid (), sig); 参数:要发送的信号 返回值:成功返回0 ,失败返回非0 数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <myhead.h> void handler (int signo) { if (signo == SIGUSR1) { printf ("逆子,何至于此!!!\n" ); raise (SIGKILL); } } int main (int argc, const char *argv[]) { if (signal (SIGUSR1, handler) == SIG_ERR) { perror ("signal error" ); return -1 ; } pid_t pid = fork(); if (pid > 0 ) { while (1 ) { printf ("我真的还想再活五百年\n" ); sleep (1 ); } }else if (pid == 0 ) { sleep (5 ); printf ("红尘已经看破,叫上父亲一起死吧\n" ); kill (getppid (), SIGUSR1); exit (EXIT_SUCCESS); } return 0 ; }
总结:信号可以完成多个进程间通知作用 ,但是,不能进行数据传输功能
system V提供的进程间通信 1> 对于内核提供的三种通信方式,对于管道 而言,只能实现单向的数据通信,对于信号通信 而言,只能完成多进程之间消息的通知,不能起到数据传输的效果。为了解决上述问题,引入的系统 V进程间通信 (V是阿拉伯数字5)
2> system V提供的进程间通信方式分别是:消息队列、共享内存、信号量(信号灯集)
3> 有关system V进程间通信对象相关的指令
1 2 3 4 5 ipcs 可以查看所有的信息(消息队列、共享内存、信号量) ipcs -q:可以查看消息队列的信息 ipcs -m:可以查看共享内存的信息 ipcs -s:可以查看信号量的信息 ipcrm -q/m/s ID :可以删除指定ID的IPC对象
4> 上述的三种通信方式,也是借助内核空间 完成的相关通信,原理是在内核空间创建出相关的对象容器 ,在进行进程间通信时,可以将信息放入对象中,另一个进程就可以从该容器中取数据了。
5> 与内核提供的管道、信号通信不同:system V的ipc对象实现了数据传递的容器与程序相分离,也就是说,即使程序以己经结束,但是放入到容器中的数据依然存在,除非将容器手动删除
消息队列 实现原理
message消息队列API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 1 、创建key值 #include <sys/types.h> #include <sys/ipc.h> key_t ftok (const char *pathname, int proj_id) ; 功能:通过给定的文件以及给定的一个随机值,创建出一个4 字节整数的key值,用于system V IPC对象的创建 参数1 :一个文件路径,要求是已经存在的文件路径,提供了key值3 字节的内容,其中,文件的设备号占1 字节,文件的inode号(文件系统层面的唯一标识)占2 字节 参数2 :一个随机整数,取后8 位(1 字节,可以用char 字符代替)跟前面的文件共同组成key值,必须是非0 的数字。作用是防止重复及防破解 返回值:成功返回key值,失败返回-1 并置位错误码 2 、通过key值,创建消息队列 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget (key_t key, int msgflg) ; 功能:通过给定的key值,创建出一个消息队列的对象,并返回消息队列的句柄ID,后期可以通过该ID操作整个消息队列 参数1 :key值,该值可以是IPC_PRIVATE,也可以是ftok创建出来的,前者只用于亲缘进程间的通信 参数2 :创建标识 IPC_CREAT:创建并打开一个消息队列,如果消息队列已经存在,则直接打开 IPC_EXCL:确保本次创建处理的是一个新的消息队列,如果消息队列已经存在,则报错,错误码位EEXIST 0664 :该消息队列的操作权限 eg: IPC_CREAT|0664 或者 IPC_CREAT|IPC_EXCL|0664 返回值:成功返回消息队列的ID号,失败返回-1 并置位错误码 3 、向消息队列中存放数据 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd (int msqid, const void *msgp, size_t msgsz, int msgflg) ; 功能:向消息队列中存放一个指定格式的消息 参数1 :打开的消息队列的id号 参数2 :要发送的消息的起始地址,消息一般定义为一个结构体类型,由用户手动定义 struct msgbuf { long mtype; 消息的类型 char mtext[1 ]; 消息正文 。。。可以自行加其他内容 }; 参数3 :消息正文的大小 参数4 :是否阻塞的标识 0 :标识阻塞形式向消息队列中存放消息,如果消息队列满了,就在该函数处阻塞 IPC_NOWAIT:标识非阻塞的形式向消息队列中存放消息,如果消息队列满了,直接返回 返回值:成功返回0 ,失败返回-1 并置位错误码 4 、从消息队列中取消息 ssize_t msgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg) ; 功能:从消息队列中取数指定类型的消息放入给定的容器中 参数1 :打开的消息队列的id号 参数2 :要接收的消息的起始地址,消息一般定义为一个结构体类型,由用户手动定义 struct msgbuf { long mtype; 消息的类型 char mtext[1 ]; 消息正文 。。。 }; 参数3 :消息正文的大小 参数4 :要接收的消息类型 0 :表示每次都取消息队列中的第一个消息,无论类型 >0 :读取队列中第一个类型为msgtyp的消息 <0 :读取队列中的一个消息,消息为绝对值小于msgtyp的第一个消息 eg: 10 -->8 -->3 -->6 -->5 -->20 -->2 -5 : 会从队列中绝对值小于5 的类型的消息中选取第一个消息,就是3 参数5 :是否阻塞的标识 0 :标识阻塞形式向消息队列中读取消息,如果消息队列空了,就在该函数处阻塞 IPC_NOWAIT:标识非阻塞的形式向消息队列中读取消息,如果消息队列空了,直接返回 返回值:成功返回实际读取的正文大小,失败返回-1 并置位错误码 5 、销毁消息队列 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl (int msqid, int cmd, struct msqid_ds *buf) ; 功能:对给定的消息队列执行相关的操作,该操作由cmd参数而定 参数1 :消息队列的ID号 参数2 :要执行的操作 IPC_RMID:删除一个消息队列,当cmd为该值时,第三个参数可以省略填NULL 即可 IPC_STAT:表示获取当前消息队列的属性,此时第三个参数就是存放获取的消息队列属性的容器起始地址 IPC_SET:设置当前消息队列的属性,此时第三个参数就是要设置消息队列的属性数据的起始地址 参数3 :消息队列数据容器结构体,如果第二个参数为IPC_RMID,则该参数忽略填NULL 即可,如果是 IPC_STAT、 IPC_SET填如下结构体: struct msqid_ds { struct ipc_perm msg_perm; 消息队列的拥有者和权限 time_t msg_stime; 最后一次发送消息的时间 time_t msg_rtime; 最后一次接收消息的时间 time_t msg_ctime; 最后一次状态改变的时间 unsigned long __msg_cbytes; 已用字节数 msgqnum_t msg_qnum; 消息队列中消息个数 msglen_t msg_qbytes; 最大消息个数 pid_t msg_lspid; 最后一次发送消息的进程pid pid_t msg_lrpid; 最后一次读取消息的进程pid }; 该结构体的第一个成员类型如下: struct ipc_perm { key_t __key; key值 uid_t uid; 当前进程的uid gid_t gid; 当前进程的组ID uid_t cuid; 消息队列创建者的用户id gid_t cgid; 消息队列创建者的组id unsigned short mode; 消息队列的权限 unsigned short __seq; 队列号 }; 返回值:成功返回0 ,失败返回-1 并置位错误码
发送端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <myhead.h> struct msgBuf { long mtype; char mtext[1024 ]; }; #define MSGSZ (sizeof(struct msgBuf)-sizeof(long)) int main (int argc, const char *argv[]) { key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } printf ("key = %#x\n" , key); int msqid = -1 ; if ((msqid = msgget (key, IPC_CREAT|0664 )) == -1 ) { perror ("msgget error" ); return -1 ; } printf ("msgqid = %d\n" , msqid); struct msgBuf buf; while (1 ) { printf ("请输入消息的类型:" ); scanf ("%ld" , &buf.mtype); getchar (); printf ("请输入消息正文:" ); fgets (buf.mtext, MSGSZ, stdin); buf.mtext[strlen (buf.mtext)-1 ] = '\0' ; msgsnd (msqid, &buf, MSGSZ, 0 ); printf ("消息存入成功\n" ); if (strcmp (buf.mtext, "quit" ) == 0 ) { break ; } } return 0 ; }
接收端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <myhead.h> struct msgBuf {long mtype; char mtext[1024 ]; }; #define MSGSZ (sizeof(struct msgBuf)-sizeof(long)) int main (int argc, const char *argv[]) { key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } printf ("key = %#x\n" , key); int msqid = -1 ; if ((msqid = msgget (key, IPC_CREAT|0664 )) == -1 ) { perror ("msgget error" ); return -1 ; } printf ("msgqid = %d\n" , msqid); struct msgBuf buf; while (1 ) { bzero (&buf, sizeof (buf)); msgrcv (msqid, &buf, MSGSZ, 1 , 0 ); printf ("读取到的消息为:%s\n" , buf.mtext); if (strcmp (buf.mtext,"quit" ) == 0 ) { break ; } } if (msgctl (msqid, IPC_RMID, NULL ) == -1 ) { perror ("msgctl error" ); return -1 ; } return 0 ; }
注意事项:
1、对于消息而言,由两部分组成:消息的类型和消息正文,消息结构体由用户自定义
2、对于消息队列而言,任意一个进程都可以向消息队列中发送消息,也可以从消息队列中取消息
3、多个进程,使用相同的key值打开的是同一个消息队列
4、对消息队列中的消息读取操作是一次性的 ,被读取后,消息队列中不存在该消息了
5、消息队列的大小:16K7
shared memory共享内存 原理图 :
共享内存的API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 1 、创建key值 #include <sys/types.h> #include <sys/ipc.h> key_t ftok (const char *pathname, int proj_id) ; 功能:通过给定的文件以及给定的一个随机值,创建出一个4 字节整数的key值,用于system V IPC对象的创建 参数1 :一个文件路径,要求是已经存在的文件路径,提供了key值3 字节的内容,其中,文件的设备号占1 字节,文件的inode号占2 字节 参数2 :一个随机整数,取后8 位(1 字节)跟前面的文件共同组成key值,必须是非0 的数字 返回值:成功返回key值,失败返回-1 并置位错误码 2 、通过key值创建共享内存段 #include <sys/ipc.h> #include <sys/shm.h> int shmget (key_t key, size_t size, int shmflg) ; 功能:申请指定大小的物理内存,映射到内核空间,创建出共享内存段 参数1 :key值,可以是IPC_PRIVATE,也可以是ftok创建出来的key值 参数2 :申请的大小,是一页(4096 字节)的整数倍,并且向上取整 参数3 :创建标识 IPC_CREAT:创建并打开一个共享内存,如果共享内存已经存在,则直接打开 IPC_EXCL:确保本次创建处理的是一个新的共享内存,如果共享内存已经存在,则报错,错误码位EEXIST 0664 :该共享内存的操作权限 eg: IPC_CREAT|0664 或者 IPC_CREAT|IPC_EXCL|0664 返回值:成功返回共享内存段的id,失败返回-1 并置位错误码 3 、将共享内存段的地址映射到用户空间 #include <sys/types.h> #include <sys/shm.h> void *shmat (int shmid, const void *shmaddr, int shmflg) ; 功能:将共享内存段映射到用户空间 参数1 :共享内存的id号 参数2 :物理内存的起始地址,一般填NULL ,由系统自动选择一个合适的对齐页 参数3 :对共享内存段的操作 0 :表示读写操作 SHM_RDONLY:只读 返回值:成功返回用于操作共享内存的指针,失败返回(void *)-1 并置位错误码 4 、释放共享内存的映射关系 int shmdt (const void *shmaddr) ; 功能:将进程与共享内存的映射取消 参数:共享内存的指针 返回值:成功返回0 ,失败返回-1 并置位错误码 5 、共享内存的控制函数 #include <sys/ipc.h> #include <sys/shm.h> int shmctl (int shmid, int cmd, struct shmid_ds *buf) ; 功能:根据给定的不同的cmd执行不同的操作 参数1 :共享内存的ID 参数2 :要操作的指令 IPC_RMID:删除共享内存段,第三个参数可以省略 IPC_STAT:获取当前共享内存的属性 IPC_SET:设置当前共享内存的属性 参数3 :如果参数2 为IPC_RMID,则参数3 可以省略填NULL ,如果参数2 为另外两个,参数3 填如下结构体变量 struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; pid_t shm_cpid; pid_t shm_lpid; shmatt_t shm_nattch; ... }; 该结构体的第一个成员结构体: struct ipc_perm { key_t __key; uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; unsigned short mode; unsigned short __seq; }; 返回值:成功返回0 ,失败饭hi-1 并置位错误码
发送端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <myhead.h> #define PAGE_SIZE 4096 int main (int argc, const char *argv[]) { key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } printf ("key = %#x\n" , key); int shmid = -1 ; if ((shmid = shmget (key, PAGE_SIZE, IPC_CREAT | 0664 )) == -1 ) { perror ("shmget error" ); return -1 ; } printf ("shmid = %d\n" , shmid); char *addr = (char *)shmat (shmid, NULL , 0 ); if (addr == (void *)-1 ) { perror ("shmat error" ); return -1 ; } printf ("addr = %p\n" , addr); while (1 ) { printf ("请输入>>>" ); fgets (addr, PAGE_SIZE, stdin); addr[strlen (addr) - 1 ] = 0 ; if (strcmp (addr, "quit" ) == 0 ) { break ; } } sleep (5 ); printf ("结束吧\n" ); if (shmdt (addr) == -1 ) { perror ("取消映射\n" ); return -1 ; } return 0 ; }
接收端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <myhead.h> #define PAGE_SIZE 4096 int main (int argc, const char *argv[]) { key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } printf ("key = %#x\n" , key); int shmid = -1 ; if ((shmid = shmget (key, PAGE_SIZE, IPC_CREAT | 0664 )) == -1 ) { perror ("shmget error" ); return -1 ; } printf ("shmid = %d\n" , shmid); char *addr = (char *)shmat (shmid, NULL , 0 ); if (addr == (void *)-1 ) { perror ("shmat error" ); return -1 ; } printf ("addr = %p\n" , addr); while (1 ) { sleep (2 ); printf ("读取到消息为:%s\n" , addr); if (strcmp (addr, "quit" ) == 0 ) { break ; } } if (shmdt (addr) == -1 ) { perror ("取消映射\n" ); return -1 ; } if (shmctl (shmid, IPC_RMID, NULL ) == -1 ) { perror ("shmctl error" ); return -1 ; } return 0 ; }
注意:
共享内存是多个进程共享同一个内存空间,使用时可能会产生竞态,为了解决这个问题,共享内存一般会跟信号量 一起使用,完成进程的同步 功能
共享内存VS消息队列:消息队列能够保证数据的不丢失性,而共享内存能够保证数据的时效性
对共享内存的读取操作不是一次性 的,当读取后,数据依然存放在共享内存中
使用共享内存,跟正常使用指针是一样的,使用时,无需再进行用户空间与内核空间的切换了,所以说,共享内存是所有进程间通信方式中效率最高 的一种通信方式。
练习:使用消息队列完成两个进程间相互通信
test1.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include <myhead.h> struct msgBuf { long mtype; char mtext[1024 ]; }; #define MSGSZ (sizeof(struct msgBuf) - sizeof(long)) int main (int argc, const char *argv[]) { key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } printf ("key = %#x\n" , key); int msqid = -1 ; if ((msqid = msgget (key, IPC_CREAT | 0664 )) == -1 ) { perror ("msgget error" ); return -1 ; } printf ("msgqid = %d\n" , msqid); pid_t pid = fork(); if (pid > 0 ) { struct msgBuf buf = {.mtype = 1 }; while (1 ) { fgets (buf.mtext, MSGSZ, stdin); buf.mtext[strlen (buf.mtext) - 1 ] = '\0' ; msgsnd (msqid, &buf, MSGSZ, 0 ); if (strcmp (buf.mtext, "quit" ) == 0 ) { break ; } } } else if (pid == 0 ) { struct msgBuf buf; while (1 ) { bzero (&buf, sizeof (buf)); msgrcv (msqid, &buf, MSGSZ, 2 , 0 ); printf ("读取到的消息为:%s\n" , buf.mtext); if (strcmp (buf.mtext, "quit" ) == 0 ) { break ; } } } else { perror ("fork error" ); return -1 ; } return 0 ; }
test2.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <myhead.h> struct msgBuf { long mtype; char mtext[1024 ]; }; #define MSGSZ (sizeof(struct msgBuf) - sizeof(long)) int main (int argc, const char *argv[]) { key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } printf ("key = %#x\n" , key); int msqid = -1 ; if ((msqid = msgget (key, IPC_CREAT | 0664 )) == -1 ) { perror ("msgget error" ); return -1 ; } printf ("msgqid = %d\n" , msqid); pid_t pid = fork(); if (pid > 0 ) { struct msgBuf buf; while (1 ) { bzero (&buf, sizeof (buf)); msgrcv (msqid, &buf, MSGSZ, 1 , 0 ); printf ("读取到的消息为:%s\n" , buf.mtext); if (strcmp (buf.mtext, "quit" ) == 0 ) { break ; } } } else if (pid == 0 ) { struct msgBuf buf = {.mtype = 2 }; while (1 ) { fgets (buf.mtext, MSGSZ, stdin); buf.mtext[strlen (buf.mtext) - 1 ] = '\0' ; msgsnd (msqid, &buf, MSGSZ, 0 ); if (strcmp (buf.mtext, "quit" ) == 0 ) { break ; } } } else { perror ("fork error" ); return -1 ; } return 0 ; }
semaphore信号量(信号灯集)
信号量相关API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 1 、创建key值 #include <sys/types.h> #include <sys/ipc.h> key_t ftok (const char *pathname, int proj_id) ; 功能:通过给定的文件以及给定的一个随机值,创建出一个4 字节整数的key值,用于system V IPC对象的创建 参数1 :一个文件路径,要求是已经存在的文件路径,提供了key值3 字节的内容,其中,文件的设备号占1 字节,文件的inode号占2 字节 参数2 :一个随机整数,取后8 位(1 字节)跟前面的文件共同组成key值,必须是非0 的数字 返回值:成功返回key值,失败返回-1 并置位错误码 2 、通过key值创建信号量集 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget (key_t key, int nsems, int semflg) ; 功能:通过给定的key值创建一个信号量集 参数1 ::key值,该值可以是IPC_PRIVATE,也可以是ftok创建出来的,前者只用于亲缘进程间的通信 参数2 :信号量数组中信号量的个数 参数3 :创建标识 IPC_CREAT:创建并打开一个信号量集,如果信号量集已经存在,则直接打开 IPC_EXCL:确保本次创建处理的是一个新的信号量集,如果信号量集已经存在,则报错,错误码置位EEXIST 0664 :该信号量集的操作权限 eg: IPC_CREAT|0664 或者 IPC_CREAT|IPC_EXCL|0664 返回值:成功返回信号量集的id,失败返回-1 并置位错误码 3 、关于信号量集的操作:P(申请资源)V(释放资源) #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop (int semid, struct sembuf *sops, size_t nsops) ; 功能:完成对信号量数组的操作 参数1 :信号量数据ID号 参数2 :有关信号量操作的结构体变量起始地址,该结构体中包含了操作的信号量编号和申请还是释放的操作 struct sembuf { unsigned short sem_num; 要操作的信号量的编号 short sem_op; 要进行的操作,大于0 表示释放资源,小于0 表示申请资源,可以理解为现有信号量value对sem_op进行加法操作 short sem_flg; 操作标识位,0 标识阻塞方式,IPC_NOWAIT表示非阻塞 } 参数3 :本次操作的信号量的个数 返回值:成功返回0 ,失败返回-1 并置位错误码 4 、关于信号量集的控制函数 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl (int semid, int semnum, int cmd, ...) ; 功能:执行有关信号量集的控制函数,具体控制内容取决于cmd 参数1 :信号量集的ID 参数2 :要操作的信号量的编号,编号是从0 开始 参数3 :要执行的操作 IPC_RMID:表示删除信号量集,cmd为该值时,参数2 可以忽略,参数4 可以不填 SETVAL:表示对参数2 对应的信号量进行设置操作(初始值) GETVAL:表示对参数2 对应的信号量进行获取值操作 SETALL:设置信号量集中所有信号量的值 GETALL:获取信号量集中的所有信号量的值 IPC_STAT:表示获取当前信号量集的属性 IPC_SET:表示设置当前信号量集的属性 参数4 :根据不同的cmd值,填写不同的参数值,所以该处是一个共用体变量 union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; 返回值:成功时:SETVAL、IPC_RMID返回0 ,GETVAL返回当前信号量的值,失败返回-1 并置位错误码 例如: 1 ) 给0 号信号量设置初始值为1 union semun us; us.val = 1 ; semctl (semid, 0 , SETVAL, us); 2 ) 删除信号量集 semctl (semid, 0 , IPC_RMID);
二次封装 将上述函数进行二次封装,封装成为只有信号量集的创建、申请资源、释放资源、销毁信号量集
sem.h
1 2 3 4 5 6 7 8 9 10 11 #ifndef _SEM_H_ #define _SEM_H_ int create_sem (int semcount) ;int P (int semid, int semno) ;int V (int semid, int semno) ;int delete_sem (int semid) ;#endif
sem.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include <myhead.h> union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; int init_sem (int semid, int semno) { int val = -1 ; printf ("请输入第%d个信号量的初始值:" , semno + 1 ); scanf ("%d" , &val); getchar (); union semun us; us.val = val; if (semctl (semid, semno, SETVAL, us) == -1 ) { perror ("semctl error" ); return -1 ; } return 0 ; } int create_sem (int semcount) { key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } int semid = -1 ; if ((semid = semget (key, semcount, IPC_CREAT | IPC_EXCL | 0664 )) == -1 ) { if (errno == EEXIST) { semid = semget (key, semcount, IPC_CREAT | 0664 ); return semid; } perror ("semget error" ); return -1 ; } 操作了 for (int i = 0 ; i < semcount; i++) { init_sem (semid, i); } return semid; } int P (int semid, int semno) { struct sembuf buf; buf.sem_num = semno; buf.sem_op = -1 ; buf.sem_flg = 0 ; if (semop (semid, &buf, 1 ) == -1 ) { perror ("P error" ); return -1 ; } return 0 ; } int V (int semid, int semno) { struct sembuf buf; buf.sem_num = semno; buf.sem_op = 1 ; buf.sem_flg = 0 ; if (semop (semid, &buf, 1 ) == -1 ) { perror ("V error" ); return -1 ; } return 0 ; } int delete_sem (int semid) { if (semctl (semid, 0 , IPC_RMID) == -1 ) { perror ("delete error" ); return -1 ; } return 0 ; }
解决同步问题 使用信号量集完成共享内存中两个进程对共享内存使用的同步问题
shmsnd.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <myhead.h> #include "sem.h" #define PAGE_SIZE 4096 int main (int argc, const char *argv[]) { int semid = create_sem (2 ); key_t key = ftok ("/" , 'k' ); if (key == -1 ) { perror ("ftok error" ); return -1 ; } printf ("key = %#x\n" , key); int shmid = -1 ; if ((shmid = shmget (key, PAGE_SIZE, IPC_CREAT | 0664 )) == -1 ) { perror ("shmget error" ); return -1 ; } printf ("shmid = %d\n" , shmid); char *addr = (char *)shmat (shmid, NULL , 0 ); if (addr == (void *)-1 ) { perror ("shmat error" ); return -1 ; } printf ("addr = %p\n" , addr); while (1 ) { P (semid, 0 ); printf ("请输入>>>" ); fgets (addr, PAGE_SIZE, stdin); addr[strlen (addr) - 1 ] = 0 ; V (semid, 1 ); if (strcmp (addr, "quit" ) == 0 ) { break ; } } if (shmdt (addr) == -1 ) { perror ("取消映射\n" ); return -1 ; } delete_sem (semid); return 0 ; }
注意:
信号量集是完成多个进程间同步问题的,一般不进行信息的通信
信号量集的使用,本质上是对多个value值进行管控,每个信号量控制一个进程 ,在进程执行前,申请一个信号量的资源,执行后,释放另一个信号量的资源
如果当前进程申请的信号量值为0,则当前进程在申请处阻塞,直到其他进程将该信号量中的资源增加到大于0
练习:进程1输出字符A,进程2输出字符B,进程3输出啊字符C,使用信号量集完成,最终输出的结果为ABCABCABCABC…
A.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <myhead.h> #include "sem.h" int main (int argc, const char *argv[]) { int semid = create_sem (3 ); while (1 ) { P (semid, 0 ); printf ("A" ); fflush (stdout); sleep (1 ); V (semid, 1 ); } return 0 ; }
B.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <myhead.h> #include "sem.h" int main (int argc, const char *argv[]) { int semid = create_sem (3 ); while (1 ) { P (semid, 1 ); printf ("B" ); fflush (stdout); sleep (1 ); V (semid, 2 ); } return 0 ; }
C.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <myhead.h> #include "sem.h" int main (int argc, const char *argv[]) { int semid = create_sem (3 ); while (1 ) { P (semid, 2 ); printf ("C" ); fflush (stdout); sleep (1 ); V (semid, 0 ); } return 0 ; }