Linux信号

在这里插入图片描述

文章目录

    • 一.信号基础
    • 二.信号的产生
      • 1.使用键盘组合键发送信号(只能给当前正在运行的进程发)
      • 信号捕捉
      • 2.使用kill指令(可以向任意进程发送信号)
      • 3.使用raise()让进程自己给自己发送信号
      • 4.硬件异常产生信号
        • a.除零引发的异常(SIGRFPE)
        • b.段错误引发的异常(SIGSEVG)
      • 5.软件问题导致的异常
      • a.匿名管道的读端关闭,写端还尝试写,操作系统会向写端发送13号SIGPIPE终止写端
      • b.14号SIGALRM定时器信号
    • 三.信号退出时的核心转储
    • 四.信号保存
      • 1.基本概念
      • 2.在内核中的表示
    • 五.信号的处理
      • 再谈地址空间
      • 信号处理全过程
    • 六.信号集操作函数
      • 1.sigset_t
      • 2.sigprocmask
      • 3.sigpending
      • 4.用以上函数写一个代码模块

一.信号基础

生活中

在生活中也有诸多信号,这些信号通常不是由我们发起的,而是我们接收以后对对应的信号做处理;最常见的莫过于红绿灯了,当红绿灯发出信号时(红灯,绿灯,黄灯);我们会有对应的行为,比如绿灯我们知道当前可以行走,红灯的时候我们需要等一等。对信号产生以后我们知道该做什么,这是因为我们曾经接受了对于这些信号的教育,知道当这些信号产生以后我们需要做什么。

技术上

信号并不是由某个进程发起的,而是操作系统发给某个进程的,一个进程异常退出,必定收到了操作系统的信号。使用kill -l可以查看全部的信号在这里插入图片描述

其中1-31为普通信号,34-64被称为实时信号

进程PCB中有一个位图结构用于标明该进程是否收到信号(32个比特位使用0/1来区分是否收到信号,0代表没收到),这也就是说发送信号时需要修改进程PCB,而修改PCB的需要只有操作系统有权限。

进程对于信号的处理有三种:1.默认,2.忽略,3.自定义;

但并不是进程一收到信号就马上处理,因为信号是随时产生的(异步),可能当信号来临时进程正在处理着更重要的事情,进程对信号的处理会在合适的时机(内核态返回用户态时);因为不是马上处理的,所以进程要对信号有保存能力

使用man 7 signal可以查看信号的默认处理行为在这里插入图片描述

Term代表是正常退出;

Core代表异常退出,可以开启核心转储功能提供错误定位(后文中会讲)

lgn代表内核级忽略

可以看到大部分信号的最终处理都是一样的(退出当前进程),系统设置这些信号主要是为了知道导致进程退出的原因是什么。

二.信号的产生

1.使用键盘组合键发送信号(只能给当前正在运行的进程发)

我们可以使用键盘组合键向进程发送信号,比如之前常用的ctrl+c其实是给进程发送二号信号

#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int signo)
{cout<<"捕捉到了信号,编号为:"<<signo<<endl;exit(1);
}int main()
{signal(2,handler);while(true){cout<<"I am a process,my pid is:"<<getpid()<<endl;sleep(1);}return 0;
}

在这里插入图片描述

信号捕捉

上述代码中的signal是一个系统调用,用来捕捉信号,给信号设置自定义处理方式的;它的第一个参数是你要捕捉的信号编号,第二个参数是一个函数指针,代表你要自定义的方法。

在这里插入图片描述

在上述代码中,虽然我对2号信号做了捕捉但是我在自定义方法中仍然选择让进程退出了,如果你的自定义方法中不让该进程退出,那么进程收到该信号后就不会再终止

将上述代码改成下面这样,无论是使用ctrl+c还是使用kill -2都无法让该进程终止

#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int signo)
{cout<<"捕捉到了信号,编号为:"<<signo<<endl;//exit(1);
}int main()
{signal(2,handler);while(true){cout<<"I am a process,my pid is:"<<getpid()<<endl;sleep(1);}return 0;
}

在这里插入图片描述


那如果我们使用signal将所有的信号都捕捉起来,是否代表该进程无法再被杀死了呢?

#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int signo)
{cout<<"捕捉到了信号,编号为:"<<signo<<endl;//exit(1);
}int main()
{for(int signo=1;signo<=31;signo++){signal(signo,handler);}while(true){cout<<"I am a process,my pid is:"<<getpid()<<endl;sleep(1);}return 0;
}

在这里插入图片描述

看起来该进程似乎已经无法被信号杀死了,那岂不是说明只要一个进程把所有的信号都捕捉起来,那这个进程就可以在系统中肆意妄为?

