Linux之进程信号(下)

news2024/11/15 11:37:30

文章目录

  • 前言
  • 信号的相关概念
  • 一、信号的保存——位图
    • 1.内核中的表示
    • 2.信号集——sigset_t
    • 3.信号集操作函数
  • 二、信号的捕捉过程
    • 1.内核态和用户态
      • 用户代码和内核代码
      • 如何分辨是用户态还是内核态
      • 一个进程如何跑到OS中执行方法
    • 2.信号捕捉的过程
  • 三、核心转储
    • 1.数组越界并不一定会导致程序崩溃
    • 2.信号的退出方式
    • 3.核心转储
    • 4.核心转储的意义
    • 5.如何支持gdb(调试)
  • 四、可重入函数
    • 1.概念
    • 2.举例
      • insert(经典的不可重入函数)
  • 五、volatile关键字
    • 编译器的优化使程序出错
    • 原因
    • 如何避免优化出错(volatile)
  • 六、SIGCHLD信号
  • 总结


前言

进程信号(上)一文中已经介绍了进程信号的概念性内容,本文我们介绍信号如何保存,以及信号捕捉的具体过程(画图理解)。同时还有核心转储、可重入函数、关键字volatile以及SIGHLD信号等补充内容。


信号的相关概念

  1. 执行信号的处理动作被称为信号递达(Delivery)。
  2. 信号从产生到递达之间的状态,叫做信号未决(Pending)。
  3. 进程可以选择阻塞某个信号,被阻塞的信号产生后将保持在未决的状态,直到进程解除对此信号的阻塞,才能执行递达的操作。
    注意:阻塞和忽略不同,信号被阻塞就不能递达,而信号被忽略则是信号递达的一种处理动作。

一、信号的保存——位图

1.内核中的表示

在进程内部要保存信号的信号,有3种数据结果是与之强相关的。

  1. 首先是pending表。
    pending表就是pending位图。
    原因:进程可能在任意时间收到OS发给它的信号,该信号可能暂时不被处理,因此需要进行保存,进程保存信号是用位图来保存的,这个位图就是pending位图,对应的被保存在pending位图的信号处于未决状态。
    OS向进程发送信号就是在目标进程的pending位图中修改对应信号的比特位,从0修改为1,意思是当前进程收到该信号。因此,发信号也可以算是写信号,PCB属于OS内核结构,只有OS有权利修改pending位图,所以发送信号的执行者只能是OS。
  2. 其次是block位图
    它的比特位的位置代表信号的标号,而比特位的内容表示该信号是否被阻塞。
  3. 最后是函数指针数组
    typedef void(*handler_t)(int signo);
    handler_t hander[32] = {0};
    在内核中也有指针指向该数组,这个数组存放着当前进程所匹配的信号递达的所有方法。数组的下标代表信号的编号,数组下标对应的内容表示对应信号的处理方法。(该数组是内核数组,因此OS可以使用对应的系统接口来任意访问该数组)

在内核中,信号的基本数据结构构成:
在这里插入图片描述
signo从1开始,信号递达的伪代码:

