【Linux系统编程】进程的退出与等待

news2024/11/18 17:37:19

进程的创建

        fork()用于创建子进程。但fork创建的子进程获得的是父进程(即调用 fork() 的进程)的一份几乎完全相同的副本,包括父进程的代码、数据、堆、栈和数据结构等内容。当进程调用fork后,一旦控制转移到内核中的fork代码后,内核将会做以下三点:

        1,分配新的内存块和内核数据结构给子进程

        2,将父进程部分数据结构内容拷贝至子进程,这里是浅拷贝

        3,添加子进程到系统进程列表当中

        4,fork返回,开始调度器调度

        这里要说明一下,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。这里父子进程执行的先后顺序完全由调度器决定。退出时父进程是最后退出的,因为父进程要回收子进程。这里注意,当系统有太多进程或实际用户的进程数超过了限制,fork调用就会创建失败。具体内部流程如下图:

        修改之前,父子进程在指针指向方面完全相同,也就是C++中的浅拷贝。当内核调用fork创建子进程后,并不会立即复制父进程的所有内存页给子进程,而是让子进程与父进程共享相同的内存页。只有当父进程或子进程尝试修改某个共享的内存页时,操作系统才会为修改者复制那一页内存,从而确保每个进程都有自己独立的数据副本。这种机制就叫做写时拷贝。写时拷贝减少了不必要的资源复制,它可以降低系统的开销,提高资源的利用率。

        通过以上图观察,在页表中还存储着一个权限的限制。平常的代码存储是按照虚拟地址进行存储的,所以当我们对代码进行操作时,这时会对页表中对应代码的权限进行访问,然后对其进行操作。比如对常量字符串进行修改时,内部就是页表对其权限的限制不可被修改。但注意,编译器在编译时出错,这只是编译器内部单纯对其做出的报错处理,与系统无关。总的来说系统防御的是运行报错,编译报错依靠编译器预防。

进程的退出

        进程退出场景共分为三种:1,代码运行完毕,结果正确。2,代码运行完毕,结果不正确。3,代码异常终止。

        首先,我们先来观察进程正常运行时的情况。进程的正常退出都是从main函数中退出的。在平常写C/C++代码中的大多情况下,我们都会在进程代码的main函数最后写上return 0。return 0其实表示进程返回0的退出码,以此退出码0来表示进程的正常退出,即进程退出码(一般情况下0表示进程退出成功,非0表示进程退出失败)。

        进程退出码在Linux中使用 echo $? 指令可打印查看。

[zhujunhao@bogon code]$ cat code.cpp
#include <iostream>
using namespace std;
int main() 
{
    return 10; //进程退出码设置为10
}

[zhujunhao@bogon code]$ g++ code.cpp -o code.exe
[zhujunhao@bogon code]$ ./code.exe
[zhujunhao@bogon code]$ echo $?
10

//当多次使用时,系统会记录最近一次进程执行完毕时的退出码

[zhujunhao@bogon code]$ echo $?  //记录此时的echo进程,执行成功,退出码为0
0
[zhujunhao@bogon code]$ echo $?    
0

[zhujunhao@bogon code]$ ls -ruowvv
ls: invalid line width: vv
[zhujunhao@bogon code]$ echo $?   //上一个ls进程运用错误,退出码为2
2

        进程退出码在C/C++中不仅可使用return 0表示,也可使用接口 exit(退出码)或_exit 指定退出码表示。exit与_exit不同的是_exit在退出时不会清理专门的缓冲区,exit调用时会先刷新缓冲区并关闭流等,然后调用_exit退出进程,所以在exit和_exit之间,最好使用exit。

        两者虽然都是用来表示进程正常退出,但不同的是 exit 退出进程是无论在哪个函数(main函数和其它普通函数)中运用都会直接将此进程退出。return退出必须在main函数中退出,其它普通函数只会当成返回值处理。在exit和return之间,return是一种更常见的退出进程方法。执行 return n 等同于执行exit(n),因为调用main的运行时,函数会将main的返回值当做 exit 的参数。

        异常处理是通过系统内部的异常信号来实现。当进程代码抛不同异常时,内部就会发送不同的异常信号,然后通过异常信号返回指定的异常退出码,这里每一个异常退出码对应一个异常信息。我们通过使用  strerror(i) 接口查看第i个的错误码所对应的错误信息,此接口参数通过与接口 errno(返回此进程的退出码。正常退出返回0,异常退出返回指定的错误码) 连用,即strerror(errno)。如下,这里我们模拟实现系统中50个异常退出码对应的异常信号。