操作系统不会允许某个进程将所有的信号都捕捉,至少kill -9信号是无法被捕捉的,因为操作系统不相信任何人,它必须要留一手来保护自身的安全

在这里插入图片描述


2.使用kill指令(可以向任意进程发送信号)

kill指令我们已经不是第一次使用了,只要有某个进程的pid,那么就可以通过kill向该进程发送信号,终止进程,kill指令其实是通过kill()系统调用实现的,这里就模拟实现以下kill

在这里插入图片描述

mysignal.cc

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string>
void Usage(const std::string& proc)
{std::cout<<"Usige:"<<getpid()<< "Signno\n"<<std::endl;
}int main(int argc,char* argv[])//运行main函数时,需要先进行传参
{if(argc!=3)//如果传入main函数的参数个数不为3{Usage(argv[0]);exit(1);}pid_t pid=atoi(argv[1]);//获取第一个命令行参数,作为pidint signo=atoi(argv[2]);//获取第二个命令行参数,作为signoint n=kill(pid,signo);//需要发送信号的进程/发送几号信号if(n==-1)//kill()失败返回-1{perror("kill");}while(1){std::cout<<getpid()<<std::endl;sleep(1);}return 0;
}

test.cc

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
int main()
{while(1){std::cout<<"这是一个正在运行的进程"<<getpid()<<std::endl;sleep(1);}return 0;
}

在这里插入图片描述

3.使用raise()让进程自己给自己发送信号

在这里插入图片描述

#include <iostream>
#include <cstdio>
#include<cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string>//当计数器运行到5时,进程会因3号进程退出
int main(int argc,char* argv[])//运行main函数时,需要先进行传参
{int cnt=0;while(cnt<=10){std::cout<<cnt++<<std::endl;sleep(1);if(cnt>=5){raise(3);}}return 0;
}

在这里插入图片描述

raise(signo)其实等价于kill(getpid(),signo),此外虽然raise看起来是进程自己给发送信号,但其实还是操作系统发的,因为发送信号本身就是操作系统对进程所做的。

此外还有一个abort()进程自己给自己发送六号信号

在这里插入图片描述


4.硬件异常产生信号

硬件异常产生信号通常是因为软件问题造成的,操作系统通过CPU中的状态寄存器的得知对应硬件的状态,即可向对应进程发送指定的信号。

a.除零引发的异常(SIGRFPE)

#include <iostream>
#include <cstdio>
#include<cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string>
using namespace std;void handler(int signo)
{cout<<"捕捉到信号,编号为:"<<signo<<endl;sleep(1);
}int main()
{signal(8,handler);int a=3/0;while(true){cout<<"当前进程正在运行ing"<<endl;}return 0;
}

在这里插入图片描述

可以看到操作系统一直在给进程发8号信号,可是操作系统是如何得知我有除零错误的呢?

当一个进程被加载进CPU中,才表明该进程正在运行,而CPU中有一套寄存器用于存放进程的上下文,其实除了存放进程上下文的寄存器外,还有寄存器存放了进程PCB的起始地址(这就是为什么CPU可以得知当前正在运行的进程是哪个),以及进程的页表地址,并且CPU中集成了MMU单元,因此将进程的虚拟地址空间转换为物理地址在CPU中就能够完成。

但操作系统之所以能得知当前进程是否有除零错误是因为有一个状态寄存器的存在,状态寄存器中有一个溢出标志位该标志位默认是零(代表正常无溢出),但除零就是除一个无限小的数,得到的结果会无限大所以就会发生溢出,状态寄存器的溢出标志位被置1,操作系统识别到了该行为就给进程发送8号信号(操作系统能识别到该行为,是因为当进程被切换时寄存器的数据也要被替换,因此状态寄存器要被恢复一次,在恢复的时候操作系统就能识别到状态寄存器的信息)。

在这里插入图片描述

b.段错误引发的异常(SIGSEVG)

CPU中集成了MMU单元,该单元是实现页表虚拟地址到物理地址之间的转换;一旦你尝试越界访问或者有野指针的问题,能被MMU识别到,然后MMU就会给进程发送信号来终止进程

在这里插入图片描述

5.软件问题导致的异常

a.匿名管道的读端关闭,写端还尝试写,操作系统会向写端发送13号SIGPIPE终止写端

b.14号SIGALRM定时器信号

在这里插入图片描述

当设定的时间到达时,操作系统向进程发送14号信号终止进程

int cnt=0;
void handler(int signo)
{cout<<"捕捉到信号,编号为:"<<signo <<"当前进程跑的时间为:"<<cnt <<endl;//sleep(1);
}int main()
{signal(14,handler);alarm(1);while(1){cnt++;}return 0;
}

