【Linux】进程控制 — 进程终止 + 进程等待

news2025/1/13 10:01:49

文章目录

  • 📖 前言
  • 1. 再次理解fork()函数
    • 1.1 fork()之后子进程代码和数据问题:
    • 1.2 fork()之后操作系统做了什么:
    • 1.3 为什么要写时拷贝??
  • 2. 进程终止
    • 2.1 main函数的返回值:
    • 2.2 exit() 和 _exit():
    • 2.3 关于终止,内核做了什么??
  • 3. 进程等待
    • 3.1 为什么要有进程等待:
    • 3.2 wait():
    • 3.3 waitpid():
    • 3.4 获取子进程status:
    • 3.5 阻塞等待
    • 3.6 非阻塞等待
  • 4. makefile的新增知识点

📖 前言

在此之前,我们学过进程的概念,进程的状态,进程地址空间等一系列进程相关的问题。本章我们继续学习进程,我们要来学习一下进程的控制,关于进程等待,进程替换等问题。


1. 再次理解fork()函数

1.1 fork()之后子进程代码和数据问题:

在此之前我们学过如何创建子进程【复习传送门】,我们之前讲的是,fork之后父子进程共享代码,在学完【进程地址空间】,之后我们知道进程具有独立性,所以进程之间为了做到互不影响,所以在创建子进程时候,一旦子进程要对父进程的数据进行修改时,就会发生写时拷贝。

  • fork之前父进程独立执行,fork之后,父子两个执行流分别执行
  • fork之前只有父进程执行
  • fork之后父子进程代码共享
  • 进程具有独立性,代码和数据必须独立的
  • 因为代码只能读取,所以就不会有人写入,更不会发生写实拷贝
  • 这就是父子进程代码共享的原因
  • 通常父子代码共享,父子再不写入时,数据也是共享的
  • 当任意一方试图写入,便以写时拷贝的方式各自一份副本
  • 那么fork之后,是否只有fork之后的代码是被父子进程共享的??
  • 这种说法并不准确
  • 一般情况下,fork之后,父子共享所有的代码!!
  • fork之后本质上是父子将所有的代码全部都共享了,只不过子进程只能从这里(fork)开始执行。
  • fork之后,父子进程谁先运行并不能给答案,是调度器给答案。

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。 注意,fork之后, 谁先执行完全由调度器决定。

子进程执行的后续代码 != 共享的所有代码只不过子进程只能从这里开始执行!!

为什么:?

  • eip程序计数器会拷贝给子进程,子进程便从该eip所指向的代码处开始执行啦!
    在这里插入图片描述
  • eip叫做:保存当前正在执行指令的下一条指令(保存当前进程执行到什么位置)
  • 也叫做:pc指针
  • eip属于当前父进程的上下文,会被拷贝到子进程当中

fork之后的代码父子都会跑,但并不代表,之 前的代码子进程不能拿到。如果愿意是可以拿到的,怎么个愿意法呢?
将子进程的eip改成main函数的入口,子进程就从头开始执行了,这种情况是存在的。

总结:

  • fork之后父子进程必须保证独立性,就必须保证代码和数据互相独立,数据以写时拷贝的方式互相独立,代码是共享的。

1.2 fork()之后操作系统做了什么:

  • 进程调用fork,当控制转移到内核中的fork代码后,内核做:
  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
  • 进程 = 内核的进程数据结构 + 进程的代码和数据

创建子进程的内核数据结构(struct task_struct + struct mm_struct + 页表) + 代码继承父进程,数据以写时拷贝的方式,来进行共享或者独立。

1.3 为什么要写时拷贝??

在这里插入图片描述
创建子进程的时候,就把数据分开,不行吗??

  • 父进程的数据,子进程不一定全用,即便使用,也不一定全部写入(会有浪费空间的嫌疑)。
  • 最理想的情况,只有会被父子修改的数据,进行分离拷贝。不需要修改的共享即可(但是从技术角度实现复杂)。
    • 如果我只把我用的数据拷贝一份,如果这个拷贝的数据我不写的话,那么这不还是浪费空间吗?
    • 就像char*p = "hello world",只读的并不写入,此时就不需要两份。
    • 更重要的是这种技术很难实现,只有子进程的代码跑了才知道要用什么数据,要修改什么变量。
  • 如果fork的时候,就无脑拷贝数据给子进程,会增加fork的成本(内存和时间)。

