第3章 Linux内核调试手段之二

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

===================

gdb 和 addr2line 调试内核模块

内核模块插入内核链表的时候,会调用 init 里面的程序,我们上面给的那个例程的程序因为是经过多年风吹雨打的,但是如果你是一个萌新的码农,你能保证自己写的内核模块没有问题吗?所以就需要调试方法,如果你写了一个内核模块加载不成功,这时候就会产生 oops ,内核不会有影响,就好像你拿了一个伪造的车票想上高铁,结果被列车员发现了,把你踢下车了,列车不受影响继续运行。


我们用个示例来说明下调试 oops 的问题,调试的最终目的就是要找到出现问题的地方。


oops.c 源代码,相比HelloWorld的代码,这个代码增加了一些东西,一个是增加了模块描述 MODULE_DESCRIPTION 和模块作者 MODULE_AUTHOR ,这些都不是必须的,但是作为一个标准的内核开发者,把自己的模块写得越规范是越好的,不仅代码看起来比较美观,而且别人也可以从这些代码里面看到一些有用的信息。


#include <linux/init.h>

#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

MODULE_DESCRIPTION ("Oops");

MODULE_AUTHOR("weiqifa");

static int my_oops_init(void)

{

int *a;

a = (int *)0x00003333;

*a = 3;

   printk(KERN_ALERT "oops %d\n",a);

   return 0;

}

static void my_oops_exit(void)

{

   printk(KERN_ALERT "Goodbye, oops\n");

}

module_init(my_oops_init);

module_exit(my_oops_exit)


Makefile 文件,这个文件跟上面的文件有些不同,加入了一个 FLAG ,这个FLAG 为了方便我们调试内核。


ifneq ($(KERNELRELEASE),)

EXTRA_CFLAGS = -Wall -g

obj-m := oops.o

else

PWD  := $(shell pwd)

KVER := $(shell uname -r)

KDIR := /lib/modules/$(KVER)/build

all:

$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:

rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions

endif


我们执行 insmod oops.ko 的时候,会出现 kernel panic,再通过 dmesg 来查看 panic 内容,其中看到的 kernel 日志如下


root@ubuntu:~/linuxBook/oopsmodules# dmesg |tail -20

[  815.844634] RSP: 0018:ffff88003b933de8  EFLAGS: 00010246

[  815.844635] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000001

[  815.844637] RDX: 0000000000040770 RSI: 0000000000003333 RDI: ffffffffa0359024

[  815.844638] RBP: ffff88003b933e68 R08: 00000000000030d4 R09: 0000000000000100

[  815.844639] R10: 8000000000000000 R11: 0000000000000000 R12: ffffffffa0358000

[  815.844640] R13: 0000000000000000 R14: 0000000001005010 R15: 0000000000000000

[  815.844641] FS:  00007fb80a1f6700(0000) GS:ffff88003d600000(0000) knlGS:0000000000000000

[  815.844643] CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b

[  815.844643] CR2: 0000000000003333 CR3: 0000000036198000 CR4: 00000000000407f0

[  815.844687] Stack:

[  815.844689]  ffff88003b933e68 ffffffff8100215a 0000000001005010 0000000000000000

[  815.844691]  ffff88003b933e38 ffffffff8105ec93 0000000000000000 0000000000000000

[  815.844693]  0000000001005010 ffffffffa0020000 ffff88003b933e68 00000000181edb8b

[  815.844695] Call Trace:

[  815.844734]  [<ffffffff8100215a>] ? do_one_initcall+0xfa/0x1b0

[  815.844740]  [<ffffffff8105ec93>] ? set_memory_nx+0x43/0x50

[  815.844752]  [<ffffffff8175a462>] do_init_module+0x80/0x1d1

[  815.844759]  [<ffffffff810ec66d>] load_module+0x4ed/0x620

[  815.844761]  [<ffffffff810e9f10>] ? show_initstate+0x50/0x50

[  815.844763]  [<ffffffff810ec854>] SyS_init_module+0xb4/0x100

[  815.844798]  [<ffffffff8177b55d>] system_call_fastpath+0x1a/0x1f