在这里插入图片描述

这就表明在1秒钟内该while循环被执行了500935048次

任何进程都可以使用闹钟,也就是说操作系统中可能同时存在多个闹钟,因此操作系统需要将闹钟给管理起来(通过先描述再组织的办法)。

三.信号退出时的核心转储

前面提到如果一个信号是Trem则是正常退出,如果是Core则是异常退出,异常信息会写到核心转储中。不过大部分云服务器都是默认关闭了该功能,可以使用ulimit -a来查看核心转储是否被打开

在这里插入图片描述

使用ulimit -c+大小可以打开核心转储并设置大小

在这里插入图片描述

核心转储的意义就是为了方便调试,当程序异常终止的时候会产生一个文件,再用gdb会该程序调试,则会直接定位到错误

在这里插入图片描述

四.信号保存

因为信号不是被立马处理的,所以进程要有对信号保存的能力,这个其实是保存再PCB中的pending位图中

1.基本概念

  1. 实际执行信号的处理动作称为信号递达(Delivery)

  2. 信号从产生到递达之间的状态,称为信号未决(Pending)。

  3. 进程可以选择阻塞 (Block )某个信号。

  4. 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

2.在内核中的表示

在这里插入图片描述

PCB中有两个位图和一个函数指针数组,而位图的下标就代表对应的信号,其中block位图代表的是该信号是否有被阻塞(1代表被阻塞),pending位图代表该信号是否有被递达;而handler是一个函数指针数组,该数组中存放的是函数指针,代表的是该进程对这个信号的处理方法

对于普通信号来说,pending位图中一个时间内只能存放一次同一个信号,如果该信号一直处于未递达的状态,那么即使后续发送了该信号也无法收到

五.信号的处理

因为信号保存在PCB中,但PCB中的数据只有操作系统有权限访问,因此要对信号做处理必须要通过操作系统来实现。

操作系统是一个层状结构,我们做开发也只是在用户层做开发,是没有权限要求操作系统帮我们修改内核中的数据的,这就是为什么一旦我们要访问内核中的数据或者硬件的时候,总是要调用系统调用。

其实调用系统调用之所以能让我们要求操作系统帮我们获得某些数据或者访问硬件,是因为在执行系统调用的时候,首先会执行Int 80这样的汇编代码,陷入内核,让我们从用户态切换到内核态。

在寄存器中有一个CR3寄存器,该寄存器中存放的数据代表的是当前代码的执行权限(0代表内核态,3代表用户态),陷入内核以后操作系统首先会修改CR3的数据。

当然也不用担心陷入内核以后找不到进程的代码,因为有寄存器保存了当前正在执行进程的PCB和用户级页表地址。

再谈地址空间

在前面的博客中只谈论了0–3G的用户级地址空间,现在就再将3–4G的内核级地址空间也拿出来谈论:

在这里插入图片描述

1.为什么用户级页表要各自有一份?

首先不同的进程拥有不同的数据,它们代码加载到内存中获得的物理地址也就不同。其次为了保证进程的独立性,每个进程都必须要有各自独立的用户级页表

2.为什么内核级页表所用进程共享一份?

因为操作系统只有一封,被加载到内存中也是独一份,因此没有必须要让每个进程都独立维护一个内核级页表

信号处理全过程

在这里插入图片描述

首先因为信号导致的系统调用陷入内核,从用户态切换到内核态,通过寄存器中保存的PCB地址找到PCB,再通过PCB中保存的位图和函数指针来识别信号,如果对于某一个信号的处理方式是自定义处理,那么必须要修改CR3中的权限值,回到用户态去执行自定义方法(因为操作系统不相信任何人,无法知道handler方法中是否有恶意代码);

执行完handler方法以后还需要再回一次内核态,因为进程的上下文数据是由操作系统保存的,无法直接知道之前是从哪一行代码跳转过来的,要想回到之前跳转的代码继续往后面执行,必须要有操作系统的参与。

上述的图也可以简化成下面这样

在这里插入图片描述

六.信号集操作函数

1.sigset_t

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。

因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。


#include <signal.h>
int sigemptyset(sigset_t *set);//将所有比特位清零,表示无有效信号
int sigfillset(sigset_t *set);//使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
//注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态

2.sigprocmask

调用sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。

如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。

如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。

假设当前的信号屏蔽字为mask,下表说明了how参数的可选值 :

在这里插入图片描述

如果调用sigprocmask函数解除了对某个未决信号的阻塞,那么再sigprocmask返回前,该信号可能已经被递达了,一旦信号递达,则说明该进程大概率也要被终止了