所以最终采用写时拷贝:

  • 只会拷贝父子修改的,变相的,就是拷贝数据的最小成本。
  • 拷贝的成本依旧存在。

写时拷贝是一种延迟拷贝的策略~~

写时拷贝(Copy-On-Write,简称COW)是一种延迟拷贝(Lazy Copy)技术。它在需要修改共享资源时,会先复制一份相应的资源副本,并且只有在修改时才会将原始资源复制一份,从而避免了不必要的复制和浪费。这种技术可以用于内存管理、文件系统等方面,可以提高系统的性能和资源利用率。由于写时拷贝只有在需要修改时才会进行复制,因此也被称为“延迟拷贝”技术。

最大的价值:

  • 只有真正使用的时候,才给写实拷贝。

最大的意义:

  • 需要时,但是不立马使用的空间,先不给该空间。
  • 那么也就意味着该控件,可以先给别的需要用的地方。
  • 变相的提高内存的使用率!!

写时拷贝本身就是有OS的内存管理模块完成的!


2. 进程终止

2.1 main函数的返回值:

我们在学习C/C++的时候,main,是入口函数,而我们清一色return 0

  • return 0,给谁return?
    • 让父进程读取的,表征进程退出的信息。
  • 为什么是return 0,其他值可以吗?

可以使其他返回值,进程代码跑完,结果是否正确:

  • 0:sucessy
  • 非零:失败()

main函数的代码,应该在实现对应逻辑的时候,对要完成的工作做判定,代码跑完结果正确才能返回0,否则返回非零值。

成功了话,知道成功就可以了,失败的话:

  • 最想知道的是,失败的原因!
  • 所以:非零标识不同的原因!
  • return X,X进程退出码。
  • echo内置命令, 让bash执行自己内部的函数:

我们把退出码设置成非零:

#include <stdio.h>

int main()
{
    return 123;
}

在bash中,最近一次执行完毕时,对应进程的退出码!

在这里插入图片描述
系统中指令代码可没有无脑return 0,当我们随便输入一个指令的时候:

在这里插入图片描述
一般而言,失败的非零值我该如何设置呢??以及默认表达的含义?

  • 我们可以自定义
  • 错误码退出码可以对应不同的错误原因,方便定位问题!

2.2 exit() 和 _exit():

常见进程退出:

  1. 代码跑完,结果正确。
  2. 代码跑完,结果不正确。
  3. 代码没跑完,程序异常了。

进程的结果鉴定是由退出码和退出信号共同决定。

终止的创建做法:

  • 1.在main函数中return。为什么其他函数不行呢??
    • 因为只有main函数return叫进程退出,其他地方叫函数返回
  • 2.在自己的代码任意地点中,调用exit() — 进程退出
    • 带的参数就是退出码。

exit() 和 _exit()的区别:

  • exit终止进程刷新缓冲区。
  • exit直接中止进程,不会有任何刷新操作。

exit():

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void func()
{
	printf("hehe");
    exit(111);
}

int main()
{
    func();

    return 123;
}

在这里插入图片描述
缓冲区刷新了~

_exit():
在这里插入图片描述
缓冲区没刷新~

exit调用了_exit:

在这里插入图片描述

2.3 关于终止,内核做了什么??

  • 进程 = 内核的进程数据结构 + 进程的代码和数据

首先进入Z状态,父进程会去等待它回收子进程的信息,读取退出时的一些信息,然后将进程设置成X状态,这时才真正的退出,释放内核结构,释放曾经进程加载到内存所对应的代码和数据。

  • struct task_struct && struct mm_struct 都是数据结构,操作系统可能并不会释放该进程的内核数据结构。

Liunx可能会维护一张废弃的数据结构链表:

  • 创建对象:1. 要开辟空间,2. 要初始化,当反复高频这种操作时候,将会大大影响效率。
  • 内核的数据结构缓冲池,slab分派器(在操作系统里面)
    • 高频的操作,直接就不再对结构重新申请。
    • 直接将数据结构缓存起来,要就拿,不要就还回来。