[zhujunhao@bogon code]$ cat code.cpp
#include <iostream>
#include <cstring>
#include <errno.h>
using namespace std;
int main() 
{
    for (int i = 0; i < 50; i++) 
    {
        cout << i << " : " << strerror(i) << endl;
    }
    return errno;  //返回进程退出码
}

[zhujunhao@bogon code]$ g++ code.cpp -o code.exe
[zhujunhao@bogon code]$ ./code.exe

//以下是退出码对应的进程代码说明
0 : Success
1 : Operation not permitted
............ 

        与此函数功能相同的还有接口 perror 直接输出错误信息。

        错误码转换成错误描述除了以上使用语言和系统自带的方法转换外还有自定义的形式实现。

[zhujunhao@bogon code]$ cat code2.cpp
#include <iostream>
#include <string>
#include <unistd.h>
using namespace std;
enum {
    A=0,
    B,
    C
};
const string ToDesc(int code) 
{
    switch(code) 
    {
        case A:
            return "A error";
        case B:
            return "B error";
        case C:
            return "C error";
        default:
            return "success";
    }
}
int main() 
{
    int code = C;
    cout << ToDesc(code) << endl;
    return code;
}
[zhujunhao@bogon code]$ g++ code2.cpp -o code2.exe
[zhujunhao@bogon code]$ ./code2.exe
C error

[zhujunhao@bogon code]$ echo $?
//自定义返回退出码为数值2

进程等待

        父进程创建子进程是为了让子进程完成某个特定的任务。但若子进程创建后,父进程如果不管不顾,就可能造成 ‘僵尸进程’ 的问题,因为僵尸进程只是释放了一部分资源,另一部分资源需要父进程收到退出信息后才可释放,所以僵尸进程会造成内存泄漏问题。另外,进程一旦变成僵尸状态,kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。最后,父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成、结果对还是不对、 或者是否正常退出等。这些信息需要父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

        进程等待的方法有两种:wait方法和waitpid方法。它们都是用来获取子进程的信息并且也可以处理僵尸进程。如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。

        当调用wait时,如果没有运行完成的子进程,父进程会阻塞等待,直到有一个子进程结束,然后wait函数返回。当调用waitpid接口时,父进程即可选择进入阻塞状态,又可选择进入非阻塞状态。

        在Linux中,wait函数是用于等待子进程结束并获取子进程的终止状态的系统调用。它在父进程中使用,用于等待其子进程终止并获得子进程的退出状态。waitpidwait函数相比提供了更多的灵活性,允许父进程指定要等待的子进程以及控制等待的行为。

wait和waitpid的头文件

#include<sys/types.h>
#include<sys/wait.h>

接口类型

pid_t wait(int* status);

pid_ t waitpid(pid_t pid, int* status, int options);

wait返回值:

        如果有一个或多个子进程结束,wait函数会直接成功返回被等待的一个进程的pid,如果该进程没有子进程,wait函数会失败,返回-1。

waitpid返回值:

        当正常返回的时,waitpid返回收集到的子进程的进程ID;
        如果设置了选项WNOHANG,即不在阻塞等待,进入非阻塞等待。而调用中waitpid发现没有已退出的子进程可收集,返回值为0; 
        如果调用中出错,即没有子进程,则返回-1,这时errno会被设置成相应的值以指示错误所在;
          
status参数:

        此参数为输出型参数,用来获取子进程退出状态, 不关心则可以设置成为NULL。

pid参数:
        Pid = -1,等待任一个子进程。与wait等效。
        Pid > 0,等待专门子进程的pid。