3.sigpending

#include <signal.h>
int sigpending(sigset_t *set);//set:输出型参数,输出当前进程pending位图
sigending()在成功时返回0,在错误时返回-1。在发生错误时,将 errno 设置。

4.用以上函数写一个代码模块

该代码实现阻塞某一个信号,将这个信号的block位图由0置1,然后接触阻塞,使该信号递达,这个进程直接寄掉

#include <iostream>
#include<vector>
#include <cstdio>
#include<cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string>
using namespace std;
#define MAX_SIGNUM 31static vector<int> sigarr={2};static void handler(int signo)
{cout<<"信号被递达,编号为:"<<signo<<endl;//sleep(1);
}void show_pending(const sigset_t&pending)
{for(int i=MAX_SIGNUM;i>=1;i--){if(sigismember(&pending,i)){cout<<"1";}elsecout<<"0";}cout<<endl;
}int main()
{sigset_t pending,block,oblock;//为特定的信号更改自定义方法for(const auto&sig:sigarr) signal(sig,handler);//初始化sigemptyset(&pending);sigemptyset(&block);sigemptyset(&oblock);//添加要被屏蔽的信号for(const auto &sig :sigarr) sigaddset(&block,sig);//  开始屏蔽,设置进内核(进程)sigprocmask(SIG_SETMASK, &block, &oblock);// 2. 遍历打印pengding信号集int cnt = 10;while(true){// 初始化sigemptyset(&pending);// 获取sigpending(&pending);// 打印show_pending(pending);sleep(1);if(cnt-- == 0){sigprocmask(SIG_SETMASK, &oblock, &block); // 一旦对特定信号进行解除屏蔽,一般OS要至少立马递达一个信号!cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";}}
return 0;
}

在这里插入图片描述

但是由于我对该信号做自定义捕捉了,所以2号信号无法终止该进程了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1111.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中List的使用方法简单介绍

Java中List的使用方法简单介绍 java中的List就是一种集合对象&#xff0c;将所有的对象集中到一起存储。List里面可以放任意的java对象&#xff0c;也可以直接放值。 使用方法很简单&#xff0c;类似于数组。 使用List之前必须在程序头引入java.util.* import java.util.*; pub…

分享四款导航页 个人主页html源码

一、开源免费&#xff0c;可以展示很多社交账号&#xff0c;也可以更换社交账号图标指向你的网站&#xff0c;上传后即可使用 https://wwwf.lanzout.com/ik7R912s031g 二、开源免费&#xff0c;不过部署稍微麻烦点 https://wwwf.lanzout.com/iCq2u12s02wb 三、适合做成导航页面…

golang网络编程学习-1rpc

网络编程主要的内容是&#xff1a; 1.TCP网络编程 2.http服务 3.rpc服务 4.websocket服务 一、rpc RPC 框架----- 远程过程调用协议RPC&#xff08;Remote Procedure Call Protocol)-----允许像调用本地服务一样调用远程服务。 RPC是指远程过程调用&#xff0c;也就是说两台服…

MySQL结构以及数据管理(增删改查)

目录 1.数据库的简介 2.数据库分类 2.1关系型数据库 2.2 非关系型数据库 3.mysql的数据类型 3.1 常用的数据库类型 4.mysql的数据库结构 4.1 查看库信息 4.2 查看表信息 5.SQL 语句 5.1 SQL语言分类&#xff1a; 1.数据库的简介 数据库&#xff08;database&#…

Spark高级特性

spark shuffle 中 map 和 reduce 是一个相对的概念&#xff0c;map是产生一批数据&#xff0c;reduce是接收一批数据&#xff0c;前一个任务是map&#xff0c;后一个任务是reduce。 hashShuffle&#xff1a;hash分组&#xff0c;一个task里面按hash值的不同&#xff0c;分到不…

微服务优雅上下线的实践方法

导语 本文介绍了微服务优雅上下线的实践方法及原理&#xff0c;包括适用于 Spring 应用的优雅上下线逻辑和服务预热&#xff0c;以及使用 Docker 实现无损下线的 Demo。同时&#xff0c;本文还总结了优雅上下线的价值和挑战。 作者简介 颜松柏 腾讯云微服务架构师 拥有超过…

Flask_实现token鉴权

目录 1、安装依赖 2、实现代码 3、测试 源码等资料获取方法 1、安装依赖 pip install flask pip install pycryptodome 2、实现代码 import random import string import time import base64from functools import wrapsfrom flask import Flask, jsonify, session, req…

RabbitMQ如何保证消息的可靠性6000字详解