3. 进程等待

3.1 为什么要有进程等待:

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼” 的kill -9也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

进程等待是必要的:

  • 不是所有父进程都需要关心子进程。
  • 一个进程应该获取子进程的退出状态。
  • 进程的退出是有退出码的,要获取进程的退出状态

确实存在一些情况,父进程不等待子进程的情况,信号部分讲解…

3.2 wait():

  • 将进程由僵尸状态变为释放状态:

在这里插入图片描述

pid_t wait(int *status);
  • status为输出型参数,获取子进程退出状态,不关心则可以设置成为NULL~~
  • 等待成功时,返回进程的pid,等待失败则返回-1

wait等待代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程创建成功
        while(1)
        {
            printf("我是子进程, 我正在运行...Pid: %d\n", getpid());
            sleep(1);
        }
    }
    else
    {
        //父进程
    
        //想看到等待成功之后僵尸进程就不见了
        printf("我是父进程:pid:%d, 我准备电脑等待子进程啦\n", getpid());
        sleep(30);
        
        pid_t ret = wait(NULL);

        if(ret < 0)
        {
            printf("等待失败!\n");
        }
        else 
        {
            printf("等待成功: result: %d\n", ret);
        }

        //父进程等20后再退出
        sleep(20);
    }
}

看到三个状态:

  1. 子进程由S状态变成Z状态
  2. Z状态在打印完 “等待成功” 时候就没了
  3. 等待的返回值就是子进程的pid
  • 要在30s之内将子进程干掉,干掉之后未满30s的时候子进程处于僵尸状态~~
  • 30s之后父进程一醒来就直接把子进程回收掉,就看到了Z状态没了(子进程被掉了)。
  • wait之后子进程就相当于退出了。

写一个监控脚本:

while :; do ps axj | head -1 && ps axj |  grep test | grep -v grep ; echo "--------------------------------------------------------------------"; sleep 1; done

kill -9之后,子进程处于僵尸状态:

在这里插入图片描述
父进程醒来之后直接把僵尸状态的子进程回收掉了:

在这里插入图片描述
wait()的方案可以解决回收子进程Z状态,让子进程进入X。

3.3 waitpid():

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

返回值:

  • 返回值大于0:等待子进程成功,返回值就是子进程的pid
  • 返回值小于0:等待失败
  • 当正常返回的时候waitpid返回收集到的子进程的进程ID
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

pid:

  • pid值大于0:是几就代表等待哪一个子进程, 指定等待
  • pid值等于-1:等待任意进程

status:

  • 这个参数,是一个输出型参数。
  • 通过调用该函数,从函数内部拿出来特定的数据。
  • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

wait/waitpid()是系统统调,通过输出型参数从从子进程的task_ struct中拿出子进程退出的退出码!

子进程会将自己的退出信息写入task_ struct~~

子进程一旦死掉,父进程直接把子进程退出码拷贝到自己(通过waitpid传进来的int* status参数,父进程就拿到了子进程的退出结果)

options:

  • 0:阻塞等待
  • WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。(非阻塞等待)

3.4 获取子进程status:

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
在这里插入图片描述
次低八位得到子进程的退出码,最低七位是终止信号。

3.5 阻塞等待

如果子进程还在运行,则父进程会被阻塞等待,直到子进程退出或被终止,才能继续执行下去。

阻塞等待,父进程等的时候,子进程压根没退出的,父进程只能阻塞式的等,只有等到子进程退出之后才能正式拿出来这里的int* status

阻塞等待验证:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        //子进程创建成功
        while(1)
        {
            printf("我是子进程, 我正在运行...Pid: %d\n", getpid());
            sleep(1);
            int* p = NULL;
            *p = 100;
        }
    }
    else
    {
        //父进程
        int status = 0;
        printf("我是父进程:pid:%d, 我准备电脑等待子进程啦\n", getpid());
        pid_t ret = waitpid(id, &status, 0);
        if(ret > 0)
        {
            //status >> 8 并不影响status的值
            //status >>= 8 才影响status的值
            printf("wait success, ret : %d, 我所等待子进程的退出码: %d, 退出信号是: %d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
        }
    }

    return 0;
}

