Linux 进程控制进程终止

目录

一、fork函数

1、概念 

2、父子进程的共享

3、 为什么父子进程会从fork()调用之后的位置继续执行代码

4、写时拷贝

5、为什么需要写时拷贝

6、写时拷贝的好处

7、fork常规用法

8、fork调用失败的原因

9、查看系统最大进程数

二、进程终止

1、进程退出场景

2、查看进程退出码

3、进程终止常见方式

正常终止(main,exit,_exit)

异常退出

4、三种进程终止区别

库函数(如exit())

系统接口(如_exit())

缓冲区

return 与进程退出


一、fork函数

1、概念 

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);

返回值:自进程中返回0,父进程返回子进程id,出错返回-1

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

2、父子进程的共享

  • 当创建子进程时,操作系统会为之分配特定的内核结构,以确保其具有独立性。理论上,这意味着子进程应拥有自己的代码和数据副本。
  • 然而,在实际操作中,我们通常不会经历一个显式的加载过程来为子进程提供独立的代码和数据。这导致子进程实际上是在“共享”父进程的代码和数据。

  • 对于代码部分,由于它是只读的,父子进程共享同一份代码不会引起问题。这种方法既高效又节省内存,因为不需要为每个子进程复制代码。
  • 对于数据部分,情况则大不相同。
    • 由于数据可能会被进程修改,保持数据的独立性变得尤为重要。如果父子进程共享相同的数据副本,任何一方对数据的修改都会影响到另一方,破坏了进程间的独立性。
    • 因此,操作系统采用了一种机制,如写时拷贝,在必要时将数据复制一份,确保子进程在需要修改数据时能够拥有自己的独立数据副本。这样既保证了进程间的独立性,又在不必要的复制操作上节省了资源。

观察下面程序:

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

