进程 信号 通信 消息队列 共享内存 进程间通信 等读书笔记及个人小练习
一、基本概念
1、进程与程序
程序是存储在磁盘上的文件,它是包含要执行的机器指令和数据的静态实体。
进程是一个正在运行的程序,一个程序可能包含多个进程(多任务、多进程),进程在操作系统中是一个执行任务的单位。
2、进程的分类
交互进程:需要用户输入数据,也会显示一些结果给用户看。
批处理进程:用来执行脚本的进程,例如Makefil。
守护进程:它是一种一直活跃的进程,一般是后台的,由操作系统启动时通过开过开机脚本加载或由超级用户加载。
3、查看进程
ps:显示当用户当前终端所控制的进程。
-a:显示所有用户的进程
-x:包括无终端控制的进程
-u:显示详细信息
-w:以更宽的方式显示
4、进程信息表
USER:属主
PID:进程号
%CPU:cpu占用率
%MEM:内存使用率
VSZ:虚拟内存的大小
RSS:物理内存的使用量
TTY:终端设备号,如果不是终端控制进程用'?'表示。
STAT:终端的状态
START:开始时间
TIME:运行时间
COMMAND:开启此进程的命令
5、进程的状态
O:就绪态,一切准备工作都已经做好,等待被调用。
R:运行态,Linux下没有就绪态,O也就是R。
S:可唤醒的睡眠态,系统调用、获取到资源、收到信息都可以被唤醒。
D:不可唤醒的睡眠态,必须等到的事件发生。
T:暂停态,收到了SIGSTOP信号,当收到SIGCONT信号则继续运行。
X:死亡态。
Z:僵尸态。
<:高优先级。
N:低优先级。
L:多线程进程。
s:有子进程的进程。
+:后台进程组。
6、父子进程
如果进程B是由进程A开启的,那么我们把进程A叫进程B的父进程,进程B叫作进程A的子进程。
当子进程结束后会向父进程发送,SIGCHLD,父进程收到信号后再支回收子进程。
当先父进程先结束,子进程就会变成孤儿进程,会被孤儿院(init)收养。
如果子进程已经结束,但父进程没有及时回收,子进程就变成了僵局进程。
7、进程标识符,
俗称进程号,它是一个无符号整数,使用getpid函数可以获取到。
这种编号是循环使用,当进程结束后,它就跟这个编号没有关系,这个编号也会被再次使用。
延时重用。
二、getxxid
进程在运行时会根据实际的调用都来获取它的权限,这样可以让程序比实际的拥有者还要高的权限。
三、fork
pid_t fork(void);
功能:创建子进程
1、失败返回-1,如果成功会返回两次。
2、父进程会返回子进程的id,子进程返回0
3、根据返回值的不同,分别为子进程和父进程设计不同的分支。
4、通过fork创建出的子进程,就是父进程的副本,它会把父进程的堆、栈、全局段、静态数据段、IO流的缓冲区都拷贝一份,父子进程共享代码段。
5、fork函数调用成功后,父子进程就开始各自执行了,它们的先后顺序是不确定的,但可以通过某些实现来保证。
练习1:编写一个程序,让此程序拥有5个进程同时执行。
练习2:编写一个程序,让此程序有三级子进程。
6、当总进程数超过系统的限制时,就无法再创建进程的了,此时fork函数会返回-1,执行失败。
7、孤儿进程:子进程还有运行,但父进程已经结束,此时子进程会被init(1)收养,子进程变成孤儿进程。
8、僵尸进程:子进程已经死亡,但父进程没有及时回收子进程,此时子进程就会变成僵尸进程。
9、父进程打开的文件和子进程是共享的。
注意:fork之前的代码,只有父进程在执行,fork之后的代码父子进程都有机会执行,根据fork返回值来控制进入不同的分支。
四、vfork
pid_t vfork(void);
功能:创建子进程
1、vfork不能单独创建子进程,需要与excl函数簇,配合才完成子进程的创建。
2、它不会复制父进程的栈、堆、数据、全局等段,也不会共享代码段,而是通过excl函数调用一个程序直接启动,从面提高创建进程的效率。
3、使用vfork创建的子进程保证,先执行子进程,后执行父进程。
五、进程的正常退出
1、从main退出,在main中执行return 0;
返回值的低8位会被父进程获取到。
2、系统的_exit(stat)/标准的_Exit(stat),这两个函数几乎没有什么区别。
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
a、返回值的低8位会被父进程获取到。
b、使用_exit/_Exit退出前会关闭所有打开的文件流,如果有子进程则会托附给init,然后向父进程发送SIGCHLD信号。
c、此函数不会返回。
3、调用标准C的exit(stat)函数
a、exit在底层实现上调用了_exit/_Exit函数,所以_exit/_Exit的特点它都具备。
b、exit结束前会调用通过atexit/on_exit注册的函数。
int atexit(void (*function)(void));
int on_exit(void (*function)(int , void *), void *arg);
4、最后一个线程正常结束
六、进程异常中止
1、进程调用了abort函数(段错误、浮点异常)
2、进程接收到某些信号
crtl+c
crtl+\
ctrl+z
3、最后一个线程收到取消操作,而且线程作出响应。
七、子进程的回收
程序是存储在磁盘上的文件,它是包含要执行的机器指令和数据的静态实体。
进程是一个正在运行的程序,一个程序可能包含多个进程(多任务、多进程),进程在操作系统中是一个执行任务的单位。
2、进程的分类
交互进程:需要用户输入数据,也会显示一些结果给用户看。
批处理进程:用来执行脚本的进程,例如Makefil。
守护进程:它是一种一直活跃的进程,一般是后台的,由操作系统启动时通过开过开机脚本加载或由超级用户加载。
3、查看进程
ps:显示当用户当前终端所控制的进程。
-a:显示所有用户的进程
-x:包括无终端控制的进程
-u:显示详细信息
-w:以更宽的方式显示
4、进程信息表
USER:属主
PID:进程号
%CPU:cpu占用率
%MEM:内存使用率
VSZ:虚拟内存的大小
RSS:物理内存的使用量
TTY:终端设备号,如果不是终端控制进程用'?'表示。
STAT:终端的状态
START:开始时间
TIME:运行时间
COMMAND:开启此进程的命令
5、进程的状态
O:就绪态,一切准备工作都已经做好,等待被调用。
R:运行态,Linux下没有就绪态,O也就是R。
S:可唤醒的睡眠态,系统调用、获取到资源、收到信息都可以被唤醒。
D:不可唤醒的睡眠态,必须等到的事件发生。
T:暂停态,收到了SIGSTOP信号,当收到SIGCONT信号则继续运行。
X:死亡态。
Z:僵尸态。
<:高优先级。
N:低优先级。
L:多线程进程。
s:有子进程的进程。
+:后台进程组。
6、父子进程
如果进程B是由进程A开启的,那么我们把进程A叫进程B的父进程,进程B叫作进程A的子进程。
当子进程结束后会向父进程发送,SIGCHLD,父进程收到信号后再支回收子进程。
当先父进程先结束,子进程就会变成孤儿进程,会被孤儿院(init)收养。
如果子进程已经结束,但父进程没有及时回收,子进程就变成了僵局进程。
7、进程标识符,
俗称进程号,它是一个无符号整数,使用getpid函数可以获取到。
这种编号是循环使用,当进程结束后,它就跟这个编号没有关系,这个编号也会被再次使用。
延时重用。
二、getxxid
pid_t getpid(void); 功能:获取进程id pid_t getppid(void); 功能:获取父进程id uid_t getuid(void); 功能:获取实际调用者的用户id, uid_t geteuid(void); 功能:获取程序的拥有者用户的id gid_t getgid(void); 功能:获取实际使用者的组id, gid_t getegid(void); 功能:获取程序的拥有者用户组id 1、切换用户 2、chmod u+s a.out 3、chmod g+s a.out
进程在运行时会根据实际的调用都来获取它的权限,这样可以让程序比实际的拥有者还要高的权限。
三、fork
pid_t fork(void);
功能:创建子进程
1、失败返回-1,如果成功会返回两次。
2、父进程会返回子进程的id,子进程返回0
3、根据返回值的不同,分别为子进程和父进程设计不同的分支。
4、通过fork创建出的子进程,就是父进程的副本,它会把父进程的堆、栈、全局段、静态数据段、IO流的缓冲区都拷贝一份,父子进程共享代码段。
5、fork函数调用成功后,父子进程就开始各自执行了,它们的先后顺序是不确定的,但可以通过某些实现来保证。
练习1:编写一个程序,让此程序拥有5个进程同时执行。
练习2:编写一个程序,让此程序有三级子进程。
6、当总进程数超过系统的限制时,就无法再创建进程的了,此时fork函数会返回-1,执行失败。
7、孤儿进程:子进程还有运行,但父进程已经结束,此时子进程会被init(1)收养,子进程变成孤儿进程。
8、僵尸进程:子进程已经死亡,但父进程没有及时回收子进程,此时子进程就会变成僵尸进程。
9、父进程打开的文件和子进程是共享的。
注意:fork之前的代码,只有父进程在执行,fork之后的代码父子进程都有机会执行,根据fork返回值来控制进入不同的分支。
四、vfork
pid_t vfork(void);
功能:创建子进程
1、vfork不能单独创建子进程,需要与excl函数簇,配合才完成子进程的创建。
2、它不会复制父进程的栈、堆、数据、全局等段,也不会共享代码段,而是通过excl函数调用一个程序直接启动,从面提高创建进程的效率。
3、使用vfork创建的子进程保证,先执行子进程,后执行父进程。
五、进程的正常退出
1、从main退出,在main中执行return 0;
返回值的低8位会被父进程获取到。
2、系统的_exit(stat)/标准的_Exit(stat),这两个函数几乎没有什么区别。
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
a、返回值的低8位会被父进程获取到。
b、使用_exit/_Exit退出前会关闭所有打开的文件流,如果有子进程则会托附给init,然后向父进程发送SIGCHLD信号。
c、此函数不会返回。
3、调用标准C的exit(stat)函数
a、exit在底层实现上调用了_exit/_Exit函数,所以_exit/_Exit的特点它都具备。
b、exit结束前会调用通过atexit/on_exit注册的函数。
int atexit(void (*function)(void));
int on_exit(void (*function)(int , void *), void *arg);
4、最后一个线程正常结束
六、进程异常中止
1、进程调用了abort函数(段错误、浮点异常)
2、进程接收到某些信号
crtl+c
crtl+\
ctrl+z
3、最后一个线程收到取消操作,而且线程作出响应。
七、子进程的回收
pid_t wait(int *status);
功能:等待子进程结束,并回收
pid_t waitpid(pid_t pid, int *status,int options);
功能:等待指定的子进程结束,并回收
1、当一个进程结束时,内核会向它的父进程发送一个信号
父进程收到这个信号后可以指定一个函数来处理,也可以选择忽略,默认情况下是忽略的。
2、父进程调用wait调用才是有意义
如果所有的子进程都在运行,则父进程阻塞(wait)
只要有一个子进程结束了,会立即返回子进程的id和结束状态。
当所有子进程都结束运行时,wait会返回-1。
如果在调用wait之前子进程就已经结束(僵尸子进程),执行wait函数时会立即返回并回收僵尸进程。
3、waitpid函数可以指定等于哪个子进程
pid:指定的pid
== -1 功能与wait类似,pid就无意义了。
> 0 等待进程号是pid的进程结束。
== 0 等待组id等于pid的进程组中任意进程结束。
< -1 等待组id是pid的绝对值的进程组中任意进程结束。
status:用于接收子进程的结束状态,如果不需要状态码可以设置为NULL;
options:
0 以阻塞状态等待子进程结束
WNOHANG 如果没有子进程退出会立即返回。
WUNTRACED 等待的进程处于停止状态,并且之前没有报告过,则立即返回。
4、如果不调用wait/waitpid函数,子进程结束后就处于僵尸状态,当父进程也结束时,父进程的父进程会把他们统一回收。
八、exec函数簇
功能:等待子进程结束,并回收
pid_t waitpid(pid_t pid, int *status,int options);
功能:等待指定的子进程结束,并回收
1、当一个进程结束时,内核会向它的父进程发送一个信号
父进程收到这个信号后可以指定一个函数来处理,也可以选择忽略,默认情况下是忽略的。
2、父进程调用wait调用才是有意义
如果所有的子进程都在运行,则父进程阻塞(wait)
只要有一个子进程结束了,会立即返回子进程的id和结束状态。
当所有子进程都结束运行时,wait会返回-1。
如果在调用wait之前子进程就已经结束(僵尸子进程),执行wait函数时会立即返回并回收僵尸进程。
3、waitpid函数可以指定等于哪个子进程
pid:指定的pid
== -1 功能与wait类似,pid就无意义了。
> 0 等待进程号是pid的进程结束。
== 0 等待组id等于pid的进程组中任意进程结束。
< -1 等待组id是pid的绝对值的进程组中任意进程结束。
status:用于接收子进程的结束状态,如果不需要状态码可以设置为NULL;
options:
0 以阻塞状态等待子进程结束
WNOHANG 如果没有子进程退出会立即返回。
WUNTRACED 等待的进程处于停止状态,并且之前没有报告过,则立即返回。
4、如果不调用wait/waitpid函数,子进程结束后就处于僵尸状态,当父进程也结束时,父进程的父进程会把他们统一回收。
八、exec函数簇
exec函数的功能:加载一个可执行文件,要和vfork函数配合才有意义。
int execl(const char *path, const char
*arg, ...);
path:可执行文件的路径
arg:给可执行文件的参数,类似于命令行参数,必须以NULL结尾,第一个必须是可以执行文件名。
execl("","a.out",NULL);
1、通过exec创建的子进程会替换掉父进程给的代码段,不拷贝父进程的堆、栈、全局、静态数据段,会用新的可执行文件替换掉他们。
2、exec只是加载一个可执行文件,并不创建进程,不会产生新的进程号。
3、只有exec函数执行结束(无论成功还是失败),父进程才能继续执行。
int execl(const char *path, const char
*arg, ...);
path:可执行文件的路径
arg:给可执行文件的参数,类似于命令行参数,必须以NULL结尾,第一个必须是可以执行文件名。
execl("","a.out",NULL);
1、通过exec创建的子进程会替换掉父进程给的代码段,不拷贝父进程的堆、栈、全局、静态数据段,会用新的可执行文件替换掉他们。
2、exec只是加载一个可执行文件,并不创建进程,不会产生新的进程号。
3、只有exec函数执行结束(无论成功还是失败),父进程才能继续执行。
int execlp(const char *file, const char *arg, ...); file:可执行文件的文件名,会从PATH环境变量指定的位置去找可执行文件。 int execle(const char *path, const char *arg,..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]);
一、基本的概念
1、中断
中止、暂停当前正在执行的进程,转而去执行其它的任务。
硬中断:来自硬件设备的中断
软中断:来自其它程序的中断
2、信号
信号是一种软中断,可以把他看作是进程与进程、内核与进程通信的一种方式,它为进程的异步执行,提供了技术支持。
3、常见的信号
4、不可靠信号(非实时)
1、编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。
2、不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。
3、进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。
5、可靠信号(实时)
1、位于[SIGRGMI(34),SIGRTMAX(64)]区间的都是可靠信号。
2、可靠信号支持排除,不会丢失。
3、无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction进行处理。
6、信号的来源
硬件来源:
键盘:Ctrl+c、Ctrl+/、Ctrl+z
驱动:硬件设备被激活、使用、失效。
内存:非法访问内存。
软件来源:
命令:kill、killall
函数:kill/raise/alarm/setitimer/sigqueue。
7、信号的处理方式
1、忽略
2、终止
3、终止+core
core dump 把内存的使用情况扔出来。
core文件是一种二进制文件,需要一些高度工具才能解析出来(gdb)。
1、gcc -g code.c -> 生成带调试信息的可执行文件。
2、运行可执行文件产生core文件
3、gdb ./a.out core 程序会停止在产生错误的位置。
ubuntu默认不产生core谁的,需要使用命令设置:
ulimit -c unlimited
4、捕获并处理
二、信号的捕获和处理
1、中断
中止、暂停当前正在执行的进程,转而去执行其它的任务。
硬中断:来自硬件设备的中断
软中断:来自其它程序的中断
2、信号
信号是一种软中断,可以把他看作是进程与进程、内核与进程通信的一种方式,它为进程的异步执行,提供了技术支持。
3、常见的信号
SIGINT(2) 终端中断信号Ctrl+c SIGQUIT(3) 终端退出信号Ctrl+/ SIGABRT(6) 调用abort函数产生的信号 SIGFPE(8) 算术信号 SIGKILL(9) 死亡信号 SIGSEGV(11) 段错误信号 SIGALRM(14) 闹钟信号 SIGCHLD(17) 子进程结束信号 SIGCONT(18) 进程继续信号 SIGSTOP(19) 进程暂停信号 SIGTSTP(20) 终端停止信号
4、不可靠信号(非实时)
1、编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。
2、不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。
3、进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。
5、可靠信号(实时)
1、位于[SIGRGMI(34),SIGRTMAX(64)]区间的都是可靠信号。
2、可靠信号支持排除,不会丢失。
3、无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction进行处理。
6、信号的来源
硬件来源:
键盘:Ctrl+c、Ctrl+/、Ctrl+z
驱动:硬件设备被激活、使用、失效。
内存:非法访问内存。
软件来源:
命令:kill、killall
函数:kill/raise/alarm/setitimer/sigqueue。
7、信号的处理方式
1、忽略
2、终止
3、终止+core
core dump 把内存的使用情况扔出来。
core文件是一种二进制文件,需要一些高度工具才能解析出来(gdb)。
1、gcc -g code.c -> 生成带调试信息的可执行文件。
2、运行可执行文件产生core文件
3、gdb ./a.out core 程序会停止在产生错误的位置。
ubuntu默认不产生core谁的,需要使用命令设置:
ulimit -c unlimited
4、捕获并处理
二、信号的捕获和处理
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighan‐ dler_t handler); 功能:向注册一个信号处理函数 signum:信号的编号,可以直接写数字,也可以使用系统提供的宏。 handler:函数指针 SIG_IGN 忽略信号 SIG_DFL 恢复信号默认的处理方式 返回值:是之前信号处理方式 函数指针、SIG_IGN、SIG_DFL、SIG_ERR
练习:实现一个杀不死的程序。
1、在有些系统中向内核注册的信号处理函数只执行一次(在执行前就被恢复成默认的处理方式),如果想持续处理信号,可以在每次的处理函数结束时再次注册。
2、SIGKILL、SIGSTOP不能被捕获、也不能被忽略。
3、普通用户只能给自己的进程发信号,超级用户可以能任意进程发送信号。
三、子进程的信号处理
1、通过fork创建的子进程会继承父进程的信号处理方式。
2、通过vfork+exec创建的子进程不会继承父进程的信号处理方式,会恢复成默认的。
练习:测试通过vfork+exec创建的子进程是否会继承父进程的信号处理方式。
四、信号的发送
1、键盘
Ctrl+c 终端中断信号
Ctrl+z 终端暂停信号,fg命令再次开启
Ctrl+/ 终端退出信号
2、错误产生的信号
除0
非法内存访问
硬件总线
3、命令产生的信号
kill -信号 进程号
killall -信号 程序名(杀死所有同名的进程)。
4、函数产生的信号
int kill(pid_t pid, int sig);
功能:向指定的进程发送信号
pid:与waitpid一样
sig:信号
0表示空信号,不会向进程发送信号,但是会测试是否能向pid发送信号,这样可以检测一个进程是否存在,返回-1表示进程不存在,errno为ESRCH。
返回值:-1,说明进程不存在
int raise(int sig);
功能:向自己发送信号
练习:实现kill命令的功能。
五、pause
#include <unistd.h>
int pause(void);
功能:休眠
1、进程调用了pause函数后会进程睡眠状态,直到有信号把它叫醒(不被忽略的信号)。
2、当信号来临后,先执行信号处理函数,信号处理函数结束后pause再返回。
3、pause函数要么不返回(一直睡眠),要么返回-1,并且修改errno的值。
4、从功能上来讲它相当于没有时间限制的sleep函数。
六、sleep
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:使用调用的进程睡眠seconds秒
1、调用sleep的进程如果没有睡眠足够的秒数,除非收到信号后才会返回。
2、sleep的返回值是0,或剩余的睡眠秒数。
3、相当于有时间限制的pause
int usleep(useconds_t usec);v
功能:睡眠usec微秒
1秒=1000毫秒=1000000微秒。
它是一种更精确的睡眠函数。
-std=c99 不建议使用
-std=gnu99 在使用系统调用时一定要使用此标准
七、alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:定时一个闹钟信号
1、让内核向调用它的进程,在seconds秒后发送一个SIGALRM信号。
2、SIGALRM信号的默认处理方式是直接退出。
练习:实现一个闹钟命令,./alarm sec
八、信号集和信号屏蔽
1、信号集:
多个信号的集合,sigset_t
由128个二进制位组成,每个二进制位表示一个信号
int sigemptyset(sigset_t *set); 功能:清空信号集 int sigfillset(sigset_t *set); 功能:填满信号信 int sigaddset(sigset_t *set, int signum); 功能:向信号集中添加信号 int sigdelset(sigset_t *set, int signum); 功能:从信号集中删除信号 int sigismember(const sigset_t *set, int signum); 功能:测试一个信号集中是否有某个信号 返回值:有返回1,没有返回0,失败返回-1
2、屏蔽信号集中的信号
每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号。
int sigprocmask(int how, const sigset_t
*set, sigset_t *oldset);
功能:设置进程的信号掩码(信号屏蔽码)
how:修改信号掩码的方式
SIG_BLOCK:向信号掩码中添加信号
SIG_UNBLOCK:从信号掩码中删除信号
SIG_SETMASK:用新的信号集替换旧的信号掩码
newset:新添加、删除、替换的信号集,也可以为空
oldset:获取旧的信号掩码
当newset为空时,就是在备份信号掩码
当进程执行一些敏感操作时不希望被打扰(原子操作),此需要向屏蔽信号。
屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。
当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次。
不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。
可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。
在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复
int sigpending(sigset_t *set);
功能:获取末决状态的信号
九、信号处理sigaction signal
屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。
当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次。
不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。
可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。
在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复
int sigpending(sigset_t *set);
功能:获取末决状态的信号
九、信号处理sigaction signal
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact); 功能:设置或获取信号处理方式 struct sigaction { // 信号处理函数指针 void (*sa_handler)(int); // 信号处理函数指针 需要使用sigqueue发送信号 void (*sa_sigaction)(int, siginfo_t *, void *); // 信号屏蔽码 sigset_t sa_mask; int sa_flags; SA_NOCLDSTOP:忽略SIGCHLD信号 SA_NODEFER/SA_NOMASK:在处理信号时不屏蔽信号 SA_RESETHAND:处理完信号后,恢复系统默认处理方式 SA_RESTART:当信号处理函数中断的系统调用,则重启系统调用。 SA_SIGINFO:用sa_sigaction处理信号 // 保留 void (*sa_restorer)(void); }; int sigqueue(pid_t pid, int sig, const union sigval value);
一、基本概念
进程间通信(IPC):进程之间交换数据的过程叫进程间通信。
进程间通信的方式:
简单的进程间通信:
命令行:父进程通过exec函数创建子进程时可以附加一些数据。
环境变量:父进程通过exec函数创建子进程顺便传递一张环境变量表。
信号:父子进程之间可以根据进程号相互发送信号,进程简单通信。
文件:一个进程向文件中写入数据,另一个进程从文件中读取出来。
命令行、环境变量只能单身传递,信号太过于简单,文件通信不能实时。
XSI通信方式:X/open 计算机制造商组织。
共享内存、消息队列、信号量
网络进程间通信方式:
网络通信就是不同机器的进程间通信方式。
传统的进程间通信方式:管道
二、管道
1、管道是一种古老的通信的方式(基本上不再使用)
2、早期的管道是一种半双工,现在大多数是全双工。
3、有名管道(这种管道是以文件方式存在的)。
int mkfifo(const char *pathname, mode_t mode);
管道通信的编程模式:
进程A 进程B
创建管道mkfifo
打开管道open 打开管道
写/读数据read/write 读/写数据
关闭管道close 关闭管道
4、无名管道:由内核帮助创建,只返回管道的文件描述符,看不到管道文件,但这种管道只能用在fork创建的父子进程之间。
int pipe(int pipefd[2]);
pipefd[0] 用来读数据
pipefd[1] 用来写数据
练习:使用无名管道,让父子进程通信。
三、XSI IPC进程间通信
1、XSI通信是靠内核的IPC对象进程通信。
2、每一个IPC对象都有一个IPC标识(类似文件描述符),IPC标识它是一个非的整数。
3、IPC对象必须要先创建,创建后才能进程获取、设置、操作、删除。
4、创建IPC对象必须要提供一个键值(key_t),键值是创建、获取IPC对象的依据。
5、产生键值的方法:
固定的字面值:1980014
使用函数计算:键值=ftok(项目路径,项目id)
使用宏让操作系统随机分配:IPC_PRIVTE
必须把获取到IPC对象标识符记录下来,告诉其它进程
6、XSI可以创建的IPC对象有:
共享内存,消息队列,信号量
四、共享内存
1、由内存维护一个共享的内存区域,其它进程把自己的虚拟地址映射到这块内存,然后多个进程之间就可以共享这块内存了。
2、这种进程间通信的好处是不需要信息复制,是进程间通信最快的一种方式。
3、但这种通信方式会面临同步的问题,需要与其它通信方式配合,最合适的就是信号。
共享内存的编程模式:
1、进程之间要约定一个键值
进程A 进程B
创建共享内存
加载共享内存 加载共享内存
卸载共享内存 卸载共享内存
销毁共享内存
int shmget(key_t key, size_t size, int shmflg);
功能:创建共享内存
size:共享的大小,尽量是4096的位数
shmflg:IPC_CREAT|IPC_EXCL
返回值:IPC对象标识符(类似文件描述符)
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:加载共享内存(进程的虚拟地址与共享的内存映射)
shmid:shmget的返回值
shmaddr:进程提供的虚拟地址,如果为NULL,操作系统会自动选择一块地址映射。
shmflg:
SHM_RDONLY:限制内存的权限为只读
SHM_REMAP:映射已经存的共享内存。
SHM_RND:当shmaddr为空时自动分配
SHMLBA:shmaddr的值不能为空,否则出错
返回值:映射后的虚拟内存地址
int shmdt(const void *shmaddr);
功能:卸载共享内存(进程的虚拟地址与共享的内存取消映射关系)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制/销毁共享内存
cmd:
IPC_STAT:获取共享内存的属性
IPC_SET:设置共享内存的属性
IPC_RMID:删除共享内存
buf:
记录共享内存属性的对象
五、消息队列
1、消息队列是一个由系统内核负责存储和管理、并通过IPC对象标识符获取的数据链表。
int msgget(key_t key, int msgflg);
功能:创建或获取消息队列
msgflg:
创建:IPC_CREAT|IPC_EXEC
获取:0
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送消息
msqid:msgget的返回人值
msgp:消息(消息类型+消息内容)的首地址
msgsz:消息内存的长度(不包括消息类型)
msgflg:
MSG_NOERROR:当消息的实际长比msgsz还要长的话,
则按照msgsz长度截取再发送,否则产生错误。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列接收消息
msgp:存储消息的缓冲区
msgsz:要接收的消息长度
msgtyp:消息的的类型(它包含消息的前4个字节)
msgflg:
MSG_NOWAIT:如果要接收的消息不存在,直接返回。
否则阻塞等待。
MSG_EXCEPT:从消息队列中接收第一个不msgtyp类型的第一个消息。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:控制/销毁消息队列
cmd:
IPC_STAT:获取消息队的属性
IPC_SET:设置消息队列的属性
IPC_RMID:删除消息队列
进程间通信(IPC):进程之间交换数据的过程叫进程间通信。
进程间通信的方式:
简单的进程间通信:
命令行:父进程通过exec函数创建子进程时可以附加一些数据。
环境变量:父进程通过exec函数创建子进程顺便传递一张环境变量表。
信号:父子进程之间可以根据进程号相互发送信号,进程简单通信。
文件:一个进程向文件中写入数据,另一个进程从文件中读取出来。
命令行、环境变量只能单身传递,信号太过于简单,文件通信不能实时。
XSI通信方式:X/open 计算机制造商组织。
共享内存、消息队列、信号量
网络进程间通信方式:
网络通信就是不同机器的进程间通信方式。
传统的进程间通信方式:管道
二、管道
1、管道是一种古老的通信的方式(基本上不再使用)
2、早期的管道是一种半双工,现在大多数是全双工。
3、有名管道(这种管道是以文件方式存在的)。
int mkfifo(const char *pathname, mode_t mode);
管道通信的编程模式:
进程A 进程B
创建管道mkfifo
打开管道open 打开管道
写/读数据read/write 读/写数据
关闭管道close 关闭管道
4、无名管道:由内核帮助创建,只返回管道的文件描述符,看不到管道文件,但这种管道只能用在fork创建的父子进程之间。
int pipe(int pipefd[2]);
pipefd[0] 用来读数据
pipefd[1] 用来写数据
练习:使用无名管道,让父子进程通信。
三、XSI IPC进程间通信
1、XSI通信是靠内核的IPC对象进程通信。
2、每一个IPC对象都有一个IPC标识(类似文件描述符),IPC标识它是一个非的整数。
3、IPC对象必须要先创建,创建后才能进程获取、设置、操作、删除。
4、创建IPC对象必须要提供一个键值(key_t),键值是创建、获取IPC对象的依据。
5、产生键值的方法:
固定的字面值:1980014
使用函数计算:键值=ftok(项目路径,项目id)
使用宏让操作系统随机分配:IPC_PRIVTE
必须把获取到IPC对象标识符记录下来,告诉其它进程
6、XSI可以创建的IPC对象有:
共享内存,消息队列,信号量
四、共享内存
1、由内存维护一个共享的内存区域,其它进程把自己的虚拟地址映射到这块内存,然后多个进程之间就可以共享这块内存了。
2、这种进程间通信的好处是不需要信息复制,是进程间通信最快的一种方式。
3、但这种通信方式会面临同步的问题,需要与其它通信方式配合,最合适的就是信号。
共享内存的编程模式:
1、进程之间要约定一个键值
进程A 进程B
创建共享内存
加载共享内存 加载共享内存
卸载共享内存 卸载共享内存
销毁共享内存
int shmget(key_t key, size_t size, int shmflg);
功能:创建共享内存
size:共享的大小,尽量是4096的位数
shmflg:IPC_CREAT|IPC_EXCL
返回值:IPC对象标识符(类似文件描述符)
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:加载共享内存(进程的虚拟地址与共享的内存映射)
shmid:shmget的返回值
shmaddr:进程提供的虚拟地址,如果为NULL,操作系统会自动选择一块地址映射。
shmflg:
SHM_RDONLY:限制内存的权限为只读
SHM_REMAP:映射已经存的共享内存。
SHM_RND:当shmaddr为空时自动分配
SHMLBA:shmaddr的值不能为空,否则出错
返回值:映射后的虚拟内存地址
int shmdt(const void *shmaddr);
功能:卸载共享内存(进程的虚拟地址与共享的内存取消映射关系)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制/销毁共享内存
cmd:
IPC_STAT:获取共享内存的属性
IPC_SET:设置共享内存的属性
IPC_RMID:删除共享内存
buf:
记录共享内存属性的对象
五、消息队列
1、消息队列是一个由系统内核负责存储和管理、并通过IPC对象标识符获取的数据链表。
int msgget(key_t key, int msgflg);
功能:创建或获取消息队列
msgflg:
创建:IPC_CREAT|IPC_EXEC
获取:0
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送消息
msqid:msgget的返回人值
msgp:消息(消息类型+消息内容)的首地址
msgsz:消息内存的长度(不包括消息类型)
msgflg:
MSG_NOERROR:当消息的实际长比msgsz还要长的话,
则按照msgsz长度截取再发送,否则产生错误。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列接收消息
msgp:存储消息的缓冲区
msgsz:要接收的消息长度
msgtyp:消息的的类型(它包含消息的前4个字节)
msgflg:
MSG_NOWAIT:如果要接收的消息不存在,直接返回。
否则阻塞等待。
MSG_EXCEPT:从消息队列中接收第一个不msgtyp类型的第一个消息。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:控制/销毁消息队列
cmd:
IPC_STAT:获取消息队的属性
IPC_SET:设置消息队列的属性
IPC_RMID:删除消息队列
博客是我在大海中捞到针的神器