目录
信号高级认识范例
【示例 】
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> //信号处理函数 void sig_usr(int signo) { if(signo == SIGUSR1) { printf("收到了SIGUSR1信号,我休息10秒......!\n"); sleep(10); printf("收到了SIGUSR1信号,我休息10秒完毕,苏醒了......!\n"); } else if(signo == SIGUSR2) { printf("收到了SIGUSR2信号,我休息10秒......!\n"); sleep(10); printf("收到了SIGUSR2信号,我休息10秒完毕,苏醒了......!\n"); } else { printf("收到了未捕捉的信号%d!\n",signo); } } int main(int argc, char *const *argv) { if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号!\n"); } if(signal(SIGUSR2,sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR2信号!\n"); } for(;;) { sleep(1); //休息1秒 printf("休息1秒~~~~!\n"); } printf("再见!\n"); return 0; }
【实验1】
- 给进程发送信号
- 进程收到信号
- 现象:在信号处理函数中休眠10秒,期间是不执行主函数中的for循环的。
- 执行信号处理函数被卡住了10秒,这个时候因为流程回不到main(),所以main中的语句无法得到执行。
【实验2】
- 给进程发送一次信号后立即再多次发送信号给进程
- 进程收到信号
- 在触发SIGUSR1信号并因此sleep了10秒种期间,就算你多次触发SIGUSR1信号,也不会重新执行SIGUSR1信号对应的信号处理函数,而是会等待上一个SIGUSR1信号处理函数执行完毕才 第二次执行SIGUSR1信号处理函数。
- 换句话说:在信号处理函数被调用时,操作系统建立的新信号屏蔽字(sigprocmask()),自动包括了正在被递送的信号,因此,保证了在处理一个给定信号的时候,如果这个信号再次发生,那么它会阻塞到对前一个信号处理结束为止。
- 不管你发送了多少次kill -usr1信号,在该信号处理函数执行期间,后续所有的SIGUSR1信号统统被归结为一次。
- 比如当前正在执行SIGUSR1信号的处理程序但没有执行完毕,这个时候,你又发送来了5次SIGUSR1信号,那么当SIGUSR1信号处理程序执行完毕(解除阻塞),SIGUSR1信号的处理程序也只会被调用一次(而不会分别调用5次SIGUSR1信号的处理程序)。
【实验3】
- 给进程发送USR1信号,调用信号处理函数休眠期间立即给进程发送USR2信号。
- 进程收到信号,表现如下:
- 执行usr1信号处理程序,但是没执行完时,是可以继续进入到usr2信号处理程序里边去执行的,这个时候,相当于usr2信号处理程序没执行完毕,usr1信号处理程序也没执行完毕,此时再发送usr1和usr2都不会有任何响应(信号屏蔽)。
- 既然是在执行usr1信号处理程序执行的时候来了usr2信号,导致又去执行了usr2信号处理程序,这就意味着,只有usr2信号处理程序执行完毕,才会返回到usr1信号处理程序,只有usr1信号处理程序执行完毕了,才会最终返回到main函数主流程中去继续执行。
- 思考:如果希望在处理SIGUSR1信号,执行usr1信号处理程序的时候,如果来了SIGUSR2信号,想堵住(屏蔽住),不想让程序流程跳到SIGUSR2信号处理中去执行
- 可以做到的(使用sigprocmask()),后续会讲解其他的如何屏蔽信号的方法。
服务器架构初步
- 项目 肯定会有多个源文件,头文件,会分别存放到多个目录,我们要规划项目的目录结构。
目录结构规划(make编译)
- 特别注意:不固安是目录还是文件,文件名中一律不要带空格,一律不要用中文,最好的方式:字母,数字,下划线,不要给自己找麻烦,远离各种坑。
- 主目录名nginx
- a)_include目录:专门存放各种头文件; 如果分散:#include "sfaf/sdafas/safd.h"。
- b)app目录:放主应用程序.c(main()函数所在的文件)以及一些比较核心的文件。
- b.1)link_obj:临时目录:会存放临时的.o文件,这个目录不手工创建,后续用makefile脚本来创建。
- b.2)dep:临时目录,会存放临时的.d开头的依赖文件,依赖文件能够告知系统哪些相关的文件发生变化,需要重新编译,后续用makefile脚本来创建。
- b.3)nginx.c:主文件,main()入口函数就放到这里。
- b.4)ngx_conf.c ,普通的源码文件,跟主文件关系密切,又不值得单独放在 一个目录。
- c)misc目录:专门存放各种杂合性的不好归类的1到多个.c文件;暂时为空。
- d)net目录:专门存放和网络处理相关的1到多个.c文件,暂时为空。
- e)proc目录:专门存放和进程处理有关的1到多个.c文件,暂时为空。
- f)signal目录:专门用于存放和信号处理 有关的1到多个.c文件。
- ngx_signal.c
- windows环境下目录结构
- vscode下文件结构
- linux环境使用tree查看目录结构
编译工具make的使用概述(编译出可执行文件)
- 使要用传统的 ,经过验证没有问题的方式来编译我们的项目,最终生成可执行文件。
- 每个.c生成一个.o,多个.c生成多个.o,最终这些.o被链接到一起,生成一个可执行文件
- 要借助make的命令来编译:能够编译,链接.......最终生成可执行文件,大型项目一般都用make来搞。
- make命令的工作原理,就去当前目录读取一个叫做makefile的文件(文本文件),根据这个makefile文件里的规则把咱们的源代码编译成可执行文件,开发者的任务就是要把这个makefile文件写出来。
- 这个makefile里边就定义了我们怎么去编译整个项目的编译、链接规则。
- 实际上makefile文件就是一个我们编译工程要用到的各种源文件等等的一个依赖关系描述。
- makefile文件:文本文件,utf8编码格式,没有扩展名,一般放在根目录下[也会根据需要放在子目录](这里nginx)。
- 不同的程序员写的makefile代码也会千差万别;但不管怎么说,最终都要把可执行文件给我生成出来。
【gcc /g++命令重要参数】
- -o:指定编译链接后生成的可执行文件名,比如
- gcc -o nginx nginx.c
- -c: 将.c编译成.o目标文件[仅执行编译操作,不进行链接操作]
- gcc -c nginx.c
- 将生成nginx.o的目标文件
- -M:显示一个源文件所依赖的各种文件
- gcc -M nginx.c
- -MM:显示一个源文件所依赖的各种文件,但不包括系统的一些头文件
- gcc -MM nginx.c
- 这种扫描是有用途的,尤其是在写makefile文件时,需要用到这些依赖关系,以做到比如当某个.h头文件更改时,整个工程会实现自动重新编译的目的。
- -g:生成调试信息。GNU 调试器可利用该信息。
- -I:gcc会先到你 用这个参数指定的目录去查找头文件,在.c/.cpp中可以
- #include <abc.h> //这里用尖括号了
- gcc -I /mnt/mydir
规划一下makefile文件的编写
- nginx根目录下放三个文件:
- makefile:是编译项目的入口脚本,编译项目从这里开始,起总体控制作用。
- config.mk:这是个配置脚本,被makefile文件包含,单独分离出来是为了应付一些可变的东西,所以一般变动的东西都往这里搞。
- common.mk:是最重要最核心的编译脚本,定义makefile的编译规则,依赖规则等,通用性很强的一个脚本,并且各个子目录中都用到这个脚本来实现对应子目录的.c文件的编译。
- 每个子目录下(app,signal)都有一个叫做makefile的文件,每个这个makefile文件,都会包含根目录下的common.mk。
- 从而实现自己这个子目录下的.c文件的编译。
- 现在的makefile不支持目录中套子目录,除非大家自己修改。
- 其他规划,上边讲过:
- app/link_obj临时目录,存放.o目标文件。
- app/dep:存放.d开头的依赖关系文件。
【makefile】
include config.mk all: #-C是指定目录 #make -C signal #可执行文件应该放最后 #make -C app #用shell命令for搞,shell里边的变量用两个$ @for dir in $(BUILD_DIR); \ do \ make -C $$dir; \ done clean: #-rf:删除文件夹,强制删除 rm -rf app/link_obj app/dep nginx rm -rf signal/*.gch app/*.gch
【config.mk】
#定义项目编译的根目录,通过export把某个变量声明为全局的[其他文件中可以用],这里获取当前这个文件所在的路径作为根目录; #BUILD_ROOT = /mnt/hgfs/linux/nginx export BUILD_ROOT = $(shell pwd) #定义头文件的路径变量 export INCLUDE_PATH = $(BUILD_ROOT)/_include #定义我们要编译的目录 BUILD_DIR = $(BUILD_ROOT)/signal/ \ $(BUILD_ROOT)/app/ #编译时是否生成调试信息。GNU调试器可以利用该信息 export DEBUG = true
【common.mk】
#.PHONY:all clean ifeq ($(DEBUG),true) #-g是生成调试信息。GNU调试器可以利用该信息 CC = gcc -g VERSION = debug else CC = gcc VERSION = release endif #CC = gcc # $(wildcard *.c)表示扫描当前目录下所有.c文件 #SRCS = nginx.c ngx_conf.c SRCS = $(wildcard *.c) #OBJS = nginx.o ngx_conf.o 这么一个一个增加.o太麻烦,下行换一种写法:把字符串中的.c替换为.o OBJS = $(SRCS:.c=.o) #把字符串中的.c替换为.d #DEPS = nginx.d ngx_conf.d DEPS = $(SRCS:.c=.d) #可以指定BIN文件的位置,addprefix是增加前缀函数 #BIN = /mnt/hgfs/linux/nginx BIN := $(addprefix $(BUILD_ROOT)/,$(BIN)) #定义存放ojb文件的目录,目录统一到一个位置才方便后续链接,不然整到各个子目录去,不好链接 #注意下边这个字符串,末尾不要有空格等否则会语法错误 LINK_OBJ_DIR = $(BUILD_ROOT)/app/link_obj DEP_DIR = $(BUILD_ROOT)/app/dep #-p是递归创建目录,没有就创建,有就不需要创建了 $(shell mkdir -p $(LINK_OBJ_DIR)) $(shell mkdir -p $(DEP_DIR)) #我们要把目标文件生成到上述目标文件目录去,利用函数addprefix增加个前缀 #处理后形如 /mnt/hgfs/linux/nginx/app/link_obj/ngx_signal2.o /mnt/hgfs/linux/nginx/app/link_obj/ngx_signal.o # := 在解析阶段直接赋值常量字符串【立即展开】,而 = 在运行阶段,实际使用变量时再进行求值【延迟展开】 # /mnt/hgfs/linux/nginx/app/link_obj/nginx.o /mnt/hgfs/linux/nginx/app/link_obj/ngx_conf.o OBJS := $(addprefix $(LINK_OBJ_DIR)/,$(OBJS)) DEPS := $(addprefix $(DEP_DIR)/,$(DEPS)) #找到目录中的所有.o文件(编译出来的) LINK_OBJ = $(wildcard $(LINK_OBJ_DIR)/*.o) #因为构建依赖关系时app目录下这个.o文件还没构建出来,所以LINK_OBJ是缺少这个.o的,我们 要把这个.o文件加进来 LINK_OBJ += $(OBJS) #------------------------------------------------------------------------------------------------------- #make找第一个目标开始执行[每个目标[就是我们要生成的东西],其实都是定义一种依赖关系],目标的格式为: #目标:目标依赖【可以省略】 # 要执行的命令【可以省略】 #如下这行会是开始执行的入口,执行就找到依赖项$(BIN)去执行了,同时,这里也依赖了$(DEPS),这样就会生成很多.d文件了 all:$(DEPS) $(OBJS) $(BIN) #这里是诸多.d文件被包含进来,每个.d文件里都记录着一个.o文件所依赖哪些.c和.h文件。内容诸如 nginx.o: nginx.c ngx_func.h #我们做这个的最终目的说白了是,即便.h被修改了,也要让make重新编译我们的工程,否则,你修改了.h,make不会重新编译,那不行的 #有必要先判断这些文件是否存在,不然make可能会报一些.d文件找不到 ifneq ("$(wildcard $(DEPS))","") #如果不为空,$(wildcard)是函数【获取匹配模式文件名】,这里 用于比较是否为"" include $(DEPS) endif #----------------------------------------------------------------1begin------------------ #$(BIN):$(OBJS) $(BIN):$(LINK_OBJ) @echo "------------------------build $(VERSION) mode--------------------------------!!!" #一些变量:$@:目标, $^:所有目标依赖 # gcc -o 是生成可执行文件 $(CC) -o $@ $^ #----------------------------------------------------------------1end------------------- #----------------------------------------------------------------2begin----------------- #%.o:%.c $(LINK_OBJ_DIR)/%.o:%.c # gcc -c是生成.o目标文件 -I可以指定头文件的路径 #如下不排除有其他字符串,所以从其中专门把.c过滤出来 #$(CC) -o $@ -c $^ $(CC) -I$(INCLUDE_PATH) -o $@ -c $(filter %.c,$^) #----------------------------------------------------------------2end------------------- #----------------------------------------------------------------3begin----------------- #我们现在希望当修改一个.h时,也能够让make自动重新编译我们的项目,所以,我们需要指明让.o依赖于.h文件 #那一个.o依赖于哪些.h文件,我们可以用“gcc -MM c程序文件名” 来获得这些依赖信息并重定向保存到.d文件中 #.d文件中的内容可能形如:nginx.o: nginx.c ngx_func.h #%.d:%.c $(DEP_DIR)/%.d:%.c #gcc -MM $^ > $@ #.d文件中的内容形如:nginx.o: nginx.c ngx_func.h ../signal/ngx_signal.h,但现在的问题是我们的.o文件已经放到了专门的目录 # 所以我们要正确指明.o文件路径这样,对应的.h,.c修改后,make时才能发现,这里需要用到sed文本处理工具和一些正则表达式语法,不必深究 #gcc -MM $^ | sed 's,\(.*\)\.o[ :]*,$(LINK_OBJ_DIR)/\1.o:,g' > $@ #echo 中 -n表示后续追加不换行 echo -n $(LINK_OBJ_DIR)/ > $@ # gcc -MM $^ | sed 's/^/$(LINK_OBJ_DIR)&/g' > $@ # >>表示追加 gcc -I$(INCLUDE_PATH) -MM $^ >> $@ #上行处理后,.d文件中内容应该就如:/mnt/hgfs/linux/nginx/app/link_obj/nginx.o: nginx.c ngx_func.h ../signal/ngx_signal.h #----------------------------------------------------------------4begin----------------- #----------------------------------------------------------------nbegin----------------- #clean: #rm 的-f参数是不提示强制删除 #可能gcc会产生.gch这个优化编译速度文件 # rm -f $(BIN) $(OBJS) $(DEPS) *.gch #------------------------------------
makefile脚本用法介绍
- 项目中.c代码依赖图
- 在根目录下,使用make命令
- make clean删除临时文件,中间文件及可执行文件