options参数:

        options可设为两种状态,数值为0时表示阻塞等待,数值不为0时表示非阻塞等待。

        这里我们先研究status。status指向的整数不能简单的当作整形来看待,这个整数是一个特殊的位掩码,用于存储子进程的退出状态信息或接收到的信号信息。这个位掩码具有自己的特定格式,它一共有32位比特位。前16位暂时我们先不用管,后面会具体解释,这里先只考虑status后16比特位,具体细节如下图:

        当进程正常退出时,系统抛出信号0表示正常,退出状态返回退出码。当进程出现异常时,进程将会收到系统下的终止信号,被信号所杀。这里在系统处理进程时,系统会优先查看是否收到了终止信号字段,若后六位的终止信号字段是0,表示没有收到异常信号,这时会查看退出状态,若也是0,表明退出码正常。若终止信号字符是0,进程状态非0,表明代码正常跑完了,但是结果不正确,不正确的原因由退出码表明。若终止信号字段非0,退出状态就不用看了,表明代码运行的中间出了问题。系统就是通过这样的信号与退出码来标明进程的退出状态。 

        这里简单说明一下信号。在Linux中,使用 kill -l 指令可查看进程信号,信号数字对应的就是处理功能(功能与数子之间是宏关系,也就是说即可使用数字表示功能,也可使用具体的名字)。当我们使用 kill -9 来强制杀掉指定的pid进程时,其实就是向系统发送信号9所对应的功能来处理指定pid的进程。

        下面我们观看当status为空时的情况:

[zhujunhao@bogon code]$ cat code.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;
int main() 
{
    pid_t id = fork();
    // Child
    if (id == 0) 
    {
        int cent = 5;
        while (cent) 
        {
            cout << "Child is Running, pid: " << getpid() << "  ppid: " << getppid() << endl;
            sleep(1);
            cent--;
        }
        cout << "子进程准备退出,马上变僵尸进程" << endl;
        exit(0);
    }
    // Father
    pid_t fid = wait(NULL);  //阻塞等待。这里不关心子进程退出状态
    if (fid > 0)
     {
        cout << "Father pid is: " << getpid() << endl;
        cout << "wait success, fid = wait(NULL): " << fid << endl;
    }
    return 0;
}
[zhujunhao@bogon code]$ g++ code.cpp -o code.exe
[zhujunhao@bogon code]$ ./code.exe
Child is Running, pid: 19283  ppid: 19282
Child is Running, pid: 19283  ppid: 19282
Child is Running, pid: 19283  ppid: 19282
Child is Running, pid: 19283  ppid: 19282
Child is Running, pid: 19283  ppid: 19282
子进程准备退出,马上变僵尸进程
Father pid is: 19282
wait success, fid = wait(NULL): 19283    //返回子进程的pid

        然后再来观察当status不为空时的情况。

[zhujunhao@bogon code]$ cat code.cpp
#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;
int main() 
{
    pid_t id = fork();
    // Child
    if (id == 0) 
    {
        int cent = 5;
        while (cent) 
        {
            cout << "Child is Running, pid: " << getpid() << "  ppid: " << getppid() << endl;
            sleep(1);
            cent--;
        }
        cout << "子进程准备退出,马上变僵尸进程" << endl;
        exit(1);  //子进程退出码设置为1
    }
    // Father
    int status = 0;
    pid_t fid = wait(&status); //这里也可设置为为waitpid("子进程的专属id", &status, 0);与wait(&status)等价
    if (fid > 0)
     {
        cout << "Father pid is: " << getpid() << endl; 
        cout << "wait success, fid = wait(nullptr) = " << fid << " : status = " << status << endl;
    }
    return 0;
}

[zhujunhao@bogon code]$ g++ code.cpp -o code.exe
[zhujunhao@bogon code]$ ./code.exe
Child is Running, pid: 28938  ppid: 28937
Child is Running, pid: 28938  ppid: 28937
Child is Running, pid: 28938  ppid: 28937
Child is Running, pid: 28938  ppid: 28937
Child is Running, pid: 28938  ppid: 28937
子进程准备退出,马上变僵尸进程
Father pid is: 28937
wait success, fid = wait(nullptr) = 28938 : status = 256  //发现status子进程退出码不是1

        上面说过,退出码是存放在专有的位字符中,存放退出码后,在后16位字段中二进制 0000 0001 0000 0000 == 十进制256。若我们只想提取出子进程的退出码,这里我们可设计一下,如以上代码中 (status>>8) & 0xFF 表示取出子进程的退出码,status & 0x7F 表示收到的信号编号。

        由于位掩码可以表示很多种意思,所以系统专门给我们提供不同的宏来表示不同的功能。如:WIFEXITED(status) 表示若正常终止子进程返回的状态,则为真。可用来查看进程是否是正常退出。WEXITSTATUS(status) 表示若WIFEXITED非零,则提取子进程的退出码。