RabbitMQ通过生产者、消费者以及MQ Broker达到了解耦的特点&#xff0c;实现了异步通讯等一些优点&#xff0c;但是在消息的传递中引入了MQ Broker必然会带来一些其他问题&#xff0c;比如如何保证消息在传输过程中可靠性&#xff08;即不让数据丢失&#xff0c;发送一次消息就…

学习babylon.js --- [2] 项目工程搭建

本文讲述如何搭建babylonjs的项目工程。 一 准备 首先创建一个目录叫MyProject&#xff0c;然后在这个目录里再创建三个目录&#xff1a;dist&#xff0c;public和src&#xff0c;如下&#xff0c; 接着在src目录里添加一个文件叫app.ts&#xff0c;本文使用typescript&#…

docker数据卷权限管理--理论和验证

一、Docker容器中用户权限管理 Linux系统的权限管理是由uid和gid负责&#xff0c;Linux系统会检查创建进程的uid和gid&#xff0c;以确定它是否有足够的权限修改文件&#xff0c;而非是通过用户名和用户组来确认。 同样&#xff0c;在docker容器中主机上运行的所有容器共享同一…

【kubernetes系列】Kubernetes之配置dashboard安装使用

Kubernetes之配置dashboard 概述 Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中&#xff0c;也可以对容器应用排错&#xff0c;还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览信息&#x…

【单例模式】—— 每天一点小知识

&#x1f4a7; 单例模式 \color{#FF1493}{单例模式} 单例模式&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专栏的文章图文并茂&#x1f995;生动形…

LiveGBS流媒体平台GB/T28181功能-作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备

LiveGBS作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备 1、背景说明2、部署国标平台2.1、安装使用说明2.2、服务器网络环境2.3、信令服务配置 3、监控摄像头设备接入3.1、海康GB28181接入示例3.2、大华GB28181接入示例3.3、华为IPC GB28181接…

SpringBoot整合ZooKeeper完整教程

目录 ZooKeeper简单介绍 一、安装zookeeper 二、springboot整合zookeeper ZooKeeper简单介绍 zookeeper是为分布式应用程序提供的高性能协调服务。zookeeper将命名、配置管理、同步和组服务等常用服务公开在一个简单的接口中&#xff0c;因此用户无需从头开始编写这些服务。可…

Android GridPager实战,从RecyclerView to ViewPager

这个简单的的案例展示了如何从RecyclerView to ViewPager&#xff0c;以网上的公开图片为样例。 安卓开发中从RecyclerView 到 ViewPager demo运行结果demo项目工程目录结构关键代码 MainActivity关键代码GridFragment关键代码ImageFragment关键代码ImagePagerFragment关键布局…

CSS---CSS面试题

目录 1.盒模型 2.offsetHeight /clientheight/scrollHeight 3.left与offsetLeft 4.对BFC规范的理解 5.解决元素浮动导致的父元素高度塌陷的问题 6.CSS样式的先级 7.隐藏页面元素 8.display: none 与 visibility: hidden 的区别 9.页面引入样式时&#xff0c;使用link与import有…

C++学习——类和对象(一)

C语言和C语言最大的区别在于在C当中引入了面向对象的编程思想&#xff0c;想要完全了解c当中的类和对象&#xff0c;就要从头开始一点一点的积累并学习。 一&#xff1a;什么是面向对象编程 我们之前学习的C语言属于面向过程的编程方法。举一个简单的例子来说&#xff1a;面向过…

使用npm和nrm查看源和切换镜像

一、使用npm查看当前源、切换淘宝镜像、切换官方源 &#xff08;1&#xff09;npm查看当前源&#xff1a; npm get registry &#xff08;2&#xff09;npm设置淘宝镜像源&#xff1a; npm config set registry http://registry.npm.taobao.org &#xff08;3&#xff09;n…

【运维工程师学习三】Linux中Shell脚本编写

【运维工程师学习三】shell编程 Shell程序分类1、系统中sh命令是bash的软链接2、Shell脚本标准格式之文件后缀3、Shell脚本标准格式之文件内容首行4、Shell脚本的运行方法一、作为可执行程序解释 二、作为解释器&#xff08;bash&#xff09;参数 5、find、grep、xargs、sort、…

网络协议与攻击模拟-17-DNS协议-报文格式

二、DNS 查询 客户机想要访问www.baidu.com&#xff0c;根据自己的 TCP / IP 参数&#xff0c;向自己的首选 DNS 服务器发送 DNS 请求 首选 DNS 收到客户机的请求后&#xff0c;会去查询自己的区域文件&#xff0c;找不到www.baidu.com的 IP 地址信息&#xff08;将请求转发到…