Linux-进程信号

Linux进程信号

  • 初步认识信号
  • 信号的存储结构
  • 信号的处理方式
  • 信号的产生
  • 硬件异常产生的信号
  • 核心转储
  • sigset_t信号集
  • 信号集的操作函数
  • 对block表的操作
  • 对pending表的操作
  • 对handler表的操作
  • 信号的捕捉
    • 用户态和内核态
  • 信号的处理过程
  • 可重入函数
  • volatile关键字

初步认识信号

生活中有哪些信号?例如:红绿灯、闹钟、手势等都叫做信号,我们接收到这些信号都会采取一些措施来应对这些信号。

操作系统里面也是有信号的,进程也是可以接受信号的,接收之后进程也会采取信号所相对应的措施。

信号可能随时产生,信号的产生对于进程来讲是异步的,所以在接收到信号时,进程可能在做优先级更高的事情,不能立即处理信号,所以进程需要有保存信号的能力,在后续合适的时间去处理这个信号。

信号会保存在进程的PCB中,进程的PCB只能由OS(operating system 操作系统的意思,后面都用OS简写代替)修改,所以无论有多少种信号的产生,最终只能由OS来完成最后的发送。

系统自定义的信号列表

1-31为普通信号:普通信号被进程接收首先保存,可以等待进程执行完优先级更高的指令再来处理信号。重点讲解普通信号。因为Linux、windows、安卓等系统都是分时操作系统,用的是分时信号

31-64为实时信号:实时信号必须马上被处理,直到处理完毕。车载系统等实时操作系统会用实时信号。
在这里插入图片描述
信号的其他相关概念:

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

信号的存储结构

进程中接收信号的时候可能不会立即处理,但是不代表不会处理,所以需要先保存起来,然后等待合适的时机去处理。所以进程需要记录一个信号在进程中是否存在,0代表不存在,1代表存在,普通信号有31个,所以PCB中信号的保存就可以用位图结构,从低位开始,第一个比特位代表1号信号,依次类推。所以给进程发送信号就是直接修改特定进程的信号位图中的特定的比特位。

实时信号在操作系统中用的是队列的存储方式(了解即可)

在PCB中有三张表分别是:pending、block、handler。

pending表:位图结构。比特的位置,表示哪一个信号,从低位到高位第一个比特位表示1号信号,比特位的内容表示是否接收到该信号,1表示接收,0表示未接收。

block表:位图结构。比特的位置,表示哪一个信号,从低位到高位第一个比特位表示1号信号,比特位的内容表示该信号是否被阻塞。

handler表:函数指针数组。该数组的下标对应信号的递达动作。SIG_DEL表示默认处理,SIG_ING表示忽略,还有就是自定义方法的函数指针。用signal自定义信号递达动作时,就是往指针数组中存放函数地址。

在这里插入图片描述

信号的处理方式

当进程收到信号时,有三种处理信号的方法:

  1. 默认方式
  2. 忽略信号
  3. 用户自定义处理

​ 用户自定义处理,有一个接口sighandler_t signal(int signum, sighandler_t handler);

在这里插入图片描述

第一个参数是信号的编号,第二个参数是一个函数指针,当进程处理signum时,会执行handler函数的方法。但是只是执行了singal方法,handler方法并不会立马执行,singal方法只是改变了信号产生时对应的执行动作。只有signum信号真正产生时,才会执行handler方法。

9号信号不能被自定义。

信号的产生

当一个进程被执行的时候,分为前台进程和后台进程,前台进程是可以被ctrl+c直接中断的,本质上就是向这个前台进程发送一个2号信号,或者ctrl+\给前台进程发送一个3号信号。

那么我们从键盘输入的时候,计算机怎么知道我从键盘里输入了数据呢?

CPU的背面有很多针脚,每一个针脚连接一个硬件,每一个针脚都有自己的编号,表示中断号,当键盘被按下的时候,连接键盘的针脚会接收到键盘被按下的信号,内存中会保存一个中断向量表,向量表中存放的都是函数指针,CPU会根据中断号去中断向量表中查询对应的函数,键盘的函数就是让OS读取数据,键盘被输入的数据就会被OS发送到前台进程。

系统接口

给对应的进程发送信号。

在这里插入图片描述

自己给自己发送信号

在这里插入图片描述

结束调用abort的进程。相当于raise(6)。6号信号即便自定义处理方式了,也会执行完自定义操作之后退出。