int main()
{
    pid_t id=fork();
    if(id<0)
    {
        //创建失败
        perror("fork");
        return 1;
    }
    else if(id==0)
    {
        //child process
        while(1)
        {
            printf("I am child, pid:%d, ppid:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else
    {
        //parent process
        while(1)
        {
            printf("I am father, pid:%d, ppid:%d\n",getpid(),getppid());
            sleep(1);
        }   
    }
    printf("you can see me\n");
    sleep(1);//进程退出顺序不一样
    return 0;
}

fork创建进程后,根据id的不同执行父子进程对应的代码,也就是说fork函数会有两个返回值? 

 

  • 使用fork()函数创建一个新的子进程,该子进程是调用进程的副本。fork()之后,程序会有两个不同的执行流,父进程和子进程将会从fork()调用之后的位置继续执行代码。(思考为什么?)
  • 由于fork()在父进程和子进程中都会进行返回,也就是fork()函数内部return会被执行两次:在父进程中返回新创建的子进程的PID(进程ID),在子进程中返回0,所以return的本质就是对变量id进行写入。

3、 为什么父子进程会从fork()调用之后的位置继续执行代码

fork()之后,程序会有两个不同的执行流,父进程和子进程将会从fork()调用之后的位置继续执行代码。

fork之后,父子进程代码共享,是after这部分共享,还是所有的代码都共享?

  • 答案:所有代码都会共享。

解释上面问题前,我们先来复习两个概念:

  • 程序计数器(PC):PC是一个特殊的寄存器,用来存储下一条要执行的指令的地址。在任意时刻,PC都指向程序中当前正在执行或即将执行的代码位置。
  • 进程上下文:当操作系统进行进程切换时,它保存当前进程的状态(包括PC和其他寄存器的值),以便该进程在未来某个时刻继续执行时,能从正确的位置恢复。这个保存的状态集合就是所谓的“进程上下文”。

我们现在来回答刚才的问题: 

  • fork()之后的行为:当fork()被调用时,操作系统会为子进程创建一个与父进程几乎完全相同的副本,包括代码、数据、堆和栈的复制(通过写时复制技术优化),但是父子进程会有各自独立的进程上下文。
  • 尽管它们可能共享相同的代码段,但是各自的PC值会指向fork()调用后的下一条指令,使得父子进程从fork()之后的代码位置继续各自的执行流。这意味着,虽然父子进程在物理上共享代码,但它们执行的路径可以完全不同,因为它们的执行流、栈、寄存器状态等是独立的。

因此,在fork()之后,子进程认为自己的程序计数器(PC)起始值就是fork()调用之后的代码位置,从这一点继续执行,而与父进程的执行流相互独立。这种设计使得父子进程能在共享代码的同时,执行各自独立的任务。

4、写时拷贝

Linux操作系统在使用写时拷贝(Copy-On-Write, COW)技术管理进程内存时,确实会使得父进程和子进程共享代码段,而只有在写操作尝试发生时才会拷贝数据段或者堆栈段。这种设计是为了提高内存使用效率和减少不必要的数据复制。

 

  • 共享代码段:当一个进程(父进程)创建一个子进程时,Linux操作系统会使子进程与父进程共享相同的代码段。这是因为代码是静态的、只读的,不会被进程修改,所以多个进程可以安全地共享同一份代码。这部分共享的代码通常存在于操作系统的物理内存中,在内存的某个位置。

  • 拷贝数据段和堆栈段:对于数据段和堆栈段,原则上是每个进程有自己的一份,以存储各自的全局变量、局部变量、堆分配的内存等。当使用写时拷贝机制时,只有当某个进程试图修改这些共享的数据段或堆栈段中的内容时,操作系统才会为该进程创建这部分内存的私有拷贝,以确保修改不会影响到其他共享这部分内存的进程。

  • 访问代码:当子进程访问代码段时,它通过自己的页表映射到相同的物理内存地址上,这块内存存储了父进程的代码段。由于代码是只读的,所以多个进程(无论是父进程还是子进程)都可以共享这一块内存,而不会造成数据的不一致。

  • 修改数据:当子进程需要修改数据时,操作系统利用写时拷贝机制来处理这一请求。如果子进程尝试写入一个共享的数据页,操作系统会首先为这个数据页创建一个拷贝,然后更新子进程的页表,使得这个被写入的地址映射到这个新的、属于子进程自己的物理内存页上。因此,这次写操作只会影响子进程自己的数据页,而不会影响到父进程或其他共享这个数据页的进程。

通过下面的例子来理解一下: 

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

int g_val=100;

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int n=0;
        while(1)
        {
            printf("I am child. pid: %d, ppid %d, g_val: %d, &g_val: %p\n",\
                    getpid(),getppid(),g_val,&g_val);
            sleep(1);
            n++;
            if(n==5)
            {
                g_val=200;
                printf("child change g_val 100 -> 200 success\n");
            }
        }
    }
    else
    {
        while(1)
        {
            printf("I am father. pid: %d, ppid %d, g_val: %d, &g_val: %p\n",\
                    getpid(),getppid(),g_val,&g_val);
            sleep(1);
        }   
    }
    return 0;
}

我们可以看到,一开始子进程和父进程的g_val的值相同,而且地址也相同。当子进程修改全局变量g_val的值之后,父子进程的g_val的值不同了,但父子进程中g_val的地址依然相同,这是为什么呢?

  • 我们观察到父进程和子进程在子进程修改全局变量g_val之前共享相同的变量值和地址。这是因为Linux采用了写时拷贝(Copy-On-Write, COW)机制来优化内存使用

    • fork()调用之后(子进程未修改g_val时),父进程和子进程实际上共享相同的物理内存页,包括全局变量g_val。这就是为什么一开始g_val的地址在父子进程中看起来相同的原因。

  • 当子进程尝试修改g_val的值时,操作系统侦测到这一写操作,并触发写时拷贝机制。

    • 这时,操作系统为子进程分配一个新的物理内存页,用于存储修改后的g_val值,而父进程继续使用原来的物理内存页。

    • 因此,尽管父子进程中g_val的虚拟地址未变,它们现在指向不同的物理地址,这就是为什么修改后子进程中g_val的值变为200,而父进程中g_val的值仍然是100的原因。

总结来说,

  1. 写时拷贝机制确保了在子进程尝试修改共享数据之前,父子进程可以高效地共享相同的物理内存页。
  2. 一旦发生写操作,操作系统会为子进程分配新的物理内存页,以保持父子进程数据的独立性。

这就解释了为什么在子进程修改g_val之后,父子进程的g_val值不同,但它们的虚拟地址仍然相同的现象。

5、为什么需要写时拷贝

字面意思理解:为何要选择写时拷贝的技术?对父子进程进行分离

  1. 用的时候,再分配,是高效使用内存的一种表现。
  2. 操作系统无法在代码执行前预知那些空间会被访问,所以不提前分配。

写时拷贝本质上是一种延迟分配策略。它允许操作系统延迟数据的复制直到实际需要修改数据时,从而优化内存使用和提高系统效率。

对于数据处理而言,进程创建时采用“写时拷贝”(Copy-On-Write, COW)技术的原因可以从几个方面来理解:

  1. 内存效率:当创建一个进程时,操作系统初期并不直接为子进程拷贝所有父进程的数据空间,因为这样做可能会拷贝大量子进程根本不会用到的数据。这不仅浪费了宝贵的内存资源,还增加了进程创建的时间。通过写时拷贝技术,只有当父进程或子进程尝试写入某个内存区域时,才会实际拷贝这部分内存。这样,只有被修改的数据才会被复制,未修改的数据则可以由父子进程共享,从而极大地提高了内存使用效率。

  2. 不可预知性:在进程运行之前,操作系统通常无法知道哪些具体的数据会被访问或修改。如果采用预先拷贝的方式,可能会拷贝很多最终未被使用或仅被读取的数据。写时拷贝技术允许系统延迟拷贝的决定,直到确实需要修改数据的那一刻,从而更加合理地利用资源。

  3. 优化资源利用:通过延迟数据拷贝,写时拷贝不仅减少了内存的占用,也减轻了操作系统的负担,因为只有在必要时才进行数据复制。这种策略意味着系统可以更有效地管理内存,同时也简化了进程创建和数据管理的过程。

6、写时拷贝的好处

  1. 因为写时拷贝的存在,父子进程得以彻底分离,这样就保证了进程独立性。
  2. 写时拷贝,是一种延时申请技术,可以提高整机内存的使用率。 

7、fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

8、fork调用失败的原因

  • 系统中有太多的进程。
  • 实际用户的进程数超过了限制。

9、查看系统最大进程数

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

int main()
{
    int max_cnt=0;
    while(1)
    {
        pid_t id=fork();
        if(id<0)
        {
            printf("fork error:%d\n",max_cnt);
            break;
        }
        if(id==0)
        {
            while(1)
            {
                sleep(1);
            }
        }
        else
        {

        }
        max_cnt++;
    }
}

输出结果:

删除刚刚用于统计所创建的进程

二、进程终止

进程终止时,操作系统做了什么?

  • 当然要释放进程申请的相关内核数据结构和对应的数据和代码,本质就是释放系统资源。

1、进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

在程序设计和开发中,main函数的返回值扮演着重要的角色,它向操作系统或上一级进程传达了程序执行的最终状态。这个返回值,也被称为退出码(Exit Code)或退出状态(Exit Status),是进程终止时的一个标识,用于表示程序的执行结果。

  1. 代码执行完成,结果正确: 这是最理想的情况,程序按照预期执行完所有的操作,并且输出了正确的结果。在这种情况下,进程通常通过执行完毕其入口点(如C语言的main函数)中的所有代码自然终止。如果是在main函数中,通常会看到一个return 0;语句,表示程序正常退出。

  2. 代码执行完成,结果不正确: 程序虽然执行到了最后,但是由于逻辑错误、错误的输入等原因,其输出的结果并不是预期的。在这种情况下,进程也是通过执行完毕其入口点中的所有代码终止,但是可以通过main函数返回一个非零值来表示错误的发生。这个非零值可以是任意的,不同的值可以代表不同的错误类型,这样做可以帮助开发者快速定位问题。

  3. 代码没有执行完,程序崩溃: 这种情况通常是由于程序中存在严重的错误,如访问非法内存、除零操作等,导致操作系统强制终止了程序的执行。在程序崩溃的情况下,main函数中的return语句不会被执行,因此程序的退出码没有意义。程序崩溃通常需要开发者通过调试工具来分析程序的崩溃原因。

2、查看进程退出码

可以通过在终端中执行echo $?命令来查看上一个终止的进程的退出码。

我们看下面代码,可以用变量代替退出码。

#include <stdio.h>
#include <unistd.h>
int sum(int top)
{
    int s=0;
    for(int i=1;i<=top;i++)
    {
        s+=i;
    }
    return s;
}
int main()
{
    int ret=0;
    printf("hello world: pid: %d, ppid: %d\n",getpid(),getppid());
    int res=sum(100);
    if(res!=5050)
    {
        ret=1;
    }
    return ret;
}
使用echo $? 查看退出码为0,表示计算1到100的累加和结果等于5050,程序判断正确。
[hbr@VM-16-9-centos control]$ make
gcc -o myproc myproc.c
[hbr@VM-16-9-centos control]$ ./myproc 
hello world: pid: 7676, ppid: 4960
[hbr@VM-16-9-centos control]$ echo $?
0
将res修改为计算1到101的累加和,退出码为1,表示程序判断错误,累加和不等于5050。
[hbr@VM-16-9-centos control]$ vim myproc.c
[hbr@VM-16-9-centos control]$ make
gcc -o myproc myproc.c
[hbr@VM-16-9-centos control]$ ./myproc 
hello world: pid: 8304, ppid: 4960
[hbr@VM-16-9-centos control]$ echo $?
1

打印系统错误信息:

  • strerror() 函数会根据传入的错误号码返回相应的错误信息字符串。这些错误信息是系统定义的,可以帮助开发者诊断程序中出现的问题。

#include <stdio.h>
#include <string.h>

int main()
{
    for(int number=0;number<100;number++)
    {
        printf("%d: %s\n",number,strerror(number));
    }
}

使用ls 查看一个不存在的目录或文件,这时打印错误信息,得到错误码为2,由上图可知,2对应的就是下面程序的错误信息 No such file or directory。

[hbr@VM-16-9-centos control]$ ls file
ls: cannot access file: No such file or directory
[hbr@VM-16-9-centos control]$ echo $?
2

3、进程终止常见方式

正常终止(main,exit,_exit)

正常终止意味着程序按预期完成了其任务或者明确地由于某种原因结束了执行。这可以通过以下几种方式实现:

main函数返回:这是最常见的终止方式。程序执行完毕后,main函数返回一个整数值给操作系统。返回值0通常表示程序成功执行,没有错误。而返回非零值(如1)则表示程序遇到了某种错误或问题。例如:

int main() {
    // 程序代码
    return 0; // 正常退出
    return 1; // 出现错误
}

调用exit函数exit函数可以在程序的任何地方被调用,用来立即终止程序,并将退出码返回给操作系统。这对于在深层嵌套函数中终止程序非常有用。

#include <unistd.h>
void exit(int status);

exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:

  • 执行用户通过 atexit或on_exit定义的清理函数。
  • 关闭所有打开的流,所有的缓存数据均被写入
  • 调用_exit

调用_exit_Exit函数:这两个函数与exit类似,但是它们不会调用任何注册的退出处理函数(如由atexit注册的函数),也不会刷新标准I/O缓冲区。它们通常用在需要立即退出程序而不进行任何清理操作的情况下。

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
  • 说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

异常退出

异常退出通常是由于程序运行时遇到了错误或者被外部信号强制终止。

信号终止:程序可以通过接收到特定的信号而被异常终止。

  • 例如,当你在终端中按Ctrl + C时,会向前台进程组发送SIGINT信号,这通常会导致程序异常终止。

处理这类信号通常需要在程序中显式地捕获和处理这些信号,以决定是否终止程序或执行某些特定的清理操作。

4、三种进程终止区别

库函数(如exit()

库函数是编程语言提供的,用于执行特定任务的函数,它们是应用层的接口。

  • 例如,exit()函数是C标准库的一部分用于结束当前进程并进行适当的清理操作,比如关闭文件描述符、清空标准输入输出的缓冲区等。
  • 库函数为开发者提供了一个高级的抽象,隐藏了底层操作系统的复杂性,使得开发者可以更容易地编写跨平台的应用程序。

系统接口(如_exit()

系统接口,或称为系统调用,是操作系统提供给应用程序的接口。

  • _exit()函数是一个系统调用,直接由操作系统提供,用于立即结束进程,不执行标准库的清理和缓冲区刷新操作。
  • 系统接口让应用程序能够请求操作系统的服务,如文件操作、进程控制等。

缓冲区

谈到printf函数,所提到的“缓冲区”指的是一个临时存储区,用于暂存输出数据。这个缓冲区是由C标准库维护的,而不是操作系统。

  • C库提供的输入输出函数(如printfscanf)通常使用这些缓冲区来提高数据处理的效率,通过减少对操作系统调用的次数来达到优化性能的目的。
  • 当调用exit()函数时,C标准库会负责刷新输出缓冲区,确保所有暂存的输出数据都被正确地写入其目标介质,如终端或文件。相反,如果直接调用_exit(),这种刷新操作不会发生,因为_exit()绕过了标准库,直接请求操作系统结束进程。

return 与进程退出

  • 在C程序中,从main函数执行return操作是结束进程的一种常见方法。
  • 实际上,执行return n;main函数中的效果等同于调用exit(n);,因为C程序的启动代码(运行时环境)会捕捉到main函数的return返回值,并将其作为参数传给exit函数。这样,即便是通过return退出,也能保证执行标准库的清理工作,如刷新缓冲区和关闭文件。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鲸鱼优化算法双馈风电机组一次调频三机九节点虚拟惯量下垂控制DFIG matlab/simulink

以频率偏差变化最小为优化目标&#xff0c;采用鲸鱼算法优化风电机组一次调频控制系数。 采用matlab.m文件与simulink.slx文件联合。 系统频率优化结果 鲸鱼算法 时域模型

Learn OpenGL 14 混合

混合 OpenGL中&#xff0c;混合(Blending)通常是实现物体透明度(Transparency)的一种技术。透明就是说一个物体&#xff08;或者其中的一部分&#xff09;不是纯色(Solid Color)的&#xff0c;它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。一个有色玻璃窗是…

SwiftUI的毛玻璃效果

SwiftUI的毛玻璃效果 记录一下 SwiftUI的毛玻璃效果 import SwiftUI /*extension ShapeStyle where Self Material {/// A material thats somewhat translucent.public static var regularMaterial: Material { get }/// A material thats more opaque than translucent.pub…

生命周期、wxs

1. 什么是生命周期 生命周期&#xff08; Life Cycle &#xff09;是指一个对象从创建 -> 运行 -> 销毁的整个阶段&#xff0c;强调的是一个时间段。例如&#xff1a;  张三出生&#xff0c;表示这个人生命周期的开始  张三离世&#xff0c;表示这个人生命周期的结束…

接口测试之文件下载

在工作中对于下载接口&#xff0c;经常会有这样的疑问&#xff1a;这类接口一般功能比较稳定&#xff0c;但是又比较重要&#xff0c;需要占用回归测试时间&#xff0c;有没有可替代的方式&#xff1f; 答案肯定是有的&#xff0c;可以从接口测试/UI自动化测试介入&#xff0c…

Ubuntu Desktop - gnome-calculator (计算器)

Ubuntu Desktop - gnome-calculator [计算器] 1. Ubuntu Software -> gnome-calculator -> Install -> Continue2. Search your computer -> Calculator -> Lock to LauncherReferences 1. Ubuntu Software -> gnome-calculator -> Install -> Continu…

pta上的几个例题

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

基于SpringBoot SSM vue办公自动化系统

基于SpringBoot SSM vue办公自动化系统 系统功能 登录 个人中心 请假信息管理 考勤信息管理 出差信息管理 行政领导管理 代办事项管理 文档管理 公告信息管理 企业信息管理 会议室信息管理 资产设备管理 员工信息管理 开发环境和技术 开发语言&#xff1a;Java 使用框架: S…

代码随想录|Day22|回溯02|216.组合总和III、17.电话号码的字母组合

216.组合总和III 本题思路和 77. 组合 类似&#xff0c;在此基础上多了一个和为 n 的判断。 class Solution:def combinationSum3(self, k: int, n: int) -> List[List[int]]:def backtrack(start, path, currentSum):# 递归终止条件&#xff1a;到达叶子节点# 如果和满足条…

替代 VMware ,为什么需要重新考虑您的存储?

国内大部分 VMware 用户使用的是 vSphere&#xff0c;很少使用 vSAN&#xff0c;这使得在国内&#xff0c;企业实施 VMware 替代时&#xff0c;考虑最多的因素很可能是存储。企业级块存储产品 XEBS&#xff0c;作为业界最开放的中立的专业软件定义存储&#xff08;SDS&#xff…

数字电子技术实验(四)

单选题 1.组合逻辑电路中产生竞争冒险的原因是&#xff1f; A. 电路没有最简化 。 B. 时延 。 C. 电路有多个输出。 D. 逻辑门的类型不同。 答案&#xff1a;B 评语&#xff1a;10分 单选题 2.下列表达式不存在竞争冒险的有&#xff1f; 答案&#xff1a;A 评语&#x…

进入jupyter notebook发现没有虚拟环境,最简单实用的解决办法!

jupyter notebook 1. 进入jupyter notebook发现没有虚拟环境2.解决办法2.1 检查是否有库ipykernel&#xff0c;我发现我没有2.2 开始安装ipykernel2.3 将虚拟环境写入 总结 1. 进入jupyter notebook发现没有虚拟环境 2.解决办法 2.1 检查是否有库ipykernel&#xff0c;我发现我…

使用R语言计算并绘制流体力学中的二维泊肃叶流

平行平板间的二维流动 在流体力学中&#xff0c;当考虑两平行平板间的二维、定常、不可压缩流动&#xff0c;并且只存在沿x方向的流动速度&#xff0c;我们可以从N-S方程推导出方向的动量方程。对于给定的方程&#xff1a; (式1) 其中&#xff0c;是压力&#xff0c;是动力粘度…

一瓶5.86万,听花酒什么来头?

听花酒&#xff0c;到底什么来头&#xff1f; 宣称有提升免疫力、改善睡眠、保障男性功能、调节生理紊乱、抗衰老等功效的听花酒&#xff0c;被315晚会曝光了。 相关话题词随即冲上了热搜。之后&#xff0c;售价最高达58600元的听花酒被京东、拼多多、淘宝等电商平台火速下架…

react中JSX的详解

目录 JSX的本质及其与JavaScript的关系探究 一、JSX的本质 二、JSX与JavaScript的关系 三、为什么要使用JSX 四、不使用JSX的后果 五、JSX背后的功能模块 JSX的本质及其与JavaScript的关系探究 在React开发中&#xff0c;JSX是一个不可或缺的部分。那么&#xff0c;JSX的…

信息系统项目管理(第四版)(高级项目管理)考试重点整理 第14章 项目沟通管理(四)

博主2023年11月通过了信息系统项目管理的考试&#xff0c;考试过程中发现考试的内容全部是教材中的内容&#xff0c;非常符合我学习的思路&#xff0c;因此博主想通过该平台把自己学习过程中的经验和教材博主认为重要的知识点分享给大家&#xff0c;希望更多的人能够通过考试&a…

【编程项目开源】拼图游戏(鸿蒙版)

目标 做个拼图游戏 效果 开发工具 下载DevEco Studio 工程截图 开源地址 https://gitee.com/lblbc/puzzle/tree/master/puzzle_hongmeng_arkUI 关于 厦门大学计算机专业|华为八年高级工程师 专注《零基础学编程系列》 http://lblbc.cn/blog 包含&#xff1a;Java | 安卓…

蓝桥杯刷题(九)

1.三国游戏 代码 #输入数据 nint(input()) Xlilist(map(int,input().split())) Ylilist(map(int,input().split())) Zlilist(map(int,input().split())) #分别计算X-Y-Z/Y-Z-X/Z-X-Y并排序 newXli sorted([Xli[i] - Yli[i] - Zli[i] for i in range(n)],reverseTrue) newYli …

图像去噪--(1)

系列文章目录 文章目录 系列文章目录前言一、图像噪声1.1 噪声定义1.2 基本特征 二、按照噪声概率分布分类1.高斯噪声2.泊松噪声 三、去噪算法3.1 线性滤波3.1.1 高斯滤波3.1.2 均值滤波 3.2 非线性滤波3.2.1 中值滤波3.2.2 双边滤波 四、深度学习总结 前言 一、图像噪声 1.1 …

如何使用Python进行数据可视化:Matplotlib和Seaborn指南【第123篇—Matplotlib和Seaborn指南】

如何使用Python进行数据可视化&#xff1a;Matplotlib和Seaborn指南 数据可视化是数据科学和分析中不可或缺的一部分&#xff0c;而Python中的Matplotlib和Seaborn库为用户提供了强大的工具来创建各种可视化图表。本文将介绍如何使用这两个库进行数据可视化&#xff0c;并提供…