[zhujunhao@bogon code]$ cat code.cpp
#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;
int main() 
{
    pid_t id = fork();
    // Child
    if (id == 0) 
    {
        cout << "Child is Running, pid: " << getpid() << "  ppid: " << getppid() << endl;
        sleep(1);
        exit(1);  //子进程退出码设置为1
    }
    // Father
    int status = 0;
    pid_t fid = waitpid(id, &status, 0);
    if (fid > 0)
     {
        cout << "Father pid is: " << getpid() << endl; 
        cout << "waitpid success, fid = waitpid(id, &status, 0) = " << fid << " : status = " << status << endl;
        cout << "子进程退出码(status>>8)&0xff: " << ((status>>8) & 0xff) << " 子进程信号编号: " << (status & 0x7f) << endl;
        if (WIFEXITED(status)) 
        {
            cout << "子进程退出码WEXITSTATUS(status): " << WEXITSTATUS(status) << endl;
        }
        else 
        {
            cout << "Chhild process error" << endl;
        }
    }
    return 0;
}

[zhujunhao@bogon code]$ g++ code.cpp -o code.exe
[zhujunhao@bogon code]$ ./code.exe
Child is Running, pid: 31042  ppid: 31041
Father pid is: 31041
waitpid success, fid = waitpid(id, &status, 0) = 31042 : status = 256
子进程退出码(status>>8)&0xff: 1 子进程信号编号: 0
子进程退出码WEXITSTATUS(status): 1

        可看出,由于信号编号为0,以上代码均正常。下面我们观看不正常的情况。这里的进程代码还是以上代码。如下图:

手动信号异常终止

 代码异常终止

这里我们在代码的子进程中添加以下代码

int a = 10;

a /= 0;   //除0错误操作

[zhujunhao@bogon code]$ g++ code.cpp -o code.exe
//这里编译时会显示警告消息

[zhujunhao@bogon code]$ ./code.cpp
bash: ./code.cpp: Permission denied
[zhujunhao@bogon code]$ ./code.exe
Child is Running, pid: 32519  ppid: 32518
Father pid is: 32518
waitpid success, fid = waitpid(id, &status, 0) = 32519 : status = 136
子进程退出码(status>>8)&0xff: 0 子进程信号编号: 8
Chhild process error

 

这里我们在原本代码的子进程中添加以下代码

int *p = NULL;

*p = 200;    //空指针解引用错误操作

[zhujunhao@bogon code]$ g++ code.cpp -o code.exe
[zhujunhao@bogon code]$ ./code.exe
Child is Running, pid: 32791  ppid: 32790
Father pid is: 32790
waitpid success, fid = waitpid(id, &status, 0) = 32791 : status = 139
子进程退出码(status>>8)&0xff: 0 子进程信号编号: 11
Chhild process error

        下面我们来说明一下waitpid第三个参数中的options。waitpid是进入阻塞状态还是进入非阻塞状态跟此参数有关。当options为0时,父进程进入阻塞状态。当options不为0时,父进程进入非阻塞状态。选择进入非阻塞状态通常将options参数设置 WNOHANG。此宏表示若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID

        在非阻塞模式下,父进程可以在不阻塞的情况下轮询 waitpid 来检查是否有子进程已经终止,因此需要这种方式需要使用循环的方式来不断轮询检查。这种方式允许父进程继续执行其他任务,同时仍然能够处理子进程的终止。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;

#define NUM 5

typedef void(*fun_t)();
fun_t tasks[NUM];

void printLog()
{
    cout << "this is a log print task" << endl;
}

void printNet()
{
    cout << "this is a net task" << endl;
}

void printNPC()
{
    cout << "this is a flush NPC" << endl;
}

void initTask()
{
    tasks[0] = printLog;
    tasks[1] = printNet;
    tasks[2] = printNPC;
    tasks[3] = NULL;
}

void excuteTask()
{
    for (int i = 0; tasks[i]; i++) 
        tasks[i]();  // 回调机制
}