在这里插入图片描述

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

在这里插入图片描述

硬件异常产生的信号

什么叫做硬件异常产生的信号呢?举个例子:

int a = 10;
a = a/0;

这两行代码如果在Linux下编译会被警告,但是还是会生成可执行程序,在运行的时候会报错(float point exception 8号信号)。然后程序就不会继续向下执行了。

这个进程是怎么接收到这个8号信号的呢?

进程被执行的时候,代码会从上到下依次执行,在CPU中有若干寄存 器,其中有一个状态寄存器会记录每行代码的状态,如果结果不正确,有数据溢出,状态寄存器由0置1。这个寄存器就有了硬件异常。OS就会给引起硬件异常的进程发送信号。如果这个进程自定义了8号信号的处理方式,然后没有退出,操作系统一直调度这个进程一直执行8号信号的自定义动作。

下面这个也是硬件异常产生的信号:

int* p = nullptr;
p = 100;//p是一个指针变量,内部有空间,可以被强行赋值
*p = 100;//野指针

*是一个解引用的操作,就是要对p指向的空间进行访问,p指向的是nullptr,也就是0号空间的地址,进程中想要对变量进行赋值,存放在虚拟地址空间需要通过页表(MMU)去访问对应的物理内存,MMU也是一个硬件,被集成在CPU中的,通过MMU访问物理内存如果访问失败,有两种失败原因,一个是MMU中没有该虚拟地址的映射关系,另一个就是有映射关系但是没有访问权限,无论哪一种。MMU都会产生硬件异常,然后OS给进程发送信号。

核心转储

Linux下有这样一个功能,在进程发生异常的时候,核心代码部分进行核心转储,将内存中进程的相关数据dump到磁盘里面,一般称为核心转储文件,以core命名。如果是云服务器的话默认是关闭的。

ulimit可以设置/显示用户可以使用资源的限制。

在这里插入图片描述

我们可以看到core file size是被设置为0的,所以默认不会进行核心转储,如果想打开core文件,可以使用ulimit -c 1024(文件大小)

在学习进程等待的时候,waitpid返回结果存储在status,status为整形,32个比特位,返回结果存储在高八位,退出信号存储在第0-7个比特位。第七个比特位就是core dump的标志位,如果为0说明核心转储是关闭的或者进程正常退出,如果为1,说明进程异常退出并且核心转储是打开的。

那怎么样才能进行核心转储呢?首先我们了解了,进程收到信号后默认动作是退出,但是信号退出动作有其中两种trem和core,有什么区别呢?core和上面的core文件有什么关系呢?

在这里插入图片描述

如果信号的action是Term,进程收到信号后会直接退出。如果是Core,OS会进行核心转储。

核心转储有什么用

如果一个进程异常退出了,退出原因是最重要的,而异常退出之后生成的核心转储文件可以帮我们很快的定位到是因为哪一行代码退出的,收到几号信号退出的。

在这里插入图片描述

生成的可执行程序默认是release,如果需要gdb调试,需要在g++编译的时候加-g选项。

gdb调试时,直接使用core-file命令,后面跟着core文件就可以定位到错误部分。

为什么核心转储是关闭的

因为核心转储文件一般都很大,这种有问题的可执行程序每执行一次就会生成一个核心转储文件,在公司里假如某个服务挂掉了,他就会一直重启,重启一次就会生成一个核心转储文件,很快就会把磁盘占满。

sigset_t信号集

信号集的操作函数

#include <signal.h>
int sigemptyset(sigset_t *set);//初始化信号集
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);//把signo信号添加到set信号集中
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);//查看signo信号是否存在

这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含
某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

对block表的操作

在这里插入图片描述

对pending表的操作

sigpending函数,把当前进程的pending表设置进set里面

在这里插入图片描述

对handler表的操作

上面介绍了signal这个函数可以修改handler表,还有一个函数也可以修改handler表。

NAME
       sigaction - examine and change a signal action
SYNOPSIS
       #include <signal.h>
       int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};
  • sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体;
  • 将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来
的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数。

示例代码:

void handler(int signo){
    cout<<"接收到了"<<signo<<"信号"<<endl;
}
int main(){

    struct sigaction act,oldact;
    memset(&act,0,sizeof(act));
    memset(&oldact,0,sizeof(oldact));
    act.sa_handler = handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);

    sigaddset(&act.sa_mask,3);
    sigaddset(&act.sa_mask,4);
    sigaddset(&act.sa_mask,5);

    sigaction(2,&act,&oldact);
   }