[  815.844800] Code: <c7> 04 25 33 33 00 00 03 00 00 00 48 89 e5 e8 3c 16 40 e1 31 c0 5d

[  815.844809] RIP  [<ffffffffa0358014>] my_oops_init+0x14/0x30 [oops]

[  815.844812]  RSP <ffff88003b933de8>

[  815.844813] CR2: 0000000000003333

[  815.844818] ---[ end trace f8f9b64af5078acc ]---


看日志也是一个比较考验程序员的事情,从上面的日志看,我们可以看到 oops 发生的关键日志如下,其中还有一些函数的堆栈调用,但是这个不是重点。


[  815.844809] RIP  [<ffffffffa0358014>] my_oops_init+0x14/0x30 [oops]

[  815.844812]  RSP <ffff88003b933de8>

查看模块的加载地址


上面出现 oops 的是从模块的基地址偏移出来的地址,我们要找到基地址,然后再用基地址和偏移地址运算,就可以知道出现问题的偏移量了


root@ubuntu:~/linuxBook/oopsmodules# cat /proc/modules |grep oops

oops 13418 1 - Loading 0xffffffffa0358000 (OX+)

root@ubuntu:~/linuxBook/oopsmodules#


使用 addr2line 找到 oops 位置


知道了基地址和偏移地址,我们就可以知道偏移量了 offset = 0xffffffffa0358014 - 0xffffffffa0358000 = 0x14


root@ubuntu:~/linuxBook/oopsmodules# addr2line -e oops.o 0x14

/home/linux/linuxBook/oopsmodules/oops.c:12

root@ubuntu:~/linuxBook/oopsmodules#


这样知道代码导致 oops 的位置是第12 行。

通过objdump 来查找oops 位置


root@ubuntu:~/linuxBook/oopsmodules# objdump -dS --adjust-vma=0xffffffffa0358000 oops.ko

oops.ko:     file format elf64-x86-64

Disassembly of section .text:

ffffffffa0358000 <init_module>:

MODULE_LICENSE("Dual BSD/GPL");

MODULE_DESCRIPTION ("Oops");

MODULE_AUTHOR("weiqifa");

static int my_oops_init(void)