我们只需要对status进行位操作,就能拿到对应的退出码和退出信号。

我们很显然做了一个对空指针的解引用:

在这里插入图片描述
我们能看到退出信号是11号信号,我们来看一下11号信号是什么:

在这里插入图片描述
SIGSEGV的全称是Segmentation Violation,即”段错误”。

用宏来获取退出码:

在这里插入图片描述

3.6 非阻塞等待

与阻塞等待不同的是,非阻塞等待不是子进程不退出就一直在那里等,而是多次调用非阻塞接口轮询检测!

非阻塞等待时,条件不满足会直接就返回,此时用户不会因为调用了waitpid () ,让自己阻塞住,一旦条件不满足该函数会立刻返回,父进程或者用户可以继续在返回之后空闲时间段内,做自己的事,做一会之后再去调用waitpid(),这种监测方式叫做非阻塞。

验证非阻塞等待:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

//非阻塞等待验证
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程 
        //死循环跑不完,但是代码出现异常了,进程收到信号,信号终止了子进程,父进程就要知道
        while(1)
        {
            printf("我是子进程,我的PID : %d, 我的PPID : %d\n", getpid(), getppid());

            sleep(3);
        }

        exit(111);
    }
    else if(id > 0)
    {
        //父进程
        //基于非阻塞的轮询等待方案
        
        int status = 0;
        while(1)
        {
            pid_t ret = waitpid(-1, &status, WNOHANG);
            if(ret > 0)
            {   
                printf("等待成功, %d, exit sig: %d, exit code: %d\n", ret, status & 0x007F, (status & 0xFF00) >> 8);
                break;
            }
            else if(ret == 0)
            {           
                //等待成功了,但是子进程没有退出 -- 函数调用成功了,只不过是在非阻塞状态
                printf("子进程好了没,还没,那么父进程就做其他事情...\n");
                sleep(1);
            }
            else 
            {
                //出错了,暂时不处理
            }
        }
    }
    else 
    {
        //do nothing         
    }
    
    return 0;
}

在非阻塞等待的时候,子进程还没退出,父进程虽然是在等子进程,但是不是卡住在那里等待,而是可以做其他的事情:

#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

typedef void (*handler_t)();

std::vector<handler_t> handlers;

void func1()
{
    printf("hello, 我是方法1\n");
}

void func2()
{
    printf("hello, 我是方法2\n");
}

void func3()
{
    printf("hello, 我是方法3\n");
}

void Load()
{
    //加载方法 -- 如果让父进程做更多的事情,把更多的方法加载进去就可以了
    handlers.push_back(func1);
    handlers.push_back(func2);
    handlers.push_back(func3);
}

//C++方法集
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程 
        //死循环跑不完,但是代码出现异常了,进程收到信号,信号终止了子进程,父进程就要知道
        while(1)
        {
            printf("我是子进程,我的PID : %d, 我的PPID : %d\n", getpid(), getppid());

            sleep(3);
        }

        exit(111);
    }
    else if(id > 0)
    {
        //父进程
        //基于非阻塞的轮询等待方案
        
        int status = 0;
        while(1)
        {
            pid_t ret = waitpid(-1, &status, WNOHANG);
            if(ret > 0)
            {   
                printf("等待成功, %d, exit sig: %d, exit code: %d\n", ret, status & 0x007F, (status & 0xFF00) >> 8);
                break;
            }
            else if(ret == 0)
            {           
                //等待成功了,但是子进程没有退出 -- 函数调用成功了,只不过是在非阻塞状态
                printf("子进程好了没,还没,那么父进程就做其他事情...\n");

                if(handlers.empty()) Load();
                for(auto f : handlers)
                {
                    f(); //回调处理对应的任务 
                }

                sleep(1);
            }
            else 
            {
                //出错了,暂时不处理
            }
        }
    }
    else 
    {
        //do nothing         
    }
    
    return 0;
}

上述代码用了回调函数来实现调用方法。

补充:

  • 阻塞的本质是进程阻塞,把进程阻塞是要改进程状态的,R -> S
  • 把进程的PCB从运行队列放到等待队列,这都是操作系统干的。
  • waitpid0恰好是系统调用,内部的代码里面,就自动会有对进程的一系列操作。