信号的捕捉

当进程接收到信号的时候,信号可能不会被立即处理,因为进程在做优先级更高的事情,那么什么时候会处理呢? 答案是当进程从内核态切换到用户态的时候,进程会在OS的指导下进行信号的检测和处理。

补充:当信号之前被block,block解除后对应的信号会被立即递达;

用户态和内核态

进程被加载到内存中被执行的时候分为用户态和内核态

用户态:执行我们自己写的代码时,进程所处的状态。

内核态:执行OS的代码时,进程所处的状态。例如:进程时间片到了,需要执行进程切换逻辑代码的时候。或者调用系统接口的时候。都处于内核态。

重新理解进程地址空间

在这里插入图片描述

  • [0GB,3GB]是用户空间,每个进程的用户空间是不一样的。每个进程都有自己的用户级页表。
  • [3GB,4BG]是内核空间,每个进程的内核空间都是一样的。所有进程都用同一张内核级页表。
  • 操作系统就是在进程中运行的。
  • 调用系统接口,就和调用自己写的库函数一样,都是在进程地址空间内完成调用。
  • 进程在用户态的时候,无法访问OS的数据和代码。CPU中有一个寄存器(CR3)中比特位为3表示用户态,比特位为0表示内核态。但是用户无法直接修改寄存器的状态,我们调用系统接口的时候就需要从用户态切换成内核态。所以系统调用接口内部会帮我们做这个事情,在刚开始进入系统函数的时候没有立马进入内核态,还没有触发状态检测,系统接口会先修改CR3的寄存器状态,然后再去执行函数代码。

进程是如何被调度的?

我们都知道进程是被OS管理和调度的。那么到底如何调度的呢?

OS的本质也是软件,是一个一直死循环的软件。电脑的开机操作本质上就是把OS加载到内存。上面提到了OS在每一个进程中的内核空间中运行。在没有进程被OS调度的时候,OS也有自己的进程可以执行,在centos7中叫做systemd也就是1号进程。

Linux是一个分时系统,如果内存中有若干个进程,它会让每个进程都能被执行到,所以每个进程都有时间片。如果进程的时间片到了,就需要切换到别的进程,OS如果执行某段逻辑代码,例如死循环等,怎么知道该进程的时间片到了,需要切换进程呢?

在电脑主板上有一个时钟硬件,它是用来记录时间的。就算我们的电脑关机很久并且不联网,再开机电脑的时间也不会错误。就是依赖这个时钟硬件。这个时钟硬件会每隔很短的时间给OS发送一次硬件中断。OS就会执行对应的中断处理方法,会检查当前进程的时间片,如果超时。OS会将当前进程进行保存等一系列处理。OS会调用一个叫做schedule();的系统接口完成进程的切换调度。而这一切都是OS在当前进程的内核空间内完成的操作。

信号的处理过程

上面说了,信号只有在内核态向用户态转换的时候才会被处理,那么具体过程是什么呢?

== 当我们执行用户态的代码时,会因为系统调用等原因陷入内核态,进入内核态后完成某种任务之后,内核态要向用户态转换,转换之前会检查一下block表和pending表,如果block为0,pending表为1,就会执行对应的handler方法,有三种处理方式,其中是SIG_DEL、SIG_IGN、自定义函数。其中自定义函数在进程的用户空间内定义的,所以需要跳转到用户态去执行自定义方法,之所以不用内核态是因为防止自定义函数利用内核态权限修改OS数据或代码。执行完自定义方法之后不能直接返回到用户态的上下文中,因为自定义方法并不知道用户态到内核态的位置,需要先返回到内核态(sigreturn),然后在内核态用sys_sigreturn()系统接口返回。==

内核对信号进行处理之前会先把pending表中的bit位置为0。

可重入函数

在这里插入图片描述

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

我们学习的大多数函数都是不可重入的。

volatile关键字

我们先看一个现象 然后解释这个关键字

int quit=0;
void handler(int signo)
{
    cout<<"quit from zero to one"<<endl;
    quit=1;
    cout<<quit<<endl;
}
int main(){
    signal(2,handler);
    while(!quit);
    cout<<"main formal quit"<<endl;
    return 0;
 }