if((1 << (signo - 1) & pcb -> block)
{
	//signo被阻塞,不能抵达
}
else
{
	if(1 << (signo - 1) & pcb -> pending)
	{
		//递达该信号
	}
}

之前了解的信号接口signal(signo, handler)的本质是拿到信号在函数指针数组的下标,然后将用户层设置的handler函数放入该数组下标所对应的位置。将来信号产生时,如果该信号没有被阻塞,则OS拿到信号,根据信号的位置得到信号的编号,进而访问数组得到处理方法。
注意:在信号没有产生时,并不妨碍它先被阻塞。
总之,进程可以识别信号并作出相应的处理,是因为程序员在设置体系时,在内核中为每个进程设置好了这三种数据结构可以用于识别信号和处理信号。

2.信号集——sigset_t

pending位图中每个信号只有一个bit的未决标志,非0即1,它不能记录信号产生了多少次,block位图也是如此。因此,未决和阻塞标志可以用相同的数据类型sigset_t
sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态:在阻塞信号集中“有效”或“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

3.信号集操作函数

sigset_t类型对于每一种信号用一个bit来表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,用户(使用者)角度不必关心,用户只能调用一下函数来操作sigset_t便利,而不用对它内部数据做任何解释。例如,使用printf打印sigset_t辩手是无意义的。
在这里插入图片描述

sigprocmask:读取或更改进程的信号屏蔽字(阻塞信号集)
在这里插入图片描述

返回值:成功返回0,失败(出错)返回-1。

sigpending:读取当前进程的未决信号集,用set参数传出。
在这里插入图片描述

返回值:调用成功返回0,失败(出错)返回-1.

二、信号的捕捉过程

信号产生的时候,进程可能不会立即处理,而是在合适的时间处理。合适的时间就是进程从内核态返回到用户态的时候进行处理,当然这说明进程之前先进入了内核态。(典型例子是系统调用和进程切换)

1.内核态和用户态

用户代码和内核代码

我们平时自己写的代码就是用户代码
用户代码不可避免的会访问OS自身的资源(getpid、waitpid…)、硬件资源(printf、write、read…),用户代码想要访问资源必须直接或间接访问OS提供的接口,即必须通过系统调用才能完成对资源的访问。系统调用是OS提供的接口,而普通用户不能以用户态的身份执行系统调用,只能先将自己的身份变为用户态才能执行。因此,执行系统调用的是进程,但是身份实际上是内核。从用户态到内核态需要都很烦的切换,还要调用OS内部的代码,所以一般系统调用花费的时间比较长,我们应该尽量避免频繁调用系统调用。

如何分辨是用户态还是内核态

一个进程在执行的时候,需要将进程的上下文数据放到CPU中的寄存器中,CPU中有许多寄存器,这些寄存器可以分为可见寄存器(eax、ebx…)和不可见寄存器(状态寄存器…)。这些寄存器在进程中具有特定的作用,例如寄存器的内容可以指向进程PCB、保存当前用户级页表,指向页表起始地址。寄存器中的CR3寄存器中存储的内容表示当前进程的运行级别:0表示内核态,3表示用户态。OS是根据CR3寄存器的内容来辨别当前进程是用户态还是内核态。

一个进程如何跑到OS中执行方法

之前我们介绍了进程地址空间,我们知道0-3G是用户级地址空间,通过用户级页表映射到不同的物理空间。除了用户级页表外还有内核级页表,OS为了维护虚拟到物理之间的OS级的代码所构成的内核级映射表,开机时就将OS加载到内存中,OS在物理内存中只保存一份(OS只有一份),因此,OS的代码和数据在内存中只有一份。当前进程从3-4G映射是将内核的代码和数据映射到当前进程的3-4G,此时使用的是内核级页表。每个进程都可以在自己特定的区域内以内核级页表的方式访问OS代码和数据,所以内核级页表只有一份(不同进程共享一份内核级页表)。
3-4G是OS内部的映射,进程建立映射的时候不仅要把用户的代码和数据与进程产生关联,还要通过用户级页表与OS产生关联,每个进程都有自己的进程地址空间,其中用户空间是每个进程独立占有的,而内核空间是从OS映射到每个进程的3-4G空间(每个进程都可以通过内核级页表映射到OS,并且每个进程看到的OS都是一样的),所以进程要访问OS的接口,需要在自己的地址空间上进行跳转。
每个进程都有内核级空间(3-4G),它们共享一个内核级页表,即使进程发生切换,内核级空间的内容也不会更改。
用户怎么才能执行访问内核数据的接口呢?
首先OS读取当前进程在CPU中CR3寄存器的内容,读取运行状态,只有当内容是0内核态时才允许进行访问,所以系统调用接口的起始位置会帮我们把用户态变为内核态(即,从3改为0)。因此,系统调用的前半段是用户态在运行。

OS是如何通过系统调用把进程从用户态该外内核态的?
中断汇编指令int 80就是陷入内核。简单理解为把进程运行级别由用户态改为内核态,在调用结束时再切换回来。

在这里插入图片描述
无论是用户态还是内核态,进程一定是处于运行的状态,区别是当前的执行级别是用户态还是内核态、页表是用户级页表还是内核级页表,以及它们可以访问的资源。

2.信号捕捉的过程

先通过系统调用陷入内核,从用户态进入内核态,可以直接从内核态进入用户态,但是由于陷入内核比较费时间,因此进入内核态后OS会做一些其他的工作,因此OS会在进程的上下文中搜索,在task_struct中找到当前进程,查看3张表:

  1. 先看block表:如果比特位内容为0,说明没有被阻塞;
  2. 继续在pending表中查看该信号对应的比特位内容,pending内容:如果为0则继续看下一个未被阻塞的信号;如果为1,则进行处理;
  3. 查看函数指针数组,找到pending对应比特位为1的信号的处理方法,对该信号进行处理。

在这里插入图片描述
为了方便记忆,我们可以将上图简化:
在这里插入图片描述

三、核心转储

1.数组越界并不一定会导致程序崩溃

在学习C语言的过程中,我们有发现数组越界并不一定会导致程序崩溃。
程序的崩溃本质是因为进程访问了未申请的空间,导致程序异常,OS向进程发送了终止进程的信号,但是实际上数组编译器在编译代码时,在栈上开辟的空间的大小与编译器是强相关的(并不仅由程序决定开辟多大空间,但是至少和程序申请的一样大)。例如,数组大小是10个元素,而它在栈上分配的字节数可能会大于10个元素空间,那么此时数组越界也可能还在有效的栈区内,因此不会发生异常,OS识别不出异常,它也不会发送信号终止进程导致程序崩溃。
例子:
文件test2.c

  1 #include<stdio.h>
  2 int main()
  3 {
  4         int arr[10];
  5         arr[100] =10;
  6         printf("arr[%d] = %d\n",100, arr[100]);
  7         printf("arr[%d] = %d\n",10000, arr[10000]);
  8         return 0;
  9 }

运行:
在这里插入图片描述
可以发现当对arr[100]进行操作时,数组虽然越界访问,但是程序并没有崩溃;而对arr[10000]进行操作时,程序崩溃了。

2.信号的退出方式

man 7 signal
在这里插入图片描述

  1. Term是正常结束,OS不会做额外的工作;
  2. Core是异常结束,OS除了终止进程的工作外,还有其他工作。

3.核心转储

在云服务器上,默认如果进程是core退出的,我们直接是看不到任何现象的,但是可以打开ulimit -a:查看系统给当前用户设置的各种资源上限:
在这里插入图片描述

core file size设置成了0,这是云服务器默认关闭了core file选项,如果想看到现象,我们需要设置:ulimit -c 数字
在这里插入图片描述
文件test2.c

  1 #include<stdio.h>
  2 int main()
  3 {
  4         int arr[10];
  5         arr[10000] = 10;
  6         printf("arr[%d] = %d\n",10000, arr[10000]);
  7         return 0;
  8 }

这时我们重新运行./test2
在这里插入图片描述
在这里插入图片描述

输出报错多了core dumpedcore表示核心,dumped表示转储,即core dumped表示核心转储。转储到当前目录下以core命名,后面跟引起core问题的进程的pid
核心转储:当进程出现异常时,我们将对应时刻进程在内存中的有效数据转储到磁盘中。

4.核心转储的意义

一旦进程出现崩溃的情况,我们会想知道为什么会崩溃、在哪里崩溃等问题,所以OS为了方便调试,会将进程崩溃的上下文数据全部dump到磁盘中用来支持调试

5.如何支持gdb(调试)

在这里插入图片描述

这种直接快速进行调试的方式叫做事后调试,在gdb中上下文直接core-file core.xxx。因为是核心转储,所以在进程终止的时候,只会检测到是以core的方式终止进程。
注意:以core方式退出的是可以被核心转储的,后续可以快速定位问题;以term退出的,一般都是正常情况下的终止进程(即,没有异常)。

四、可重入函数

1.概念

一般而言,我们认为main执行流和信号捕捉执行流是两个执行流。
在main中和handler中,某函数被重复进入,程序会出现问题,则该函数称为不可重入函数
在main中和handler中,某函数被重复进入,程序不会出现问题,则该函数称为可重入函数
我们目前使用的大部分接口都是不可重入的(重入和不重入是特性)。

2.举例

insert(经典的不可重入函数)

main函数调用insert:向链表head插入Nodel。
inset只做了第一步就被中断(由于信号原因,执行信号捕捉),此时进程会被挂起,然后唤醒再次回到用户态检查待处理的信号,切换到sighandler方法,如果此时的sighandler方法中也调用了insert函数(要将Node2头插到链表中:Node2节点的next指向下一个节点的位置,然后让head的next指向Node2,如此完成Node2的头插),信号捕捉完后就成功的将Node2头插到链表中。接下来回到main执行流,对Node1进行插入的第二步,让head的next指向Node1。程序的最后只有head1插入到链表中,而head2找不到了(发生内存泄漏),出现问题。

不可重入函数:
调用了malloc/free的函数。malloc也是用全局链表来进行管理的;
调用了标准I/O库的函数。标准I.O库的函数实现都是以不可重入的方式使用全局数据结构。

五、volatile关键字

编译器的优化使程序出错

通过自定义方法handler修改全局q,程序不会退出。
文件mysignal.c

  1 #include<stdio.h>
  2 #include<signal.h>
  3 int quit = 0;
  4 void handler(int signo)
  5 {
  6         printf("%d号信号,正在被捕获", signo);
  7         printf("quit:%d", quit);
  8         quit = 1;
  9         printf("->%d\n", quit);
 10 }
 11 int main()
 12 {
 13         printf("my pid = %d\n", getpid());
 14         signal(2, handler);
 15         while(!quit);
 16         printf("我是正常退出的");
 17         return 0;
 18 }

运行:
优化前:退出
在这里插入图片描述
优化后:不退出
在这里插入图片描述

原因

O3优化:
编译器认为quit在main执行流中只是被检测,没有被修改,编译器就对quit做了优化(将quit放入寄存器,这样后续就不用再去内存中读取quit,提高了程序运行效率)。
因此,虽然程序中修改了quit,但只是改变了内存中的quit,CPU的寄存器中保存的值不会一起改变,所以无论内存中的quit怎么改,寄存器中的quit一直不变一直为0。
而while循环因为代码的优化,导致检测quit时读取的是寄存器中的值,而不是内存中的值,因此一直循环,就导致了程序不退出的结果。
这就相当于寄存器中的quit值覆盖率物理内存中quit变量值。

举个生活中的例子:
一个不太会做饭的人,某天煮了一锅汤,他用勺子舀了一勺试味道,发现盐放少了,就往锅里加盐。然后,他又尝了一口勺子里的汤,发现盐还是少,就继续加盐,直到把一包盐加完,还是觉得汤里没盐,但是他家人舀了一勺喝了一口差点被齁死。最终发现他调味道的时候只试最开始的内勺汤,因为不想浪费太多汤来试味道,就一直没有换新的汤,就导致这一锅汤都不能喝的结果。

如何避免优化出错(volatile)

volatile可以保持可见性。
给quit加volatile关键字,则quit就会通过内存读取而不是寄存器,就能保持变量quit的内存可见性。
文件signal.c

  1 #include<stdio.h>
  2 #include<signal.h>
  3 volatile int quit = 0;
  4 void handler(int signo)
  5 {
  6         printf("%d号信号,正在被捕获", signo);
  7         printf("quit:%d", quit);
  8         quit = 1;
  9         printf("->%d\n", quit);
 10 }
 11 int main()
 12 {
 13         printf("my pid = %d\n", getpid());
 14         signal(2, handler);
 15         while(!quit);
 16         printf("我是正常退出的\n");
 17         return 0;
 18 }

在这里插入图片描述

六、SIGCHLD信号

子进程退出时,会向父进程发送17号信号SIGCHLD。
文件mysignal.c

  1 #include<stdio.h>
  2 #include<signal.h>
  3 #include<stdlib.h>
  4 void handler(int signo)
  5 {
  6         printf("pid:%d, %d号信号正在被捕捉!\n", getpid(), signo);
  7 }
  8 int main()
  9 {
 10         signal(SIGCHLD, handler);
 11         printf("我是父进程:%d, ppid:%d\n", getpid(), getppid());
 12         pid_t id = fork();
 13         if(id == 0)
 14         {
 15                 printf("我是子进程:%d, ppid:%d,我要退出了\n", getpid(), getppid());
 16                 exit(1);
 17         }
 18         while(1) sleep(1);
 19         return 0;
 20 }

运行:
在这里插入图片描述
实际上,因为UNIX的历史原因,想要不产生僵尸进程还有一种方式:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork处理的子进程,在终止时会自动清理掉,不会通知父进程,也不会产生僵尸进程。
系统默认的忽略动作和用户用sigaction函数自定义的忽略,通常是没有区别的,但这是一个特例。

signal(SIGCHLD, SIG_IGN);
sigaction(SIGCHLD, act, oldact);

注意:虽然SIGCHLD默认动作就是忽略,但是它与手动设置表现的不同。
默认是收到信号就忽略处理,但是该等还是要等;
手动设置的SIG_IGN,子进程退出时发送给父进程的信号会被父进程忽略,但是子进程会被OS回收。
这两者是有区别的,含义不一样。


总结

以上就是今天要讲的内容,本文我们介绍信号如何保存,以及信号捕捉的具体过程(画图理解)。同时还有核心转储、可重入函数、关键字volatile以及SIGHLD信号等补充内容。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

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

相关文章

CTFHub | 远程包含

0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习&#xff0c;实训平台。提供优质的赛事及学习服务&#xff0c;拥有完善的题目环境及配套 writeup &#xff0c;降低 CTF 学习入门门槛&#xff0c;快速帮助选手成长&#xff0c;跟随主流比赛潮流。 0x01 题目描述…

Flutter 库:提升开发体验——Quick

Flutter 库&#xff1a;提升开发体验——Quick 文章目录 Flutter 库&#xff1a;提升开发体验——Quick一、概述1、简介2、功能3、官方资料4、思考 二、基本使用1、安装2、基本使用3、运行结果 三、List 列表扩展示例四、Map 映射扩展示例五、其它示例 一、概述 1、简介 Quic…

打印杨辉三角

这个公式&#xff0c;不好算&#xff0c;我觉得还是杨辉三角算起来方便&#xff1a;c#代码如下&#xff1a; double 打印杨辉三角(int n)//n必须是偶数&#xff0c;展开项是n1&#xff0c;中间项是n/2,此处返回中间项的概率202306131722 { //for (int i 0…

3个月面过字节测开岗,拿个20K不过分吧?

计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实习是在一家初创…

浅聊一下 C#程序的 内存映射文件 玩法

一&#xff1a;背景 1. 讲故事 前段时间训练营里有朋友问 内存映射文件 是怎么玩的&#xff1f;说实话这东西理论我相信很多朋友都知道&#xff0c;就是将文件映射到进程的虚拟地址&#xff0c;说起来很容易&#xff0c;那如何让大家眼见为实呢&#xff1f;可能会难倒很多人&…

耗时3个月,线下访谈30+ csdn大佬,规划出了我的云原生学习路线

前言 大家好&#xff0c;我是沐风晓月&#xff0c;最近线下拜访不少云原生方向的大佬和csdn其他方向的大佬&#xff0c;受益匪浅。 于是在 5月23日&#xff0c;我定下来自己的目标&#xff1a; 我的目标&#xff1a; 可以说&#xff0c;这个世代给予的机遇&#xff0c;让我…

WWDC苹果高管脱口秀,Mike Rockwell透露Vision Pro开发的秘密

WWDC2023之后例行的“The Talk Show”脱口秀如期展开&#xff0c;深入讨论了WWDC上的一切。本次脱口秀分别谈及Mac游戏、Vision Pro以及操作系统和AI&#xff0c;由John Gruber主持&#xff0c;嘉宾包括&#xff1a;苹果技术发展部副总裁Mike Rockwell、以及Greg Joswiak、John…

上线客流人数统计系统实现资源的最大化利用

在流量管理方面&#xff0c;智慧客流采集系统的应用可以帮助商家实现资源的最大化利用。通过对客流量数据的分析&#xff0c;商家可以准确把握客流量变化规律&#xff0c;进而制定出最优化的资源配置方案。 AI客流视觉监控 一、某汽车4S店智慧客流采集系统案例展示 以某汽车4S…

人民大学中外合作办学_人大女王金融硕士——国际顶尖的金融硕士学位等你来拿

进入金融圈后&#xff0c;你会发现学历是筛选手段之一&#xff0c;之后的晋升更是看重学历。一个金融学士学位可以让你跨过门槛进入到金融行业&#xff0c;想要得到更好的发展&#xff0c;就需要从知识和证书等多方面进行提升了。辞职去攻读硕士学位不太现实&#xff0c;幸好遇…

vsftpd安装配置,Linux系统配置FTP服务器教程(CentOS 7)

vsftpd&#xff08;very secure FTP daemon&#xff09;是Linux下的一款小巧轻快、安全易用的FTP服务器软件。今天飞飞将和你分享Linux系统安装并配置vsftpd&#xff0c;搭建FTP环境。 先让我们来认识下FTP&#xff01; FTP&#xff08;File Transfer Protocol&#xff09;是一…

C语言-运算符

1 运算符的概念以及分类 运算符的概念 用运算符将运算对象(也称操作数&#xff09;连接起来的、符合&#xff23;语法规则的式子&#xff0c;称为&#xff23;表达式运算对象包括常量、变量、函数等 运算符的分类 1、双目运算符&#xff1a;即参加运算的操作数有两个 例&…

系统移植 网络环境配置和TFTP服务器搭建

目录 网络环境配置 1. 配置windows环境下协议 2. 配置linux环境下的网络 3. 开发板 4. Ping 不通解决办法 搭建TFTP服务器环境搭建 》1.验证tftp服务器环境是否搭建 》2.若是未安装&#xff0c;执行下面命令 》3.修改tftp服务器配置文件 》4.在ubuntu下创建工作目录并…

华为OD机试真题 JavaScript 实现【日志采集系统】【2023Q1 100分】

一、题目描述 日志采集是运维系统的的核心组件。日志是按行生成&#xff0c;每行记做一条&#xff0c;由采集系统分批上报。 如果上报太频繁&#xff0c;会对服务端造成压力&#xff1b;如果上报太晚&#xff0c;会降低用户的体验&#xff1b;如果一次上报的条数太多&#xf…

ffplay播放http-mp4视频提示 “stream 1, offset 0x1f85: partial file“错误

解决办法就是将非交织视频 重新 remux. ffmpeg -i a.mp4 -c copy -movflags faststart a1.mp4 -movflags faststart : 这个是将moov移动到 mp4头部,和remux没有关系. 这样处理也是通过HTTP 流式播放必须要做的一步.

Linux防火墙学习笔记11

iptables扩展动作&#xff1a;TARGET EXTENSION: filter&#xff1a; -j ACCEPT -j DROP -j REJECT -j LOG nat: -j SNAT: 源地址转换 -j MASQUERADE: 转换源地址&#xff08;伪装&#xff09; -j DNAT: 目的地址转换及端口 -j REDIRECT: 转换目标端口 本机端…

Java后端入职第六天,Nginx搭建负载均衡+反向代理

一:需求背景 Nginx相信大家应该不陌生吧!是一个高性能web服务器,可以做负载均衡或者是反向代理,比如,把静态页面直接部署到到html,实现动静分离,一个服务器部署多个服务,缓解服务压力,等等,都可以利用Nginx实现。 1.负载均衡 这个理解比较简单,其实就是压力分摊,…

Aspose.Pdf使用教程:获取PDF文档中的所有注释

Aspose.PDF 是一款高级PDF处理API&#xff0c;可以在跨平台应用程序中轻松生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现&#xff0c;保护和打印文档。无需使用Adobe Acrobat。此外&#xff0c;API提供压缩选项&#xff0c;表创建和处理&#xff0c;图形和图像功能&am…

小马赠书【第7期】清华出版社 IT BOOK 多得活动(送书5本)

本期 敬 之 共精心挑选了 15 本 IT 相关书籍&#xff0c;包含 前端、后端、数据分析、人工智能、python、低代码 等各个领域。关于如何参与等具体活动信息请看活动详情页&#xff0c;以下是 15 本 IT 书籍介绍&#xff1a; 活动详情页&#xff1a;小马赠书【第7期】 1. C《C 高…

Django高级扩展之静态文件和ajax使用

目录 使用静态文件 创建静态文件目录 Settings.py配置 创建css文件 路由与视图 设置路由 视图 模板使用静态文件 调用css 调用js 调用图片 优化路径 加载标签 修改文件路径 修改图片路径 修改js路径 修改css路径 使用Ajax 应用场景 示例 路由 视图 模板…

Koordinator 最佳实践系列:精细化 CPU 编排

作者&#xff1a;乔普、申信 介绍 在云原生环境中&#xff0c;集群提供者常常将不同类型的工作负载部署在同一个集群中&#xff0c;利用不同业务的不同峰值效果&#xff0c;实现资源分时复用&#xff0c;避免资源浪费。然而&#xff0c;不同类型负载之间混合部署常常会导致资…