int main()
{
    initTask();
    pid_t id = fork();
    if (id == 0)
    {
        cout << "Child: pid = " << getpid() << "  ppid = " << getppid() << endl;
        sleep(5);
        exit(10);
    }
    int status = 0;
    while (true)  // 非阻塞状态,不断循环访问子进程是否退出
    {
        pid_t fid = waitpid(id, &status, WNOHANG);
        if (fid > 0)
        {
            cout << "waitpid success, fid = waitpid(id, &status, 0) = " << fid << "  status = " << status << endl;
            cout << "子进程退出码WEXITSTATUS(status): " << WEXITSTATUS(status) << "  " << "子进程信号编号: " << (status & 0x7f) << endl;
            break;
        }
        else if (fid == 0)
        {
            cout << "Child is running, Father do other thing" << endl;

            // 这里子进程在运行的时候父进程模拟实现运行自己的部分
            cout << "#################### task begin ####################" << endl;
            excuteTask();
            cout << "#################### task end   ####################" << endl;
        }
        else  // 返回值-1的情况
        {
            perror("waitpid");
            break;
        }
        sleep(1);
    }
    return 0;
}

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

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

相关文章

ATTRIBUTE_HELPER_HEADER

ATTRIBUTE_HELPER_HEADER是ns3中的一个宏定义&#xff0c;用于声明类类型的属性值、访问器和检查器。 例如&#xff1a; ATTRIBUTE_HELPER_HEADER (QueueSize);此宏声明&#xff1a; 属性值类typeValue&#xff0c;属性访问器函数MaketypeAccessor&#xff0c;AttributeChec…

CesiumJS 沙盒

CesiumJS 沙盒 通过CesiumJS 沙盒快速测试CesiumJS的一些功能&#xff0c;免去安装开发环境的困恼。 Hello World https://sandcastle.cesium.com/index.html 简单修改&#xff08;F8运行&#xff09;&#xff1a;去掉界面上UI const viewer new Cesium.Viewer("cesi…

C++中的STL-string类

文章目录 一、为什么学习string类&#xff1f;1.1 C语言中的字符串 二、准库中的string类2.2 string类2.3 string类的常用接口说明2.4 string类对象的容量操作2.5 string类对象的访问及遍历操作2.5 string类对象的修改操作2.7 string类非成员函数2.8 模拟实现string 一、为什么…

C++特性之一:继承

1. 派生类的成员变量、成员函数、构造、析构 2. 继承的切片 3. 重定义/隐藏 重定义/隐藏&#xff1a;派生类和基类有同名的成员&#xff0c;就叫隐藏。派生类的成员隐藏了基类的成员。 隐藏时可以通过类作用限定符来访问被隐藏的成员。 class Person { public:void Print(){…

一文了解Spring的SPI机制

文章目录 一文了解Spring的SPI机制Java SPIServiceLoader Spring SPISpringboot利用Spring SPI开发starter 一文了解Spring的SPI机制 Java SPI SPI 全称 Service Provider Interface &#xff0c;是 Java提供的一套用来被第三方实现或者扩展的接口&#xff0c;它可以用来启用…

考研数学——高数:高斯公式

助记: 关于积分时什么时候可以将变量整体代入积分式的问题&#xff1a;在积分过程中&#xff0c;如果某一整体恒为常量&#xff0c;则可以直接替换为定值&#xff0c;常见于对线或面的积分。 而在这题&#xff0c;用高斯公式之前是面积分&#xff0c;如果有这个整体出现的话是…

寒假作业Day 11

寒假作业Day 11 一、选择题 栈满的判断&#xff1a;在链式存储结构中&#xff0c;栈的大小是动态的&#xff0c;只受限于系统分配给程序的内存大小。因此&#xff0c;理论上&#xff0c;链式栈不会因为空间不足而“满”。所以&#xff0c;不需要判断栈满。 栈空的判断&#xf…

解决VS编译中文报错 error C2001:常量中有换行符

产生原因&#xff1a;文件中有中文字符&#xff0c;但是文件是utf-8格式的&#xff0c;使用msvc编译器编译时就会产生上述错误 首先说明&#xff1a;我是通过方法2解决该问题的。 解决办法&#xff1a; 方式1&#xff1a; 通过把源文件转换为gbk编码&#xff0c;但是只能一…

TCP和UDP基础

tcp服务器及客户端链接 ucd服务器及客户端

python自学7