当我们调用某些函数的时候,因为条件不就绪,需要我们阻塞等待,本质:就是当前进程自己变成阻塞状态,等条件就绪的时候,在被唤醒!


4. makefile的新增知识点

在这里插入图片描述

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

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

相关文章

以京东首页为例,设计用例框架。

以下是一个可能的京东首页的用例框架设计&#xff1a; 1. 区域划分&#xff1a; a. 顶部导航栏&#xff1a;包括京东的Logo、搜索框、登录/注册入口、购物车等。 b. 主要内容区域&#xff1a;展示各类商品、促销活动、广告位等。 c. 商品分类导航&#xff1a;提供各类…

复习之Linux下的文件管理

1.文件的建立 #touch westos-------建立空文件/修改文件的建立时间 &#xff08;1&#xff09;建立空文件 &#xff08;2&#xff09;修改文件的建立时间 ----右击点属性显示文件的建立时间 ---- 再次输入touch westos,westos文件的建立时间更新&#xff01; -----westos -t…

使用 Kafka Assistant,为您的开发加速

简要介绍 快速查看所有 Kafka 集群&#xff0c;包括Brokers、Topics和Consumers支持各种认证模式&#xff1a;PLAINTEXT、SASL_PLAINTEXT、SSL、SASL_SSL对Kafka集群进行健康检查查看分区中的消息内容并添加新消息查看消费者订阅了哪些主题&#xff0c;以及分区被分配给了哪些…

金融、医疗、教育等各场景下小程序SDK的应用

近年来&#xff0c;随着数字经济的飞速发展和移动终端的迅速普及&#xff0c;移动互联网全面覆盖&#xff0c;各类应用服务层出不穷&#xff0c;涵盖了方方面面的生活、工作和学习。 而小程序作为一种轻量级的应用形态&#xff0c;越来越受到开发者和用户的欢迎。为了满足不同行…

DataNode启动报错Failed to add storage directory [DISK]file:【已解决】

Failed to add storage directory [DISK]file hadoop启动后缺少DataNode进程报错out文件报错log文件解决 hadoop启动后缺少DataNode进程 jps查看hadoop进程缺少DataNode的进程 报错out文件 查看DataNode的out日志 DataNode启动报错 ulimit -a for user root core file size…

前k个高频单词

&#x1f495;**不要害怕前方的未知和困难&#xff0c;因为它们都是你成长的机会。不要过于在意别人的眼光和评价&#xff0c;因为唯有你的内心才知道自己真正的价值。珍惜当下&#xff0c;享受生活的点滴&#xff0c;让自己变得更加坚强、自信、成熟。**&#x1f495; &#x…

LG Gram 14 (14Z90N) 电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板LG Gram 14 (14Z90N) 处理器Intel Core i5-1035G4已驱动 内存M471A1G44AB0-CWE * 2已驱动 硬盘MTFDHBA512TDV-1AZ1AABYY已驱动 显卡Intel Iris …

Axel – 用于 Linux 的命令行文件下载加速器

动动发财的小手&#xff0c;点个赞吧&#xff01; 如果您是那种喜欢下载和试用多个 Linux 发行版的人&#xff0c;我们相信您会张开双臂欢迎一个说到做到的下载加速器——一个按照其描述进行操作的下载加速器。 在本指南[1]中&#xff0c;我们将向您介绍 Axel&#xff0c;这是一…

由浅入深Dubbo核心源码剖析高可用集群

目录 1 服务集群的概述1.1 概述1.2 调用过程1.3 组件介绍 2 集群容错机制2.1 内置集群容错策略2.2 集群容错调优2.3 源码分析 3 集群负载均衡策略3.1 负载均衡的主要作用3.2 内置的负载均衡策略3.3 负载均衡总结 4 服务治理4.1 服务治理的概述4.2 执行过程4.3 服务治理功能 1 服…

由浅入深Dubbo核心源码剖析服务暴露与发现

目录 1 概述2 Spring中自定义Schema2.1 案例使用2.2 dubbo中的相关对象 3 服务暴露机制3.1 术语解释3.2 流程机制3.3 源码分析3.4 总结 4 服务发现4.1 服务发现流程4.2 源码分析4.3 总结 1 概述 dubbo是一个简单易用的RPC框架&#xff0c;通过简单的提供者&#xff0c;消费者配…