上面这段代码正常应该是接收到2号信号 然后修改quit退出死循环 然后正常退出。看看结果:

在这里插入图片描述

实际上确实是这样,没什么不对,但是Linux下gcc的优化级别分为O0 O1 O2 O3,O3的优化级别是最高的,O0是默认编译方式,不做优化,不优化就不会有什么问题,如果把优化级别改成O1,就不一样了。

g++ -o $@ $^ -std=c++11 -O1

再进行编译运行

在这里插入图片描述

无论发送多少2号信号都不会退出,为什么?解释一下:

quit这个变量是在内存中存放的,cpu想要执行这段代码,对这个循环进行逻辑判断,需要先把quit的数据从内存中获取到cpu再进行运算,但是如果编译级别优化之后,cpu对这个quit变量使用频率非常高并且发现main函数里面只是对quit变量进行取反再判断,并没有进行修改。所以把变量存放在寄存器内部,每次判断从寄存器读取数据即可,即使变量在内存中被修改cpu也不会重新读取,这就叫做内存位置不可见了。解决方法加上volatile关键字,就是告诉cpu每次要从内存中获取数据,保证内存可见性。

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

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

相关文章

C# CAD2016 多边形顶点按方向重新排序

多边形顶点按方向重新排序 初始化多边形顶点集合 outerPoints 创建一个名为 outerPoints 的 List<Point2d>&#xff0c;用于存储多边形的所有顶点坐标。 计算多边形顶点集合的边界框&#xff08;BoundingBox&#xff09; 使用LINQ的Aggregate方法遍历整个outerPoints列表…

基于Zigbee的智能温室大棚系统(附详细使用教程+完整代码+原理图+完整课设报告)

🎊项目专栏:【Zigbee课程设计系列文章】(附详细使用教程+完整代码+原理图+完整课设报告) 前言 👑由于无线传感器网络(也即是Zigbee)作为🌐物联网工程的一门必修专业课,具有很强的实用性,因此很多院校都开设了zigbee的实训课程;👑同时最近很多使用了我的单片机课…

计算机网络——10FTP

FTP FTP&#xff1a;文件传输协议 向远程主机上传输文件或从远程主机接收文件客户/服务器模式 客户端&#xff1a;发起传输的一方服务器&#xff1a;远程主机 ftp:RFC 959ftp服务器&#xff1a;端口号为21 FTP&#xff1a;控制连接与数据连接分开 控制连接 FTP客户端与FTP服…

[FFmpeg学习]从视频中获取图片

从视频中获取图片是一个比较直观的例子&#xff0c;这里从一个基础的例子来查看FFmpeg相关api的使用&#xff0c;从mp4文件中获取一帧图像&#xff0c;保存为jpeg格式图片&#xff0c;mp4文件比较好准备&#xff0c;一般手机录屏文件就是mp4格式。 原理还是比较清楚&#xff0…

【杂谈】扣子(Coze) 初体验

扣子(Coze)是什么 官方原文如下&#xff1a; 扣子&#xff08;coze.cn&#xff09;是一款用来开发新一代 AI Chat Bot 的应用编辑平台&#xff0c;无论你是否有编程基础&#xff0c;都可以通过这个平台来快速创建各种类型的 Chat Bot&#xff0c;并将其发布到各类社交平台和通…

B2084 质因数分解

题目描述 已知正整数 n 是两个不同的质数的乘积&#xff0c;试求出较大的那个质数。 输入格式 输入只有一行&#xff0c;包含一个正整数 n&#xff08;6<n<&#xff09;。 输出格式 输出只有一行&#xff0c;包含一个正整数 p&#xff0c;即较大的那个质数。 输入输…

Java 基于 SpringBoot+Vue 的社区医院系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

《Java 简易速速上手小册》第8章:Java 性能优化(2024 最新版)

文章目录 8.1 性能评估工具 - 你的性能探测仪8.1.1 基础知识8.1.2 重点案例&#xff1a;使用 VisualVM 监控应用性能8.1.3 拓展案例 1&#xff1a;使用 JProfiler 分析内存泄漏8.1.4 拓展案例 2&#xff1a;使用 Gatling 进行 Web 应用压力测试 8.2 JVM 调优 - 魔法引擎的调校8…

根据Ruoyi做二开