{

ffffffffa0358000: e8 00 00 00 00       callq  ffffffffa0358005 <init_module+0x5>

ffffffffa0358005: 55                   push   %rbp

int *a;

a = (int *)0x00003333;

*a = 3;

   printk(KERN_ALERT "oops %d\n",a);

ffffffffa0358006: be 33 33 00 00       mov    $0x3333,%esi

ffffffffa035800b: 48 c7 c7 00 00 00 00 mov    $0x0,%rdi

ffffffffa0358012: 31 c0                 xor    %eax,%eax

static int my_oops_init(void)

{

int *a;

a = (int *)0x00003333;

*a = 3;

ffffffffa0358014: c7 04 25 33 33 00 00 movl   $0x3,0x3333

ffffffffa035801b: 03 00 00 00

MODULE_LICENSE("Dual BSD/GPL");

MODULE_DESCRIPTION ("Oops");

MODULE_AUTHOR("weiqifa");

static int my_oops_init(void)

{

ffffffffa035801f: 48 89 e5             mov    %rsp,%rbp

int *a;

a = (int *)0x00003333;

*a = 3;

   printk(KERN_ALERT "oops %d\n",a);

ffffffffa0358022: e8 00 00 00 00       callq  ffffffffa0358027 <init_module+0x27>

   return 0;

}

ffffffffa0358027: 31 c0                 xor    %eax,%eax

ffffffffa0358029: 5d                   pop    %rbp

ffffffffa035802a: c3                   retq  

ffffffffa035802b: 0f 1f 44 00 00       nopl   0x0(%rax,%rax,1)

ffffffffa0358030 <cleanup_module>:

static void my_oops_exit(void)

{

ffffffffa0358030: e8 00 00 00 00       callq  ffffffffa0358035 <cleanup_module+0x5>

ffffffffa0358035: 55                   push   %rbp

   printk(KERN_ALERT "Goodbye, oops\n");

ffffffffa0358036: 48 c7 c7 00 00 00 00 mov    $0x0,%rdi

ffffffffa035803d: 31 c0                 xor    %eax,%eax

*a = 3;

   printk(KERN_ALERT "oops %d\n",a);

   return 0;

}

static void my_oops_exit(void)

{

ffffffffa035803f: 48 89 e5             mov    %rsp,%rbp

   printk(KERN_ALERT "Goodbye, oops\n");

ffffffffa0358042: e8 00 00 00 00       callq  ffffffffa0358047 <cleanup_module+0x17>

}

ffffffffa0358047: 5d                   pop    %rbp

ffffffffa0358048: c3                   retq  

ffffffffa0358049: 00 00                 add    %al,(%rax)

...

root@ubuntu:~/linuxBook/oopsmodules#

这样看就更加清晰了,我们知道出现 oops 的位置是 ffffffffa0358014 直接在上面找就可以看到了。


*a = 3;

ffffffffa0358014: c7 04 25 33 33 00 00 movl   $0x3,0x3333


使用函数dump_stack()调试内核


不知道大家有没有跟我一样的困惑,Linux 代码非常多,也非常大,有时候为了找到这个函数是从哪里调用的,需要花费非常多的时间去查找代码,而且很多宏函数还会蒙蔽你的双眼,让你沦陷在Linux 内核里面,这时候就需要这么一个函数,让你拨开层层乌云,看清它的真面目,这时候,你就会有一个感觉,我从哪里来,我要到哪里去?都是一清二楚了。


废话不多说,dump_stack()这个函数可以打印当前函数的上下文调用,让你更加直观的知道你的函数调用关系,这样,你就更清楚的知道,你的爸爸的妈妈的妹妹的姑姑的姐姐的儿子的弟弟是你的什么关系了。


代码

#include <linux/init.h>

#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)

{

   dump_stack();

   printk(KERN_ALERT "Hello, world\n");

   return 0;

}

static void hello_exit(void)

{

   printk(KERN_ALERT "Goodbye, cruel world\n");

}

module_init(hello_init);

module_exit(hello_exit);


执行sudo insmod hello.ko后的kernel日志如下

[176360.807755]  [<ffffffff81765bf5>] dump_stack+0x64/0x82

[176360.807776]  [<ffffffffa0275000>] ? 0xffffffffa0274fff

[176360.807779]  [<ffffffffa027500e>] hello_init+0xe/0x20 [hello]

[176360.807931]  [<ffffffff8100215a>] do_one_initcall+0xfa/0x1b0

[176360.808709]  [<ffffffff8105ec93>] ? set_memory_nx+0x43/0x50

[176360.808717]  [<ffffffff8175a462>] do_init_module+0x80/0x1d1

[176360.809093]  [<ffffffff810ec66d>] load_module+0x4ed/0x620

[176360.809097]  [<ffffffff810e9f10>] ? show_initstate+0x50/0x50

[176360.809100]  [<ffffffff810ec854>] SyS_init_module+0xb4/0x100

[176360.809187]  [<ffffffff8177b55d>] system_call_fastpath+0x1a/0x1f

[176360.809308] Hello, world


可以看到,里面的地址还有堆栈的调用,使用dump_stack函数不用外加什么头文件,使用起来非常方便,也不用指定说,只有在oops的时候才调用,正常的时候,我们想看函数调用关系的时候,也可以调用,就像我上面那样。


实现源码位置

/lib/dump_stack.c


几个比较关键的函数调用关系

主要想研究的同学可以去看看dump_stack_print_info 和show_stack里面的实现。


640?wx_fmt=png



dump_stack总结

我们知道,CPU工作的时候,有不同的工作模式,不同的工作模式,代表有不同的权限,就比如我是老板,我才可能有保险柜的钥匙。


这里有一个用户模式,超级用户模式,和中断模式,我们知道,每个进程分配的内存空间是不一样的,他们的堆栈有一个task_struct来维护,每个进程不能互相访问相互的内存,进程切换的时候,SP指针去执行要执行进程的堆栈地址,所以,我们可以知道一个事情,SP这个东东啊,是可以知道所有进程的东西的。


但是呢,操作系统在运行的时候,CPU肯定有需要从用户模式跳转到中断模式运行,进入中断的时候,SP指针这个东东,就体现出当前的堆栈了,所以dump_stack就是从这些不断的切换中,把堆栈地址给保存打印出来,得到一个上下文的调用关系。


使用vmlinux调试内核


我们知道,使用objdump反编译调试的是动态加载的内核模块,但是我们需要调试那些静态编译进入内核的那些代码,不能使用这个方法,这时候就需要使用vmlinux,vmlinux是每次编译内核的时候生成的内核符号表,里面包含了所有编译到内核的函数名,还有偏移地址。


使用方法(高通平台)

arm-eabi-gdb out/target/product/msm8625/obj/KERNEL_OBJ/vmlinux


在内核的.config里面要打开 DEBUG_INFO和DEBUG_VM


定位故障方法


(gdb) l * qrd7627a_add_io_devices+0x100

0xc07cd05c is in qrd7627a_add_io_devices (/home/yejialong/GH700C/kernel/arch/arm/mach-msm/msm8x25/goso-msm7627a-io.c:1851).

1846            } else if (machine_is_msm8625q_skud() || machine_is_msm8625q_evbd()) {

1847        #ifndef CONFIG_CALA02

1848                    platform_device_register(&pmic_mpp_leds_pdev_skud);

1849        #endif

1850                    /* enable the skud flash and torch by gpio leds driver */

1851                    platform_device_register(&gpio_flash_skud);

1852            } else if (machine_is_msm8625q_skue()) {

1853                     /* enable the skue flashlight by gpio leds driver */

1854                    platform_device_register(&gpio_flash_skue);

1855            }


使用方法(MTK平台)

weiqifa@weiqifa-Inspiron-3847:~/weiqifa/tm100$ ./prebuilts/gcc/linux-x86/arm/arm-eabi-4.7/bin/arm-eabi-gdb ./out/target/product/tm100/obj/KERNEL_OBJ/vmlinux

GNU gdb (GDB) 7.3.1-gg2

Copyright (C) 2011 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.  Type "show copying"

and "show warranty" for details.

This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android".

For bug reporting instructions, please see:

<http://source.android.com/source/report-bugs.html>...

Reading symbols from /home/weiqifa/weiqifa/tm100/out/target/product/tm100/obj/KERNEL_OBJ/vmlinux...done.

(gdb)


使用方法(rockchip平台)


./prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-addr2line -f -e kernel/vmlinux

weiqifa@dev:~/rk3399_7in1$ ./prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-addr2line -f -e kernel/vmlinux ffffff8008459f3c

rk_iommu_domain_free

/data/weiqifa/rk3399_7in1/kernel/drivers/iommu/rockchip-iommu.c:1005 (discriminator 2)

weiqifa@dev:~/rk3399_7in1$


一些调试相关的命令

查看中断

linux@ubuntu:/usr/src/linux-headers-3.13.0-117/kernel$ cat /proc/interrupts

           CPU0      

  0:         26   IO-APIC-edge      timer

  1:      12200   IO-APIC-edge      i8042

  8:          1   IO-APIC-edge      rtc0

  9:          0   IO-APIC-fasteoi   acpi

 12:     110704   IO-APIC-edge      i8042

 14:          0   IO-APIC-edge      ata_piix

 15:          0   IO-APIC-edge      ata_piix

 16:      21165   IO-APIC-fasteoi   vmwgfx, snd_ens1371

 17:      76243   IO-APIC-fasteoi   ehci_hcd:usb1, ioc0

 18:        657   IO-APIC-fasteoi   uhci_hcd:usb2

 19:     197583   IO-APIC-fasteoi   eth0

 40:          0   PCI-MSI-edge      PCIe PME, pciehp


这个可以在嵌入式调试的时候查看中断是否被触发,非常有作用。


查看工作队列

cat /proc/sched_debug


查看内核定时器

cat /proc/timer_list


proc应该重点关注

proc下面的文件系统应该重点关注,内核的调试信息很多都在这里面,特别是初学者,把下面的每个文件夹都看看,作用还是非常明显的。


下周开始,我们可能进去封闭开发了,时间会更加紧张,可能会分享一些心得,连载的话我还是会写,只会迟到不会缺席,这样也是对自己的一种鞭策。


共勉,加油~

======================

640?wx_fmt=png





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

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

相关文章

儿童手表怎么删除联系人_华为儿童手表4X体验:与你一起守护孩子的成长,带娃不再辛苦...

带娃是一件很辛苦的事情&#xff0c;从身体到精神的辛苦&#xff0c;相信各位家长都懂。对于照看正在成长期的孩子&#xff0c;更是让很多家长亲身感受"成长的烦恼"。孩子活泼好动的天性让很多家长都不放心&#xff0c;同时大部分家长又没有能力随时在身边守护&#…

Jmeter分布式测试过程中遇到的问题及摘抄前辈问题汇总

遇到的常见问题&#xff1a; 1、在Controller端上控制某台机器Run&#xff0c;提示"Bad call to remote host"。 解决方法&#xff1a;检查被控制机器上的jmeter-server有没有启动&#xff0c;或者JMeter.properties中remote_hosts的配置错误。2、Agent机器启动Jmete…

介绍一个我创业的朋友

大家好&#xff0c;今天给大家介绍一位跟我一样正在创业路上的朋友&#xff0c;不知道大家对我之前的文章还有没有印象&#xff0c;最近我在做一件特别有意思的事情&#xff0c;这件有意思的事情一直催促着我起床上班&#xff0c;每天都充满能量和动力&#xff0c;又累又充实的…

微信小程序装修解决方案ppt_装修公司微信小程序都有哪些功能?

传统装修行业存在收费不透明、消费者装修服务过程体验差等问题&#xff0c;传统装修已无法满足消费者的实际需求&#xff0c;面临无客量、无签约的困境。然而&#xff0c;随着移动互联网的发展&#xff0c;许多装饰企业利用微信小程序来帮助其疏导和推广。一个装修公司小程序能…

要用什么态度去面对生活?

最近&#xff0c;张扣扣的新闻铺天盖地&#xff0c;因为我非常喜欢逛知乎&#xff0c;刚好张扣扣的新闻这几天上了知乎热搜&#xff0c;所以我就关注上了&#xff0c;说实话&#xff0c;有点痛心&#xff0c;外人看起来很爽&#xff0c;忍辱负重好多年&#xff0c;终于把自己的…

第3章 Linux内核调试手段之三

之前的内核调试&#xff0c;我觉得应该再加上下面的东西&#xff0c;只有好好把下面的几个问题研究透了&#xff0c;你可能才是一个真正的内核高手&#xff0c;或者说&#xff0c;你还不是一个高手&#xff0c;就是一个内核的普通工程师&#xff0c;这个是我和Z总聊天说的&…

中希尔排序例题代码_【数据结构与算法】这或许是东半球分析十大排序算法最好的一篇文章...

码农有道 历史文章目录(请戳我)关于码农有道(请戳我)前言本文全长 14237 字&#xff0c;配有 70 张图片和动画&#xff0c;和你一起一步步看懂排序算法的运行过程。预计阅读时间 47 分钟&#xff0c;强烈建议先收藏然后通过电脑端进行阅读。No.1 冒泡排序冒泡排序无疑是最为出名…

星期四随笔

周二的晚上&#xff0c;发了一篇文章&#xff0c;题目是《要用什么态度去面对生活》&#xff0c;发文是凌晨1点&#xff0c;早上7点起来&#xff0c;用手机刷了一下&#xff0c;很多回复&#xff0c;其中几个回复有点偏激&#xff0c;感觉被刺痛了&#xff0c;毕竟是睡着的&…

根据大小分割大文本_基于深度学习的图像分割在高德地图的实践

一、前言图像分割(Image Segmentation)是计算机视觉领域中的一项重要基础技术&#xff0c;是图像理解中的重要一环。图像分割是将数字图像细分为多个图像子区域的过程&#xff0c;通过简化或改变图像的表示形式&#xff0c;让图像能够更加容易被理解。更简单地说&#xff0c;图…

别人不让你发传单怎么办?

人生在世难免遇到几个傻逼&#xff0c;我今天没有上班&#xff0c;去外面兼职发了一天的传单&#xff0c;也许你们没有过发传单的经历&#xff0c;当然了&#xff0c;我也不是想为了增加人生阅历去发传单&#xff0c;单纯就是今天没什么事情&#xff0c;刚刚好公司停电&#xf…

模块化加载_谈谈双亲委派模型的第四次破坏-模块化

前言JDK9引入了Java模块化系统(Java Platform Moudle System)来实现可配置的封装隔离机制&#xff0c;同时JVM对类加载的架构也做出了调整&#xff0c;也就是双亲委派模型的第四次破坏。前三次破坏分别是&#xff1a;双亲委派模型推出之前&#xff0c;SPI机制&#xff0c;以及O…

第4章 原子操作

有一件事情&#xff0c;你不得不承认&#xff0c;C语言相对汇编来说是高级语言&#xff0c;为什么&#xff0c;因为高级语言会形成封装&#xff0c;比如&#xff0c;我需要把一个变量A&#xff0c;对于CPU来说&#xff0c;先从内存里把这个变量读进运算寄存器&#xff0c;然后运…

龙芯下中标系统C语言查找设备号_龙芯:主控进驻国产激光打印机并适配麒麟OS...

导读&#xff1a;龙芯中科宣布&#xff0c;龙芯1C0300B作为主控芯片&#xff0c;已经批量用于天津光电出品的多款激光打印机中&#xff0c;在打印扫描、通信控制、协议解析方面发挥着重要的作用。图&#xff1a;龙芯1C0300B 主控芯片龙芯1C系列是基于GS232处理器核的高性价比单…

随笔日记

最近加班比较多&#xff0c;昨晚上又坐了最后一班地铁回家&#xff0c;回到家已经是一点了&#xff0c;在路上遇到一个美团外卖的小哥&#xff0c;小哥跟我说&#xff0c;嗨&#xff0c;帅哥&#xff0c;我想借你的头盔用一下&#xff0c;你们可以想象一下&#xff0c;在晚上12…

C#的命名空间

对于大型组织而言&#xff0c;如果涉及到产品线&#xff0c;项目&#xff0c;公共平台很多&#xff0c;如何通过命名空间将所有代码有效的组织起来。就一个目的&#xff0c;用的时候能够很容易的找到。 对于命名空间在大型项目中&#xff0c;必须组织好&#xff0c;一般命名空间…

今日头条关键词排名怎么搜索_公众号搜索关键词排名、公众号怎么排名靠前

真正的互联网老鸟其实都知道&#xff0c;不管在线上推广什么产品都好&#xff0c;其实都没有所谓的难度&#xff0c;获取流量方式其实就是简单的一批&#xff0c;哪里有怎么玄乎&#xff0c;不管以后遇到什么问题&#xff0c;除了首先需要知道的核心关键词&#xff0c;无非就是…

​CPU单挑到群架发展史

CPU 又称中央处理器&#xff0c;搞软件开发的兄弟已经耳熟能详了&#xff0c;CPU 的发展史最基本的矛盾就是软件性能需求的快速提升与 CPU 技术工艺性能提升发展相对缓慢之间的矛盾。摩尔定律也是基于此背景&#xff0c;将硬件性能的有限提升给软件开发者一个预期&#xff0c;告…

JDK、JRE、JVM三者间的关系

JDK&#xff08;Java Development Kit&#xff09;是针对Java开发员的产品&#xff0c;是整个Java的核心&#xff0c;包括了Java运行环境JRE、Java工具和Java基础类库。Java Runtime Environment&#xff08;JRE&#xff09;是运行JAVA程序所必须的环境的集合&#xff0c;包含J…

典型方法_裴礼文老师编数学分析中的典型问题与方法练习参考答案的说明

裴礼文老师编《数学分析中的典型问题与方法》练习参考答案该书共计7章36节1036页&#xff0c;各章由知识点总结、例题和练习组成&#xff0c;知识点基本按照传统《数学分析》教材先后顺序跟进。例题和练习出自《数学分析》经典习题和近年《数学分析》考研试题&#xff0c;极具代…

第4章 原子操作 第二节

-------------------------------------------------------------上一章节说的原子操作&#xff0c;有同学在下面留言说&#xff0c;原子操作不是万能的吧&#xff1f;确实是&#xff0c;原子操作不是万能的&#xff0c;体系结构在完成原子操作部分也是花费了很大的心思&#x…