《深入理解Java虚拟机》Java虚拟机的监控及诊断工具相关命令行

《深入理解Java虚拟机》Java虚拟机的监控及诊断工具相关命令行 1.jps 查看当前系统正在运行的java进程 相关参数 -l 打印模块名以及包名 -v 打印虚拟机相关参数 -m 打印传给主类的参数 -mlv 以上内容都打印 2.jstat 打印目标 Java 进程的性能数据 -gc 打印gc回收相关信息…

基于ResNet-18实现Cifar-10图像分类

目录 1、作者介绍2、数据集介绍2.1Cifar-10数据集介绍&#xff1a; 3、ResNet网络介绍3.1Residual Network残差网络3.2ResNet18网络结构 4、代码复现及实验结果4.1训练代码4.2测试代码4.3实验结果 1、作者介绍 安耀辉&#xff0c;男&#xff0c;西安工程大学电子信息学院&…

144 Tops,特斯拉如何低成本实现了城市NOA?

作者 | 树人 编辑 | 德新 根据特斯拉2022年Q4的财务文件披露&#xff1a;FSD Beta已有将近40万用户。 这是目前全世界部署规模最大的城市NOA系统。 而特斯拉实现这样一套系统&#xff0c;在车端几乎仅用了8个摄像头和144 Tops算力的FSD计算平台。这种性能压榨和成本控制能力让…

2023年内网穿透常用的几个工具

作为一名开发者&#xff0c;先给大家普及一下什么是内网&#xff0c;什么是外网。 所谓内网就是内部建立的局域网络或办公网络。比如一家公司或一个家庭有多台计算机&#xff0c;他们利用不同网络布局将这一台或多台计算机或其它设备连接起来构成一个局部的办公或者资源共享网…

这可能是最全面的Java面试八股文了

Java的特点 Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。 Java具有平台独立性和移植性。 Java有一句口号&#xff1a;Write once, run anywhere&#xff0c;一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编…

2024王道数据结构考研丨第五篇:树、图

2024王道数据结构考研笔记专栏将持续更新&#xff0c;欢迎 点此 收藏&#xff0c;共同交流学习… 文章目录 第五章&#xff1a;树5.1树的基本概念5.1.1树的定义5.1.2基本术语5.1.3树的性质 5.2二叉树的概念5.2.1 二叉树的定义与特性5.2.2几种特殊的二叉树5.2.3二叉树的存储结构…

2022级云曦实验室考试(一)reverse

一.Reverse 打开后是个rar压缩包&#xff0c;解压后 发现这玩意儿&#xff0c;我也不知道是个啥&#xff0c;之前没做过这类题 浅搜一下 啊&#xff0c;看不懂 用一下自己的歪办法 用txt打开看看有没有啥&#xff1f; 发现两个里面都有相同的flag&#xff0c;改成正确格式&…

C语言小游戏--三子棋

目录 问题描述 逻辑分析 具体实现 1.进入菜单界面 2.初始化棋盘 3.打印棋盘 4.玩家下棋 5.电脑下棋 6.判断输赢 运行结果 完整代码 game.h game.c test.c 问题描述 结合C语言所学知识&#xff0c;简单实现一个三子棋小游戏。 逻辑分析 进入菜单界面初始化棋盘…

帅地这些年看过的书

大家好&#xff0c;我是帅地。 好久没有给大家推荐书籍了&#xff0c;我一般很少给大家推荐书籍&#xff0c;因为自己没看过的&#xff0c;基本不推&#xff0c;只推荐我自己看过且自己自认为不错的书籍。 因为我自己本身是凭借着扎实的基础拿到大厂 offer 的&#xff0c;所以…

nodej+vues汽车销售4s店服务平台商城系统购物车积分兑换7z9d2

在经济快速发展的带动下&#xff0c;汽车服务平台的发展也是越来越快速。用户对汽车服务信息的获取需求很大。在互联网飞速发展的今天&#xff0c;制作一个汽车服务平台系统是非常必要的。本系统是借鉴其他人的开发基础上&#xff0c;用MySQL数据库和nodejs定制了汽车服务平台系…