Ruoyi二开 前言菜单代码生成总结 前言 之前写过一篇文章&#xff0c;若依微服务版本搭建&#xff0c;超详细&#xff0c;就介绍了怎么搭建若依微服务版本&#xff0c;我们使用若依就是为了简化我们的开发&#xff0c;减少开发周期的&#xff0c;这篇文章就会介绍怎么使用若依进…

大型社区门口适合开什么店?商业趋势与消费需求分析

作为一名资深的鲜奶吧创业者&#xff0c;我在这个行业已经摸爬滚打了五年。期间&#xff0c;我见证了社区商业的蓬勃发展&#xff0c;也深刻体会到了选址对于店铺经营的重要性。 这篇文章&#xff0c;我和大家分享一下我的见解&#xff0c;探讨一下大型社区门口适合开什么店&a…

软件实例分享,茶楼收银软件管理系统,支持计时计费商品销售会员管理定时语音提醒功能

软件实例分享&#xff0c;茶楼收银软件管理系统&#xff0c;支持计时计费商品销售会员管理定时语音提醒功能 一、前言 以下软件教程以 佳易王茶社计时计费管理系统软件V18.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 问&#xff1a;这个软…

AI绘画作品的展示和变现-2

4.7 制作红包封面 中国的节日和传统文化元素仍然可以成为创作者们的创作灵感&#xff0c;创造出更多的变现机会。比如元宵节&#xff0c;可以制作大型元宵图案&#xff0c;进行引流并卖出元宵。 而春分、谷雨等节气也可以成为创作的灵感来源&#xff0c;创作出与之相关的图案&…

Java实现音乐平台 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示 四、核心代码4.1 查询单首音乐4.2 新增音乐4.3 新增音乐订单4.4 查询音乐订单4.5 新增音乐收藏 五、免责说明 一、摘要 1.1 项目介绍 基于微信小程序JAVAVueSpringBootMySQL的音乐平台&#xff0c;包含了音乐…

Go+:一种简单而强大的编程语言

Go是一种简单而强大的编程语言&#xff0c;它是在Go语言之上构建的&#xff0c;旨在提供更加强大、灵活和易于使用的编程体验。Go与Go语言共享大部分语法和语义&#xff0c;因此Go开发人员可以很快上手Go&#xff0c;同时也可以使用Go来编写更加简洁和高效的代码。在本文中&…

蓝桥杯嵌入式第11届真题(完成) STM32G431

蓝桥杯嵌入式第11届真题(完成) STM32G431 题目 代码 程序和之前的大同小异&#xff0c;不过多解释 main.c /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief :…

Windows搭建docker+k8s

安装Docker Desktop 从官网下载&#xff0c;然后直接安装即可&#xff0c;过程很简单&#xff0c;一直Next就行。 有一点需要注意就是要看好对应的版本&#xff0c;因为后边涉及到版本的问题。 https://www.docker.com/products/docker-desktop 安装完成&#xff0c;双击图…

Golang中的fmt包:格式化输入输出的利器

Golang中的fmt包&#xff1a;格式化输入输出的利器 在软件开发的世界里&#xff0c;fmt包就像是一位忠实的伙伴&#xff0c;始终陪伴着开发人员。它简化了格式化输入输出的过程&#xff0c;让打印和扫描数据变得轻松自如。无论是向控制台输出简单的消息&#xff0c;还是处理复杂…

【深度学习】S2 数学基础 P1 线性代数(上)

目录 基本数学对象标量与变量向量矩阵张量降维求和非降维求和累计求和 点积与向量积点积矩阵-向量积矩阵-矩阵乘法 深度学习的三大数学基础 —— 线性代数、微积分、概率论&#xff1b; 自本篇博文以下几遍博文&#xff0c;将对这三大数学基础进行重点提炼。 本节博文将介绍线…

涛哥聊Python | pymunk,一个强大的 Python 库!

本文来源公众号“涛哥聊Python”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;pymunk&#xff0c;一个强大的 Python 库&#xff01; 大家好&#xff0c;今天为大家分享一个强大的 Python 库 - pymunk。 Github地址&#xff1a;…

计算机二级C语言的注意事项及相应真题-4-程序修改

目录&#xff1a; 31.逐个比较p、q所指两个字符串对应位置中的字符&#xff0c;把ASCII值大或相等的字符依次存放到c所指数组中&#xff0c;形成一个新的字符串32.求矩阵&#xff08;二维数组)a[N][N]中每行的最小值&#xff0c;结果存放到数组b中33.将一个十进制整数转换成r(二…