第二章第一节面向对象 程序的格式都不一样&#xff0c;每个人填写的方式也有自己的习惯&#xff0c;比如收集个人信息&#xff0c;可能有人用字典字符串或者列表&#xff0c; 类的成员方法 类和对象 构造方法 挨个传输值太麻烦了&#xff0c;也没有方便点的&#xff0c;有&…

4_springboot_shiro_jwt_多端认证鉴权_Redis存储会话

1. 什么是会话 所谓的会话&#xff0c;就是用户与应用程序在某段时间内的一系列交互&#xff0c;在这段时间内应用能识别当前访问的用户是谁&#xff0c;而且多次交互中可以共享数据。我们把一段时间内的多次交互叫做一次会话。 即用户登录认证后&#xff0c;多次与应用进行交…

WPF —— Calendar日历控件详解

1&#xff1a; Calendar的简介 日历控件用于创建可视日历&#xff0c;让用户选择日期并在选择日期时触发事件。 DisplayMode 用来调整日历显示模式&#xff0c;分为Month、Year 和Decade 三种。如下是None 2&#xff1a;Calendar控件常用的属性 SelectionMode 选中日历的类…

原生php单元测试示例

下载phpunit.phar https://phpunit.de/getting-started/phpunit-9.html 官网 然后win点击这里下载 新建目录 这里目录可以作为参考&#xff0c;然后放在根目录下 新建一个示例类 <?phpdeclare(strict_types1);namespace Hjj\DesignPatterns\Creational\Hello;class He…

Python实战:爬取小红书

有读者在公众号后台询问爬取小红书&#xff0c;今天他来了。 本文可以根据关键词&#xff0c;在小红书搜索相关笔记&#xff0c;并保存为excel表格。 爬取的字段包括笔记标题、作者、笔记链接、作者主页地址、作者头像、点赞量。 一、先看效果 1、爬取搜索页 2、爬取结果保存到…

在IDE中配置tomcat服务器

目录 一、新建一个java项目二、添加web框架三、配置tomcat服务器四、运行访问发布的项目 前言&#xff1a;在 IntelliJ IDEA 中配置 Tomcat 服务器是 Java Web 开发的基础步骤&#xff0c;以下是如何在 IDEA 中设置 Tomcat 并部署 Web 项目的简要指南。 一、新建一个java项目 新…

DVWA-master 存储型xss

什么是存储型xss 存储型xss意味着可以与数据库产生交互的&#xff0c;可以直接存在数据库中 先将DVWA安全等级改为低 先随便写点东西上传 我们发现上传的内容会被显示&#xff0c;怎么显示的呢&#xff1f; 它先是上传到数据库中&#xff0c;然后通过数据库查询语句将内容回显 …

TSINGSEE青犀AI烟火识别等算法打造电瓶车消防安全解决方案

一、背景分析 根据国家消防救援局的统计&#xff0c;2023年全国共接报电动自行车火灾2.1万起&#xff0c;相比2022年上升17.4%&#xff0c;电动自行车火灾安全隐患问题不容忽视。 电瓶车火情主要问题和原因&#xff1a; 电瓶车/电池质量良莠不齐用户安全意识薄弱&#xff0c…

Shell编程入门

Shell编程入门 一、Shell概述1.1 Shell的作用1.2 Linux提供的Shell解释器1.3 Centos默认的解析器是bash 二、Shell脚本入门案例三、变量3.1 系统变量3.2 自定义变量3.3 特殊变量 四、运算符五、条件判断5.1 基本语法5.2 常用判断条件5.3 多条件判断 六、流程控制6.1 if语句6.2 …

山景BP1048 烧录器烧写

1.首先确保硬件连接没问题&#xff0c;烧写器不能亮红灯&#xff0c;亮红灯说明硬件没正确连接。硬件连接如下&#xff1a; 2.点击Flash Burner 3.编程目标闪存选择SDK包自带的烧写驱动器&#xff0c;闪存映像档选择编译好的bin文件。 4.点击刻录 5.看见有进度条在跑&#x…

一文看懂 关系模型-完整性约束

关系模型中有三类完整性约束&#xff1a;实体完整性、参照完整性和用户自定义的完整性。其中实体完整性和参照完整性是关系模型必须满足的完整性约束&#xff0c;被称为关系的两个不变性&#xff0c;由关系系统自动支持。 实体完整性详解&#xff1a; 若属性A是基本关系R的主属…