-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
511 lines (264 loc) · 270 KB
/
atom.xml
File metadata and controls
511 lines (264 loc) · 270 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Maxw的小站</title>
<subtitle>Maxw学习记录</subtitle>
<link href="https://mackz-maxw.github.io/atom.xml" rel="self"/>
<link href="https://mackz-maxw.github.io/"/>
<updated>2025-12-04T22:53:35.605Z</updated>
<id>https://mackz-maxw.github.io/</id>
<author>
<name>Mackz-Maxw</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>操作系统基础 | 6.1 定时器与时间管理 - 定时器:节拍,赫兹和jiffies</title>
<link href="https://mackz-maxw.github.io/2025/12/04/oper_sys29timer/"/>
<id>https://mackz-maxw.github.io/2025/12/04/oper_sys29timer/</id>
<published>2025-12-04T22:51:56.771Z</published>
<updated>2025-12-04T22:53:35.605Z</updated>
<content type="html"><![CDATA[<h3 id="定时器与时间管理"><strong>定时器与时间管理</strong></h3><p>时间的流逝对内核至关重要。大量内核函数是由时间驱动的,而非事件驱动¹。其中一些函数是周期性的,例如调度器运行队列的平衡或屏幕刷新。它们按照固定计划执行,比如每秒100次。内核还会在未来某个相对时间调度其他函数,例如延迟的磁盘I/O。举例来说,内核可能会调度一个500毫秒后执行的任务。最后,内核还必须管理系统运行时间以及当前日期和时间。</p><p>需注意相对时间与绝对时间的区别。调度一个5秒后发生的事件不需要绝对时间的概念——只需相对时间(例如,从现在起5秒后)。相反,管理当前时间不仅要求内核理解时间的流逝,还需要掌握某种绝对时间度量。这两个概念对时间管理都至关重要。</p><p>此外,处理周期性事件与内核调度未来特定时间点事件的方式在实现上有所不同。周期性事件(比如每10毫秒一次)由系统定时器驱动。系统定时器是一种可编程硬件,能够以固定频率发出中断。该定时器的中断处理程序(称为定时器中断)负责更新系统时间并执行周期性工作。系统定时器及其定时器中断是Linux的核心,也是本章的重点内容。</p><p>本章的另一个重点是动态定时器,这种机制用于调度在指定时间间隔后仅执行一次的事件。例如,软盘设备驱动程序使用定时器在指定空闲时间后关闭软盘驱动器电机。内核可以动态创建和销毁定时器。本章将探讨动态定时器的内核实现,以及可供代码调用的相关接口。</p><p>¹更准确地说,时间驱动事件也属于事件驱动——这里的事件即指时间的流逝。但在本章中,我们特别强调时间驱动事件,因为其在内核中出现的频率及重要性尤为突出。</p><h3 id="内核的时间概念"><strong>内核的时间概念</strong></h3><p>显然,计算机对时间的理解有些抽象。实际上,内核必须与系统硬件协同工作来理解和管理时间。硬件提供了一个系统定时器,内核借助它来度量时间的流逝。该系统定时器基于电子时钟源运行,例如数字时钟或处理器频率。系统定时器会按照预设频率(称为节拍率)触发(通常称为"命中"或"弹出")。当系统定时器触发时,它会发出一个中断,内核通过特定的中断处理程序来处理该中断。</p><p>由于内核知晓预设的节拍率,它就能计算出任意两次连续定时器中断之间的时间间隔。这个周期称为一个"节拍",相当于1/(节拍率) 秒。这正是内核追踪实际时间和系统运行时间的方式。</p><p>实际时间(即一天中的具体时刻)对用户空间应用程序至关重要。内核之所以要追踪实际时间,根本原因在于内核控制着定时器中断。一系列系统调用向用户空间提供日期和时间信息。系统运行时间(即系统启动后的相对时间)对内核空间和用户空间都很有用。大量代码必须感知时间的流逝。两次运行时间读数(当前值与过去值)之间的差值,就是这种相对性的简单度量。</p><p>定时器中断对操作系统的管理至关重要。大量内核功能的生灭都与时间流逝紧密相关。定时器中断定期执行的部分工作包括:</p><ul><li>更新系统运行时间</li><li>更新实际时间</li><li>在SMP系统上,确保调度器运行队列处于平衡状态,若不平衡则进行平衡调整(如第4章"进程调度"所述)</li><li>运行所有已到期的动态定时器</li><li>更新资源使用情况和处理器时间统计信息</li></ul><p>其中部分工作会在每次定时器中断时执行——也就是说,这些工作以节拍率的频率执行。而其他函数则定期执行,但仅在第n次定时器中断时才触发。也就是说,这些函数以节拍率的某个分数频率执行。在"定时器中断处理程序"一节中,我们将详细探讨该中断处理程序。</p><h3 id="节拍率hz">节拍率:HZ</h3><p>系统定时器的频率(即节拍率)是在系统启动时,基于一个静态的预处理器定义HZ 来设定的。对于每个受支持的体系结构,HZ的值都不同。在某些受支持的体系结构中,它甚至在不同的机器类型之间也存在差异。</p><p>内核在 <code><asm/param.h></code>头文件中定义了该值。节拍率的频率为 HZ 赫兹,周期为 1/HZ秒。例如,在默认情况下,x86 架构将 HZ 定义为 100。因此,i386上的定时器中断频率为 100Hz,即每秒发生 100次(每百分之一秒一次,也就是每 10 毫秒一次)。HZ 的其他常见值还有 250 和1000,分别对应 4 毫秒和 1 毫秒的周期。</p><p>在编写内核代码时,切勿假定 HZ具有任何特定值。如今这已不是一个常见的错误,因为许多体系结构的节拍率各不相同。然而,在过去,Alpha是唯一节拍率不等于 100Hz 的架构,经常会看到代码错误地硬编码了值100,而实际上本应使用 HZ 值。后文将展示在内核代码中使用 HZ 的示例。</p><p>定时器中断的频率至关重要。正如您所看到的,定时器中断执行大量工作。实际上,内核的整个时间概念都源于系统定时器的周期性。选择合适的值,就像经营一段成功的关系,全在于权衡妥协。</p><h4 id="理想的-hz-值">理想的 HZ 值</h4><p>从 Linux 的最初版本开始,i386 架构的定时器中断频率一直是 100Hz。然而,在 2.5 开发系列期间,频率被提升到了 1000Hz,并且(像这类事情一样)引起了争议。尽管频率后来又回到了 100Hz,但它现在是一个配置选项,允许用户编译具有自定义 HZ值的内核。由于系统的许多部分都依赖于定时器中断,改变其频率会对系统产生显著影响。当然,选择较大或较小的HZ 值各有优缺点。</p><p>提高节拍率意味着定时器中断运行得更频繁。因此,它执行的工作也会更频繁地发生。这带来以下好处:</p><ul><li>定时器中断具有更高的分辨率,因此所有定时事件也具有更高的分辨率。</li><li>定时事件的准确性得到提高。</li></ul><p>分辨率随着节拍率的提高而同比例提升。例如,当 HZ=100时,定时器的粒度是 10 毫秒。换句话说,所有周期性事件都沿着定时器中断的10毫秒周期发生,无法保证更精细的精度(我们这里使用的是计算机领域的"精度"含义,而非科学上的。科学上的精度是对可重复性的统计度量。在计算机中,精度是指用于表示一个值的有效数字位数)。而当HZ=1000 时,分辨率是 1 毫秒——精细了 10 倍。尽管内核代码可以创建具有 1毫秒分辨率的定时器,但并不能保证在 HZ=100 时提供的精度足以在优于 10毫秒间隔的任何时间点上执行定时器。</p><p>同样,准确性也以相同的方式提高。假设内核在随机时间启动定时器,由于定时器可能在任何时间到期,但仅在定时器中断发生时才会被执行,因此定时器的平均误差为定时器中断周期的一半。例如,对于HZ=100,事件发生的时间平均会在期望时间的 +/- 5毫秒范围内。因此,平均误差为 5 毫秒。对于 HZ=1000,平均误差降至 0.5毫秒——提高了十倍。</p><h4 id="提高-hz-值节拍率的优势"><strong>提高 HZ值(节拍率)的优势</strong></h4><p>更高的分辨率和准确性带来了多重优势:</p><ul><li>内核定时器以更精细的分辨率和更高的准确性执行。(这带来了大量改进,其中之一如下所述。)</li><li>诸如 <code>poll()</code> 和 <code>select()</code>这类可选择使用超时值的系统调用,能够以更高的精度执行。</li><li>资源使用情况或系统运行时间等测量值,能以更精细的分辨率被记录。</li><li>进程抢占的发生更加精确。</li></ul><p>最显而易见的性能提升,来自于 <code>poll()</code> 和<code>select()</code>超时精度的改善。这种改进可能相当显著;一个重度使用这些系统调用的应用程序,可能会浪费大量时间等待定时器中断,而实际上超时时间早已到期。请记住,平均误差(即可能浪费的时间)是定时器中断周期的一半。</p><p>提高节拍率的另一个好处是进程抢占的准确性更高,从而降低了调度延迟。回顾第4 章,定时器中断负责递减运行进程的时间片计数。当计数减至零时,会设置<code>need_resched</code>标志,并且内核会尽快运行调度器。现在假设一个给定的进程正在运行,其时间片剩余2 毫秒。在 2毫秒后,调度器应该抢占当前运行进程并开始执行一个新进程。但不幸的是,这个事件直到下一次定时器中断发生时才会被处理,而这可能不是在2 毫秒之后。最坏的情况下,下一次定时器中断可能要在 <code>1/HZ</code>秒之后才会到来!当 <code>HZ=100</code> 时,一个进程可能额外多运行近 10毫秒。当然,这一切会达到平衡,公平性得以保持,因为所有任务在调度时都承受着相同的不精确度——但问题不在这里。问题的根源在于延迟抢占所产生的<strong>延迟</strong>。如果待调度的任务有对时间敏感的操作需要执行,例如重新填充音频缓冲区,那么这种延迟可能是不可接受的。将节拍率提高到1000Hz,能将最坏情况下的调度超限降低到仅 1毫秒,平均情况下的超限降低到仅 0.5 毫秒。</p><h4 id="提高-hz-值节拍率的劣势"><strong>提高 HZ值(节拍率)的劣势</strong></h4><p>既然提高节拍率有这么多好处,那它一定有某些缺点,否则最初就会设定为1000Hz(甚至更高)。确实,存在一个主要问题:更高的节拍率意味着更频繁的定时器中断,也就意味着更高的开销,因为处理器必须花费更多时间来执行定时器中断处理程序。节拍率越高,处理器执行定时器中断所花费的时间就越多。这不仅导致可用于其他工作的处理器时间减少,还会更频繁地冲击处理器的缓存并增加功耗。</p><p>关于开销影响的问题存在争议。从 <code>HZ=100</code> 提高到<code>HZ=1000</code>显然会带来十倍的开销。然而,最初的开销究竟有多大呢?最终的共识是,至少在现代系统上,<code>HZ=1000</code>并不会产生不可接受的开销,并且向 1000Hz定时器的转变对性能的损害并不大。尽管如此,在 2.6内核中,仍然可以在编译时为 <code>HZ</code>设置不同的值(并非任意值,例如在x86上一般为100,500和1000)。</p><p><strong>无节拍操作系统</strong></p><p>您可能会问,操作系统是否真的需要一个固定的定时器中断?尽管这已成为 40年来的常态,几乎所有通用操作系统都采用类似于本章所述的定时器中断,但Linux 内核支持一个称为"无节拍操作"的选项。当内核构建时设置了<code>CONFIG_HZ</code>配置选项,系统会根据待处理的定时器动态地调度定时器中断。定时器中断不再是固定地每1毫秒触发一次,而是根据需要被动态地调度和重新调度。如果下一个定时器设定在3 毫秒后触发,那么定时器中断就在 3毫秒后触发。在此之后,如果没有工作需要处理的时间长达 50毫秒,内核会将中断重新调度到 50 毫秒后再触发。</p><p>减少开销是受欢迎的,但真正的收益在于功耗的节省,尤其是在空闲系统上。在基于标准节拍的系统中,即使是在空闲期间,内核也需要处理定时器中断。而在无节拍系统中,空闲时刻不会被不必要的定时中断所打断,从而降低了系统功耗。无论空闲期是200 毫秒还是 200 秒,长期累积的收益将转化为可观的节电效果。</p><h3 id="jiffies变量">jiffies变量</h3><ul><li>jiffies是一个全局变量,记录自系统启动以来发生的时钟“滴答”(tick)数量。内核在启动时将其初始化为0,并在每次定时器中断时将其加 1。</li><li>由于每秒有 HZ 次定时器中断,所以每秒有 HZ 个jiffies。系统运行时间(uptime)等于 jiffies / HZ(秒)。</li><li>实际实现略有复杂:内核初始化 jiffies为一个“特殊的初始值”(offset),使变量更频繁地溢出以便捕捉bug;要取得真实值时要先减去这个偏移。</li></ul><h4 id="在内核中的声明与常用换算">在内核中的声明与常用换算</h4><ul><li>jiffies 在头文件 <linux/jiffies.h> 中声明为: extern unsignedlong volatile jiffies;</li><li>把秒转换为 jiffies(ticks): seconds * HZ</li><li>把 jiffies 转换为秒: jiffies / HZ</li><li>将秒换算为 ticks 更常见,例如设置未来某个时间点: unsigned longtime_stamp = jiffies; /* now <em>/ unsigned long next_tick = jiffies +1; /</em> one tick from now <em>/ unsigned long later = jiffies +5</em>HZ; /* five seconds from now <em>/ unsigned long fraction =jiffies + HZ / 10; /</em> a tenth of a second from now */</li><li>一般只有与用户空间通信时才会把 ticks换算为秒,内核内部通常不关心绝对时间。</li></ul><h4 id="jiffies-的内部表示与溢出问题">jiffies 的内部表示与溢出问题</h4><ul><li>jiffies 一直定义为 unsigned long:在 32 位架构上是 32 位,在 64位架构上是 64 位。</li><li>示例溢出时间:若 HZ=100,32-bit jiffies 大约在 497 天后溢出;若HZ=1000,则约在 49.7 天后溢出。</li><li>如果在所有架构上都把 jiffies 存为 64 位(u64),对于合理的 HZ值,jiffies将几乎永远不会溢出。但出于性能和历史兼容性的考虑,开发者希望保留 jiffies为 unsigned long。</li><li>解决办法(linker magic):在 <linux/jiffies.h> 中除了 jiffies之外还声明了一个 64 位变量: extern u64 jiffies_64;链接脚本(ld)在链接内核镜像时将 jiffies 覆盖到 jiffies_64 的起始位置:jiffies = jiffies_64; 因此,在 32 位机器上,jiffies 代表 jiffies_64 的低32 位;大多数代码仍然直接读 jiffies(低 32位),而时间管理代码会使用完整的 64 位值以防止溢出。</li><li>在 64 位架构上,jiffies_64 和 jiffies 指向相同的值;可以直接读取jiffies,也可以调用 get_jiffies_64() 来读取完整的 64位值,二者等效。</li></ul><blockquote><p>在 32 位架构上,无法以原子方式一次性读取 64 位值的两个 32位字。因此需要get_jiffies_64()来读取完整的 64 位jiffies。该特殊函数在读取之前通过 xtime_lock 对 jiffies计数加锁,从而保证读取的一致性与原子性。</p></blockquote><h4 id="jiffies-的环绕wraparound">jiffies 的环绕(Wraparound)</h4><p>像任何 C 整数一样,当 jiffies增加超过其能表示的最大值时会发生溢出(wraparound)。对于 32位无符号整数,最大值是 2^32 − 1 =4294967295,因此计数在达到该值后再加一就会回绕到 0。</p><p>举例说明一个潜在的问题: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">unsigned</span> <span class="type">long</span> timeout = jiffies + HZ/<span class="number">2</span>; <span class="comment">/* timeout in 0.5s */</span></span><br><span class="line"><span class="comment">/* do some work ... */</span></span><br><span class="line"><span class="comment">/* then see whether we took too long */</span></span><br><span class="line"><span class="keyword">if</span> (timeout > jiffies) {</span><br><span class="line"> <span class="comment">/* we did not time out, good ... */</span></span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">/* we timed out, error ... */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure> 意图是在半秒后超时。若在设置<code>timeout</code> 之后 <code>jiffies</code> 发生了回绕(从最大值回到0),那么 <code>jiffies</code> 可能变得比 <code>timeout</code> 小,导致<code>if</code>条件结果被颠倒(逻辑上已经超时但条件判断显示未超时),从而产生错误。</p><p>为避免这种情况,内核提供了四个用于比较 tick 计数的宏(位于<code><linux/jiffies.h></code>),它们能正确处理回绕。下面是简化版定义:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> time_after(unknown, known) ((long)(known) - (long)(unknown) < 0)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> time_before(unknown, known) ((long)(unknown) - (long)(known) < 0)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> time_after_eq(unknown, known) ((long)(unknown) - (long)(known) >= 0)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> time_before_eq(unknown, known) ((long)(known) - (long)(unknown) >= 0)</span></span><br></pre></td></tr></table></figure> - 参数说明:<code>unknown</code> 通常为<code>jiffies</code>(当前时间),<code>known</code>为要比较的目标时间(例如 <code>timeout</code>)。 - 语义: -<code>time_after(unknown, known)</code>:若 unknown 在 known 之后则返回true。 - <code>time_before(unknown, known)</code>:若 unknown 在 known之前则返回 true。 - 带 <code>_eq</code> 的版本在等于时也返回 true。</p><p>使用这些宏修改后的超时示例: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">unsigned</span> <span class="type">long</span> timeout = jiffies + HZ/<span class="number">2</span>; <span class="comment">/* timeout in 0.5s */</span></span><br><span class="line"><span class="comment">/* ... */</span></span><br><span class="line"><span class="keyword">if</span> (time_before(jiffies, timeout)) {</span><br><span class="line"> <span class="comment">/* we did not time out, good ... */</span></span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">/* we timed out, error ... */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>为什么这些宏能防止回绕带来的错误? -这些宏利用有符号整数(long)的减法和符号位来判断先后关系:无论是否发生了回绕,通过把差值作为带符号数比较,仍能得到正确的先后顺序(在合理的时间差范围内,这些宏为标准做法并被广泛使用)。你可以用不同参数值自己演练,模拟某一参数回绕到0 后的情况,观察结果如何保持正确。</p><h2 id="用户空间与-hzuser-space-and-hz">用户空间与 HZ(User-Space andHZ)</h2><p>早期内核(2.6 之前)直接以内核的 HZ 值向用户空间导出基于 tick的值,这会导致当内核 HZ 改变时用户空间看到的数值不再正确 ——因为某些用户空间程序假定了特定的 HZ 值。举例:若内核 HZ 被扩大,导出的uptime 等值会被误放大,用户看到的“20 小时”实际上可能只有 2 小时。</p><p>为避免这种兼容性问题,内核定义了 USER_HZ,表示用户空间所期望的 HZ值(在 x86 上历史上 USER_HZ = 100)。内核中提供了函数用来把以内核 HZ单位计数的 jiffies 转换成以 USER_HZ 单位的值: -jiffies_to_clock_t():将(32 位)jiffies(以 HZ 为单位)转换为以 USER_HZ为单位的 “clock ticks” 值。 - jiffies_64_to_clock_t():将 64 位jiffies_64 从 HZ 转换到 USER_HZ。</p><p>当 USER_HZ 与 HZ 是整倍数关系、且 USER_HZ ≤ HZ时,转换表达式较简单,例如: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> x / (HZ / USER_HZ);</span><br></pre></td></tr></table></figure>若两者不是整倍数关系,则使用更复杂的算法以保持精度与兼容性。</p><p>示例(将计时结果转换并输出给用户空间): <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">unsigned</span> <span class="type">long</span> start;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> total_time;</span><br><span class="line"></span><br><span class="line">start = jiffies;</span><br><span class="line"><span class="comment">/* do some work ... */</span></span><br><span class="line">total_time = jiffies - start;</span><br><span class="line">printk(<span class="string">"That took %lu ticks\n"</span>, <span class="type">jiffies_to_clock_t</span>(total_time));</span><br></pre></td></tr></table></figure>用户空间期望看到的值是以 USER_HZ 为单位的ticks。如果你想对用户更友好地显示,通常应把结果转换为秒:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">printk(<span class="string">"That took %lu seconds\n"</span>, total_time / HZ);</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="定时器与时间管理"><strong>定时器与时间管理</strong></h3>
<p>时间的流逝对内核至关重要。大量内核函数是由时间驱动的,而非事件驱动¹。其中一些函数是周期性的,例如调度器运行队列的平衡或屏幕刷新。它们按照固定计划执行,比如每秒100次。内核</summary>
<category term="os basic" scheme="https://mackz-maxw.github.io/categories/os-basic/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-图论1</title>
<link href="https://mackz-maxw.github.io/2025/11/20/kamacode43graph/"/>
<id>https://mackz-maxw.github.io/2025/11/20/kamacode43graph/</id>
<published>2025-11-20T20:56:23.312Z</published>
<updated>2025-11-20T20:56:32.029Z</updated>
<content type="html"><![CDATA[<h3 id="卡码网-98.-所有可达路径">卡码网 98. 所有可达路径</h3><p>给定一个有 n 个节点的有向无环图,节点编号从 1 到n。请编写一个程序,找出并返回所有从节点 1 到节点 n的路径。每条路径应以节点编号的列表形式表示</p><p>输入描述 > 第一行包含两个整数 N,M,表示图中拥有 N 个节点,M 条边> 后续 M 行,每行包含两个整数 s 和 t,表示图中的 s 节点与 t节点中有一条路径</p><p>输出描述 >输出所有的可达路径,路径中所有节点之间空格隔开,每条路径独占一行,存在多条路径,路径输出的顺序可任意。如果不存在任何一条路径,则输出-1。 > 注意输出的序列中,最后一个节点后面没有空格! 例如正确的答案是<code>1 3 5</code>,而不是 <code>1 3 5</code>, 5后面没有空格!</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><vector></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><list></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line">vector<vector<<span class="type">int</span>>> result;</span><br><span class="line">vector<<span class="type">int</span>> path;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">dfs</span><span class="params">(<span class="type">const</span> vector<list<<span class="type">int</span>>> &vnode, <span class="type">int</span> n)</span></span>{</span><br><span class="line"> <span class="comment">// if(path.empty())path.push_back(1);</span></span><br><span class="line"> <span class="type">int</span> cur_i = path.<span class="built_in">back</span>();</span><br><span class="line"> <span class="keyword">if</span>(cur_i == n){</span><br><span class="line"> result.<span class="built_in">push_back</span>(path);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> list<<span class="type">int</span>> n_i = vnode[cur_i];</span><br><span class="line"> <span class="keyword">if</span>(!n_i.<span class="built_in">empty</span>()){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i : n_i){</span><br><span class="line"> path.<span class="built_in">push_back</span>(i);</span><br><span class="line"> <span class="built_in">dfs</span>(vnode, n);</span><br><span class="line"> path.<span class="built_in">pop_back</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="type">int</span> n, m;</span><br><span class="line"> cin >> n >> m;</span><br><span class="line"> vector<list<<span class="type">int</span>>> <span class="built_in">vnode</span>(n+<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < m; i++){</span><br><span class="line"> <span class="type">int</span> node, next_i;</span><br><span class="line"> cin >> node >> next_i;</span><br><span class="line"> vnode[node].<span class="built_in">push_back</span>(next_i);</span><br><span class="line"> }</span><br><span class="line"> path.<span class="built_in">push_back</span>(<span class="number">1</span>);</span><br><span class="line"> <span class="built_in">dfs</span>(vnode, n);</span><br><span class="line"> <span class="keyword">if</span>(result.<span class="built_in">empty</span>()){</span><br><span class="line"> cout << <span class="number">-1</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">auto</span> &path : result){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < path.<span class="built_in">size</span>()<span class="number">-1</span>; i++){</span><br><span class="line"> cout << path[i] << <span class="string">' '</span>;</span><br><span class="line"> }</span><br><span class="line"> cout << path.<span class="built_in">back</span>() << endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h3 id="卡码网-98.-所有可达路径">卡码网 98. 所有可达路径</h3>
<p>给定一个有 n 个节点的有向无环图,节点编号从 1 到
n。请编写一个程序,找出并返回所有从节点 1 到节点 n
的路径。每条路径应以节点编号的列表形式表示</p>
<p>输入描</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-单调栈2</title>
<link href="https://mackz-maxw.github.io/2025/11/20/kamacode42monos/"/>
<id>https://mackz-maxw.github.io/2025/11/20/kamacode42monos/</id>
<published>2025-11-20T20:55:57.047Z</published>
<updated>2025-11-20T20:56:09.120Z</updated>
<content type="html"><![CDATA[<h3 id="接雨水">42. 接雨水</h3><p>求雨水高度,需要弹出当前池底值,再求两边最小:min(凹槽左边高度,凹槽右边高度) - 凹槽底部高度 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">trap</span><span class="params">(vector<<span class="type">int</span>>& height)</span> </span>{</span><br><span class="line"> <span class="type">int</span> water = <span class="number">0</span>;</span><br><span class="line"> stack<<span class="type">int</span>> st;</span><br><span class="line"> st.<span class="built_in">push</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i < height.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">while</span>(!st.<span class="built_in">empty</span>() && height[i] > height[st.<span class="built_in">top</span>()]){</span><br><span class="line"> <span class="type">int</span> mid = st.<span class="built_in">top</span>();</span><br><span class="line"> st.<span class="built_in">pop</span>();</span><br><span class="line"> <span class="keyword">if</span>(!st.<span class="built_in">empty</span>()){</span><br><span class="line"> <span class="type">int</span> h = <span class="built_in">min</span>(height[i], height[st.<span class="built_in">top</span>()]) - height[mid];</span><br><span class="line"> <span class="type">int</span> w = i - st.<span class="built_in">top</span>()<span class="number">-1</span>;</span><br><span class="line"> water += h * w;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> st.<span class="built_in">push</span>(i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> water;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="柱状图中最大的矩形">84. 柱状图中最大的矩形</h3><p>这题我初步想法是对于每个柱,求以它为高度的最大矩形。但是具体怎么用类似前后缀表的方法优化查询,我有点没思路。看了题解反应过来还是要用单调栈求区间的宽和高,同时因为我们要弹出一个元素来获取左边元素的下标,为了头尾元素能顺利出栈,需要在前后都加入0<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">largestRectangleArea</span><span class="params">(vector<<span class="type">int</span>>& heights)</span> </span>{</span><br><span class="line"> stack<<span class="type">int</span>> st;</span><br><span class="line"> <span class="type">int</span> max_h = <span class="number">0</span>;</span><br><span class="line"> heights.<span class="built_in">insert</span>(heights.<span class="built_in">begin</span>(), <span class="number">0</span>);</span><br><span class="line"> heights.<span class="built_in">push_back</span>(<span class="number">0</span>);</span><br><span class="line"> st.<span class="built_in">push</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i < heights.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">if</span>(heights[i] >= heights[st.<span class="built_in">top</span>()]){</span><br><span class="line"> st.<span class="built_in">push</span>(i);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">while</span>(!st.<span class="built_in">empty</span>() && heights[i] < heights[st.<span class="built_in">top</span>()]){</span><br><span class="line"> <span class="type">int</span> mid_i = st.<span class="built_in">top</span>();</span><br><span class="line"> st.<span class="built_in">pop</span>();</span><br><span class="line"> <span class="type">int</span> left_i = st.<span class="built_in">top</span>();</span><br><span class="line"> <span class="type">int</span> w = i - left_i - <span class="number">1</span>;</span><br><span class="line"> <span class="type">int</span> h = heights[mid_i];</span><br><span class="line"> max_h = <span class="built_in">max</span>(max_h, w * h);</span><br><span class="line"> }</span><br><span class="line"> st.<span class="built_in">push</span>(i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> max_h;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="接雨水">42. 接雨水</h3>
<p>求雨水高度,需要弹出当前池底值,再求两边最小:min(凹槽左边高度,
凹槽右边高度) - 凹槽底部高度 <figure class="highlight cpp"><table><tr><td class="gutte</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-单调栈1</title>
<link href="https://mackz-maxw.github.io/2025/10/25/kamacode41monos/"/>
<id>https://mackz-maxw.github.io/2025/10/25/kamacode41monos/</id>
<published>2025-10-26T01:27:51.591Z</published>
<updated>2025-10-26T01:28:12.781Z</updated>
<content type="html"><![CDATA[<h3 id="每日温度">739. 每日温度</h3><p>维护一个栈来记录未更新的数组值<code>using xx = xxxx</code>仅可用于为现有变量创建别名,如果数组变量名太长请创建引用<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">dailyTemperatures</span><span class="params">(vector<<span class="type">int</span>>& temperatures)</span> </span>{</span><br><span class="line"> stack<<span class="type">int</span>> st;</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">out_v</span><span class="params">(temperatures.size(), <span class="number">0</span>)</span></span>;</span><br><span class="line"> st.<span class="built_in">push</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i < temperatures.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">if</span>(temperatures[i] <= temperatures[st.<span class="built_in">top</span>()]){</span><br><span class="line"> st.<span class="built_in">push</span>(i);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">while</span>(!st.<span class="built_in">empty</span>() && temperatures[i] > temperatures[st.<span class="built_in">top</span>()]){</span><br><span class="line"> out_v[st.<span class="built_in">top</span>()] = i - st.<span class="built_in">top</span>();</span><br><span class="line"> st.<span class="built_in">pop</span>();</span><br><span class="line"> }</span><br><span class="line"> st.<span class="built_in">push</span>(i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> out_v;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="下一个更大元素-i">496.下一个更大元素 I</h3><p>和上一题很像的思路,但是需要借助两个数组都没有重复数字的假设构造map,使答案不超时<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string"><unordered_map></span></span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">nextGreaterElement</span><span class="params">(vector<<span class="type">int</span>>& nums1, vector<<span class="type">int</span>>& nums2)</span> </span>{</span><br><span class="line"> unordered_map<<span class="type">int</span>, <span class="type">int</span>>mp;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < nums1.<span class="built_in">size</span>(); i++){</span><br><span class="line"> mp[nums1[i]] = i;</span><br><span class="line"> }</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">ng</span><span class="params">(nums1.size(), <span class="number">-1</span>)</span></span>;</span><br><span class="line"> stack<<span class="type">int</span>> st;</span><br><span class="line"> st.<span class="built_in">push</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i < nums2.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">while</span>(!st.<span class="built_in">empty</span>() && nums2[i] > nums2[st.<span class="built_in">top</span>()]){</span><br><span class="line"> <span class="type">int</span> ntop = nums2[st.<span class="built_in">top</span>()];</span><br><span class="line"> <span class="keyword">if</span>(mp.<span class="built_in">find</span>(ntop) != mp.<span class="built_in">end</span>()){</span><br><span class="line"> ng[mp[ntop]] = nums2[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line"> st.<span class="built_in">pop</span>();</span><br><span class="line"> }</span><br><span class="line"> st.<span class="built_in">push</span>(i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ng;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br></pre></td></tr></table></figure></p><h3 id="下一个更大元素ii">503.下一个更大元素II</h3><p>我想的是找到最大的元素,从下一个数开始遍历一遍,不过看题解直接遍历两遍数组即可<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">nextGreaterElements</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">res</span><span class="params">(nums.size(), <span class="number">-1</span>)</span></span>;</span><br><span class="line"> <span class="comment">// int max_n = nums[0];</span></span><br><span class="line"> <span class="comment">// int max_i = 0;</span></span><br><span class="line"> <span class="comment">// for(int i = 0; i < nums.size(); i++){</span></span><br><span class="line"> <span class="comment">// if(nums[i] > max_n){</span></span><br><span class="line"> <span class="comment">// max_n = nums[i];</span></span><br><span class="line"> <span class="comment">// max_i = i;</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> stack<<span class="type">int</span>> st;</span><br><span class="line"> st.<span class="built_in">push</span>(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j < nums.<span class="built_in">size</span>()*<span class="number">2</span>; j++){</span><br><span class="line"> <span class="type">int</span> n_i = j % nums.<span class="built_in">size</span>();</span><br><span class="line"> <span class="keyword">while</span>(!st.<span class="built_in">empty</span>() && nums[n_i] > nums[st.<span class="built_in">top</span>()]){</span><br><span class="line"> res[st.<span class="built_in">top</span>()] = nums[n_i];</span><br><span class="line"> st.<span class="built_in">pop</span>();</span><br><span class="line"> }</span><br><span class="line"> st.<span class="built_in">push</span>(n_i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="每日温度">739. 每日温度</h3>
<p>维护一个栈来记录未更新的数组值
<code>using xx = xxxx</code>仅可用于为现有变量创建别名,如果数组变量名太长请创建引用
<figure class="highlight cpp"><t</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-动态规划13</title>
<link href="https://mackz-maxw.github.io/2025/10/24/kamacode40palin/"/>
<id>https://mackz-maxw.github.io/2025/10/24/kamacode40palin/</id>
<published>2025-10-24T14:50:36.603Z</published>
<updated>2025-10-24T14:50:45.924Z</updated>
<content type="html"><![CDATA[<h3 id="回文子串">647. 回文子串</h3><p>首先是找到递推关系,对于字符串中下标i-j的字串,如果<code>s[i] == s[j]</code>则可以由<code>dp[i+1][j-1]</code>推出<code>dp[i][j]</code>然后是遍历顺序,因为要先知道<code>dp[i+1][j-1]</code>,所以要从下往上,从左至右遍历<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">countSubstrings</span><span class="params">(string s)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">bool</span>>> <span class="built_in">dp</span>(s.<span class="built_in">size</span>(),<span class="built_in">vector</span><<span class="type">bool</span>>(s.<span class="built_in">size</span>(), <span class="literal">false</span>));</span><br><span class="line"> <span class="type">int</span> cnt = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = s.<span class="built_in">size</span>()<span class="number">-1</span>; i >= <span class="number">0</span>; i--){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = i; j < s.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(s[i] != s[j]){</span><br><span class="line"> dp[i][j] = <span class="literal">false</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">if</span>(j - i <= <span class="number">1</span>){</span><br><span class="line"> dp[i][j] = <span class="literal">true</span>;</span><br><span class="line"> cnt++;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = dp[i+<span class="number">1</span>][j<span class="number">-1</span>];</span><br><span class="line"> <span class="keyword">if</span>(dp[i][j] == <span class="literal">true</span>)cnt++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cnt;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="最长回文子序列">516.最长回文子序列</h3><p>和上一题思路类似 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">longestPalindromeSubseq</span><span class="params">(string s)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">int</span>>><span class="built_in">dp</span>(s.<span class="built_in">size</span>(), <span class="built_in">vector</span><<span class="type">int</span>>(s.<span class="built_in">size</span>(),<span class="number">0</span>));</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = s.<span class="built_in">size</span>()<span class="number">-1</span>; i >= <span class="number">0</span>; i--){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = i; j < s.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(s[i] != s[j]){</span><br><span class="line"> dp[i][j] = <span class="built_in">max</span>(dp[i+<span class="number">1</span>][j], dp[i][j<span class="number">-1</span>]);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">if</span>(i == j){</span><br><span class="line"> dp[i][j] = <span class="number">1</span>;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(i - j == <span class="number">1</span>){</span><br><span class="line"> dp[i][j] = <span class="number">2</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = dp[i+<span class="number">1</span>][j<span class="number">-1</span>]+<span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> dp[<span class="number">0</span>][s.<span class="built_in">size</span>()<span class="number">-1</span>];</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="回文子串">647. 回文子串</h3>
<p>首先是找到递推关系,对于字符串中下标i-j的字串,如果<code>s[i] == s[j]</code>则可以由<code>dp[i+1][j-1]</code>推出<code>dp[i][j]</code>
然</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>代码随想录 | 八股-DNS与CDN</title>
<link href="https://mackz-maxw.github.io/2025/10/21/kamabagu8/"/>
<id>https://mackz-maxw.github.io/2025/10/21/kamabagu8/</id>
<published>2025-10-21T23:38:41.797Z</published>
<updated>2025-10-21T23:41:44.017Z</updated>
<content type="html"><![CDATA[<h3 id="dns查询过程">DNS查询过程</h3><p>DNS 用来将主机名和域名转换为IP地址, 其查询过程一般通过以下步骤:</p><p>本地DNS缓存检查:首先查询本地DNS缓存,如果缓存中有对应的IP地址,则直接返回结果。如果本地缓存中没有,则会向本地的DNS服务器(通常由你的互联网服务提供商(ISP)提供,比如中国移动)发送一个DNS查询请求。如果本地DNS解析器有该域名的ip地址,就会直接返回,如果没有缓存该域名的解析记录,它会向根DNS服务器发出查询请求。根DNS服务器并不负责解析域名,但它能告诉本地DNS解析器应该向哪个顶级域(.com/.net/.org)的DNS服务器继续查询。本地DNS解析器接着向指定的顶级域名DNS服务器发出查询请求。顶级域DNS服务器也不负责具体的域名解析,但它能告诉本地DNS解析器应该前往哪个权威DNS服务器查询下一步的信息。本地DNS解析器最后向权威DNS服务器发送查询请求。权威DNS服务器是负责存储特定域名和IP地址映射的服务器。当权威DNS服务器收到查询请求时,它会查找"example.com"域名对应的IP地址,并将结果返回给本地DNS解析器。本地DNS解析器将收到的IP地址返回给浏览器,并且还会将域名解析结果缓存在本地,以便下次访问时更快地响应。浏览器发起连接:本地DNS解析器已经将IP地址返回给您的计算机,您的浏览器可以使用该IP地址与目标服务器建立连接,开始获取网页内容。</p><h3 id="cdn是什么有什么作用">CDN是什么,有什么作用?</h3><p>CDN是一种分布式网络服务,通过将内容存储在分布式的服务器上,使用户可以从距离较近的服务器获取所需的内容,从而加速互联网上的内容传输。</p><p>就近访问:CDN在全球范围内部署了多个服务器节点,用户的请求会被路由到距离最近的 CDN节点,提供快速的内容访问。 内容缓存:CDN节点会缓存静态资源,如图片、样式表、脚本等。当用户请求访问这些资源时,CDN会首先检查是否已经缓存了该资源。如果有缓存,CDN节点会直接返回缓存的资源,如果没有缓存所需资源,它会从源服务器(原始服务器)回源获取资源,并将资源缓存到节点中,以便以后的请求。通过缓存内容,减少了对原始服务器的请求,减轻了源站的负载。可用性:即使某些节点出现问题,用户请求可以被重定向到其他健康的节点。</p>]]></content>
<summary type="html"><h3 id="dns查询过程">DNS查询过程</h3>
<p>DNS 用来将主机名和域名转换为IP地址, 其查询过程一般通过以下步骤:</p>
<p>本地DNS缓存检查:首先查询本地DNS缓存,如果缓存中有对应的IP地址,则直接返回结果。
如果本地缓存中没有,则会向本地</summary>
<category term="comp basic" scheme="https://mackz-maxw.github.io/categories/comp-basic/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-模拟笔试1</title>
<link href="https://mackz-maxw.github.io/2025/10/21/kamapentest_data/"/>
<id>https://mackz-maxw.github.io/2025/10/21/kamapentest_data/</id>
<published>2025-10-21T16:22:01.115Z</published>
<updated>2025-10-21T16:21:42.381Z</updated>
<content type="html"><![CDATA[<h3 id="卡码笔试-263.数据重删">卡码笔试 263.数据重删</h3><p>题目描述:输入一串存储的数据,用N表示数据个数,用K表示数据块的大小,设计一个方法判断当前数据块是否和前面的数据块有重复,两个数据块内容完全一样则表示重复,如果重复则将这个数据块删除,并且在第一个出现数据块的后面增加重复数据的计数,输出经过重删之后的数据内容。输入示例: <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">8 </span><br><span class="line">3 </span><br><span class="line">3 4 5 3 4 5 5 4</span><br></pre></td></tr></table></figure> 输出示例: <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">3 4 5 2 5 4 1</span><br></pre></td></tr></table></figure> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><map></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><vector></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="type">int</span> total;</span><br><span class="line"> <span class="type">int</span> len;</span><br><span class="line"> <span class="type">int</span> c;</span><br><span class="line"> vector<<span class="type">int</span>> data;</span><br><span class="line"> cin>>total>>len;</span><br><span class="line"> <span class="keyword">while</span>(cin>>c){</span><br><span class="line"> data.<span class="built_in">push_back</span>(c);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> map<vector<<span class="type">int</span>>, <span class="type">int</span>> mp;</span><br><span class="line"> vector<vector<<span class="type">int</span>>> uniqueBlock;</span><br><span class="line"> vector<<span class="type">bool</span>> isUnique;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < data.<span class="built_in">size</span>(); i+=len){</span><br><span class="line"> <span class="type">int</span> cnt = <span class="number">0</span>;</span><br><span class="line"> vector<<span class="type">int</span>> block;</span><br><span class="line"> <span class="keyword">while</span>(cnt < len && (i+cnt) < data.<span class="built_in">size</span>()){</span><br><span class="line"> block.<span class="built_in">push_back</span>(data[i+cnt]);</span><br><span class="line"> <span class="comment">// cout<<"pushed in block: "<<data[i+cnt]<<endl;</span></span><br><span class="line"> cnt++;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line"> mp[block]++;</span><br><span class="line"> <span class="keyword">if</span>(mp[block] == <span class="number">1</span>){</span><br><span class="line"> isUnique.<span class="built_in">push_back</span>(<span class="literal">true</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> isUnique.<span class="built_in">push_back</span>(<span class="literal">false</span>);</span><br><span class="line"> }</span><br><span class="line"> uniqueBlock.<span class="built_in">push_back</span>(block);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < uniqueBlock.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">if</span>(isUnique[i] == <span class="literal">true</span>){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> n: uniqueBlock[i]){</span><br><span class="line"> cout<<n<<<span class="string">' '</span>;</span><br><span class="line"> }</span><br><span class="line"> cout<<mp[uniqueBlock[i]]<<<span class="string">' '</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// cout<<"total data: "<<total<<endl;</span></span><br><span class="line"> <span class="comment">// cout<<"len of blocks: "<<len<<endl;</span></span><br><span class="line"> <span class="comment">// for(int i: data){</span></span><br><span class="line"> <span class="comment">// cout<<i<<' ';</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="卡码笔试-263.数据重删">卡码笔试 263.数据重删</h3>
<p>题目描述:
输入一串存储的数据,用N表示数据个数,用K表示数据块的大小,设计一个方法判断当前数据块是否和前面的数据块有重复,两个数据块内容完全一样则表示重复,如果重复则将这个数据块删除,</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>算法笔试 | 某厂后端复盘</title>
<link href="https://mackz-maxw.github.io/2025/10/21/intv_xp/"/>
<id>https://mackz-maxw.github.io/2025/10/21/intv_xp/</id>
<published>2025-10-21T16:18:49.849Z</published>
<updated>2025-10-20T16:54:44.309Z</updated>
<content type="html"><![CDATA[<p>这回笔试,我选择寄得比算法多(好久没刷八股题了=_=||),某境外电商的算法还是相对简单的</p><h3 id="mysql的默认事务隔离级别">MySQL的默认事务隔离级别</h3><p>REPEATABLE READ</p><h3 id="算法">算法</h3><h4 id="我ac了">我ac了:</h4><ul><li>求杨辉三角指定行指定区间的和(暴力就可以了)</li><li>给定一个数组,这个数组的每一项是一个模块的单元测试,每次合并两个模块都需要执行两个模块单元测试数之和,问合并所有模块需要的最小测试数是多少(从小到大sort一下,再相加即可)</li></ul><h4 id="考试时发现我忘记了">考试时发现我忘记了:</h4><p>sort库函数怎么传递比较参数(比如我不需要默认的从小到大,而是从大到小该怎么办)怎么构造并使用大/小顶堆</p><h4 id="我没有ac">我没有ac:</h4><p>打包员有m个相同重量上限k的袋子,需要打包weights数组个物品,这些物品一定是从前向后依次打包的,已知物品个数n,每个物品重量的数组weights,求k的最小值</p><p>这个我本来以为需要背包算法,后来发现二分查找就可以了</p>]]></content>
<summary type="html"><p>这回笔试,我选择寄得比算法多(好久没刷八股题了=_=||),某境外电商的算法还是相对简单的</p>
<h3 id="mysql的默认事务隔离级别">MySQL的默认事务隔离级别</h3>
<p>REPEATABLE READ</p>
<h3 id="算法">算法</h</summary>
<category term="interview" scheme="https://mackz-maxw.github.io/categories/interview/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-动态规划12</title>
<link href="https://mackz-maxw.github.io/2025/10/21/kamacode39editd/"/>
<id>https://mackz-maxw.github.io/2025/10/21/kamacode39editd/</id>
<published>2025-10-21T16:17:10.448Z</published>
<updated>2025-10-21T16:23:32.802Z</updated>
<content type="html"><![CDATA[<h3 id="不同的子序列">115.不同的子序列</h3><p>这题我想到要对于t的每个字符,一在s里匹配到,就从两串的下一个字符开始往后匹配。这么一看感觉开始递归了,不太动态规划。</p><p>题解的递推公式如下: 这一类问题,基本是要分析两种情况 -<code>s[i - 1]</code> 与 <code>t[j - 1]</code>相等 -<code>s[i - 1]</code> 与 <code>t[j - 1]</code> 不相等</p><p>当<code>s[i - 1]</code> 与<code>t[j - 1]</code>相等时,<code>dp[i][j]</code>可以有两部分组成。 -一部分是用<code>s[i - 1]</code>来匹配,那么个数为<code>dp[i - 1][j - 1]</code>。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要<code>dp[i-1][j-1]</code>。 -一部分是不用<code>s[i - 1]</code>来匹配,个数为<code>dp[i - 1][j]</code>。当<code>s[i - 1]</code> 与<code>t[j - 1]</code>不相等时,<code>dp[i][j]</code>只有一部分组成,不用<code>s[i - 1]</code>来匹配(就是模拟在s中删除这个元素),即:<code>dp[i - 1][j]</code></p><p>注意上限长度且所有字符一样的s和t,会使得dp超<code>long long</code>的限制,此时使用<code>uint64_t</code><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">numDistinct</span><span class="params">(string s, string t)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">uint64_t</span>>> <span class="built_in">dp</span>(t.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="built_in">vector</span><<span class="type">uint64_t</span>>(s.<span class="built_in">size</span>()+<span class="number">1</span>,<span class="number">0</span>));</span><br><span class="line"> <span class="comment">// dp[0][0] = 1;</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>; i<s.<span class="built_in">size</span>();i++){</span><br><span class="line"> <span class="keyword">if</span>(s[i] == t[<span class="number">0</span>])dp[<span class="number">0</span>][i] = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">1</span>; i<=t.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j=<span class="number">1</span>; j<=s.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(s[j<span class="number">-1</span>] == t[i<span class="number">-1</span>]){</span><br><span class="line"> dp[i][j] = dp[i<span class="number">-1</span>][j<span class="number">-1</span>] + dp[i][j<span class="number">-1</span>];</span><br><span class="line"> <span class="comment">// if(i == 1)dp[i][j] = max(dp[i][j], 1);</span></span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = dp[i][j<span class="number">-1</span>];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// for(auto c:dp){</span></span><br><span class="line"> <span class="comment">// for(int c_i : c){</span></span><br><span class="line"> <span class="comment">// cout<<c_i<<' ';</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="keyword">return</span> dp[t.<span class="built_in">size</span>()][s.<span class="built_in">size</span>()];</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="两个字符串的删除操作">583. 两个字符串的删除操作</h3><p>和上一题有些类似的思路 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">minDistance</span><span class="params">(string word1, string word2)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">int</span>>> <span class="built_in">dp</span>(word1.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="built_in">vector</span><<span class="type">int</span>>(word2.<span class="built_in">size</span>()+<span class="number">1</span>,<span class="number">0</span>));</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= word1.<span class="built_in">size</span>(); i++)dp[i][<span class="number">0</span>] = i;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j <= word2.<span class="built_in">size</span>(); j++)dp[<span class="number">0</span>][j] = j;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= word1.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j <= word2.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(word1[i<span class="number">-1</span>] == word2[j<span class="number">-1</span>]){</span><br><span class="line"> dp[i][j] = dp[i<span class="number">-1</span>][j<span class="number">-1</span>];</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = <span class="built_in">min</span>(dp[i<span class="number">-1</span>][j], dp[i][j<span class="number">-1</span>]) + <span class="number">1</span>;</span><br><span class="line"> dp[i][j] = <span class="built_in">min</span>(dp[i][j], dp[i<span class="number">-1</span>][j<span class="number">-1</span>]+<span class="number">2</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// for(auto line : dp){</span></span><br><span class="line"> <span class="comment">// for(auto i : line){</span></span><br><span class="line"> <span class="comment">// cout<<i<<' ';</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="keyword">return</span> dp[word1.<span class="built_in">size</span>()][word2.<span class="built_in">size</span>()];</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="编辑距离">72. 编辑距离</h3><p>跟上两题差不多的思路,差点就写对了 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">minDistance</span><span class="params">(string word1, string word2)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">int</span>>> <span class="built_in">dp</span>(word1.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="built_in">vector</span><<span class="type">int</span>>(word2.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < word1.<span class="built_in">size</span>(); i++)dp[i+<span class="number">1</span>][<span class="number">0</span>] = (i+<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">0</span>; i < word2.<span class="built_in">size</span>(); i++)dp[<span class="number">0</span>][i+<span class="number">1</span>] = (i+<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// dp[0][0] = 1;</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= word1.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j <= word2.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(word1[i<span class="number">-1</span>] != word2[j<span class="number">-1</span>]){</span><br><span class="line"> dp[i][j] = <span class="built_in">min</span>(dp[i<span class="number">-1</span>][j]+<span class="number">1</span>, dp[i][j<span class="number">-1</span>]+<span class="number">1</span>);</span><br><span class="line"> dp[i][j] = <span class="built_in">min</span>(dp[i][j], dp[i<span class="number">-1</span>][j<span class="number">-1</span>]+<span class="number">1</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = dp[i<span class="number">-1</span>][j<span class="number">-1</span>];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// for(auto line : dp){</span></span><br><span class="line"> <span class="comment">// for(int i : line){</span></span><br><span class="line"> <span class="comment">// cout << i << ' ';</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="comment">// cout << endl;</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="keyword">return</span> dp[word1.<span class="built_in">size</span>()][word2.<span class="built_in">size</span>()];</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="不同的子序列">115.不同的子序列</h3>
<p>这题我想到要对于t的每个字符,一在s里匹配到,就从两串的下一个字符开始往后匹配。这么一看感觉开始递归了,不太动态规划。</p>
<p>题解的递推公式如下: 这一类问题,基本是要分析两种情况 -
<code</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-动态规划11</title>
<link href="https://mackz-maxw.github.io/2025/10/02/kamacode38seq2/"/>
<id>https://mackz-maxw.github.io/2025/10/02/kamacode38seq2/</id>
<published>2025-10-02T21:48:44.109Z</published>
<updated>2025-10-11T15:30:22.702Z</updated>
<content type="html"><![CDATA[<h3 id="最长公共子序列">1143.最长公共子序列</h3><p>按照动态规划,每次比较成功就加一,取所有值最大的思路是错的,这种会把乱序但是相同的字符也算进去。于是我想了半天怎么先循环以i,j为右下角的正方形,本来想的是记忆化将i,j赋值为比较后相等的值,看了题解发现得在递推公式上作更改因为字符中间可能会插入别的字符,所以ac,ace的比较结果和ac,aced的比较结果是一样的<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">longestCommonSubsequence</span><span class="params">(string text1, string text2)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">int</span>>> <span class="built_in">dp</span>(text1.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="built_in">vector</span><<span class="type">int</span>> (text2.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= text1.<span class="built_in">size</span>();i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j <= text2.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(text1[i<span class="number">-1</span>] == text2[j<span class="number">-1</span>]){</span><br><span class="line"> dp[i][j] = dp[i<span class="number">-1</span>][j<span class="number">-1</span>]+<span class="number">1</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = <span class="built_in">max</span>(dp[i<span class="number">-1</span>][j], dp[i][j<span class="number">-1</span>]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> dp[text1.<span class="built_in">size</span>()][text2.<span class="built_in">size</span>()];</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="不相交的线">1035.不相交的线</h3><p>既然一个数只能连一根线,那么其实和上一题是一个意思了<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">maxUncrossedLines</span><span class="params">(vector<<span class="type">int</span>>& nums1, vector<<span class="type">int</span>>& nums2)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">int</span>>> <span class="built_in">dp</span>(nums1.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="built_in">vector</span><<span class="type">int</span>> (nums2.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= nums1.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j <= nums2.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(nums1[i<span class="number">-1</span>] == nums2[j<span class="number">-1</span>]){</span><br><span class="line"> dp[i][j] = dp[i<span class="number">-1</span>][j<span class="number">-1</span>] + <span class="number">1</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = <span class="built_in">max</span>(dp[i<span class="number">-1</span>][j], dp[i][j<span class="number">-1</span>]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> dp[nums1.<span class="built_in">size</span>()][nums2.<span class="built_in">size</span>()];</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="最大子序和">53. 最大子序和</h3><p>我想的是,<code>dp[i]</code>由<code>dp[i-1]</code>,<code>dp[i-1]+nums[i]</code>,<code>nums[i]</code>中的最大值决定,但是这样解会算出不连续的最大值</p><p>题解的推算法是这样的: <code>dp[i]</code>只有两个方向可以推出来: -<code>dp[i-1] + nums[i]</code>,即:<code>nums[i]</code>加入当前连续子序列和- <code>nums[i]</code>,即:从头开始计算当前连续子序列和再找每个的<code>dp[i]</code>最大值 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">maxSubArray</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">dp</span><span class="params">(nums.size(), <span class="number">0</span>)</span></span>;</span><br><span class="line"> dp[<span class="number">0</span>] = nums[<span class="number">0</span>];</span><br><span class="line"> <span class="type">int</span> res = nums[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i < nums.<span class="built_in">size</span>(); i++){</span><br><span class="line"> dp[i] = <span class="built_in">max</span>(dp[i<span class="number">-1</span>]+nums[i], nums[i]);</span><br><span class="line"> <span class="keyword">if</span>(dp[i] > res)res = dp[i];</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> res;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="判断子序列">392.判断子序列</h3><p>常规做的话双指针法即可,按照动态规划来做是和第一题是差不多的思路<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">bool</span> <span class="title">isSubsequence</span><span class="params">(string s, string t)</span> </span>{</span><br><span class="line"> vector<vector<<span class="type">int</span>>> <span class="built_in">dp</span>(s.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="built_in">vector</span><<span class="type">int</span>>(t.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i <= s.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j <= t.<span class="built_in">size</span>(); j++){</span><br><span class="line"> <span class="keyword">if</span>(s[i<span class="number">-1</span>] == t[j<span class="number">-1</span>]){</span><br><span class="line"> dp[i][j] = dp[i<span class="number">-1</span>][j<span class="number">-1</span>] + <span class="number">1</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> dp[i][j] = dp[i][j<span class="number">-1</span>];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> dp[s.<span class="built_in">size</span>()][t.<span class="built_in">size</span>()] == s.<span class="built_in">size</span>() ? <span class="literal">true</span> : <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="最长公共子序列">1143.最长公共子序列</h3>
<p>按照动态规划,每次比较成功就加一,取所有值最大的思路是错的,这种会把乱序但是相同的字符也算进去。
于是我想了半天怎么先循环以i,j为右下角的正方形,本来想的是记忆化将i,j赋值为比较后相等的值,看了题</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>代码随想录 | 八股-TCP连接</title>
<link href="https://mackz-maxw.github.io/2025/10/02/kamabagu7/"/>
<id>https://mackz-maxw.github.io/2025/10/02/kamabagu7/</id>
<published>2025-10-02T21:29:05.157Z</published>
<updated>2025-10-02T21:48:16.113Z</updated>
<content type="html"><![CDATA[<h3id="tcp连接三次握手的过程为什么是三次可以是两次或者更多吗">TCP连接三次握手的过程,为什么是三次,可以是两次或者更多吗?</h3><ol type="1"><li>三次握手的过程</li></ol><p>第一次握手:客户端向服务器发送一个SYN(同步序列编号)报文,请求建立连接,客户端进入SYN_SENT 状态。第二次握手:服务器收到SYN 报文后,如果同意建立连接,则会发送一个SYN-ACK(同步确认)报文作为响应,同时进入SYN_RCVD 状态。第三次握手:客户端收到服务器的SYN-ACK 报文后,会发送一个ACK(确认)报文作为最终响应,之后客户端和服务器都进入ESTABLISHED状态,连接建立成功。</p><p>(2)为什么需要三次握手因为TCP需要简历双向的数据连接。通过三次握手,客户端和服务器都能够确认对方的接收和发送能力。第一次握手确认了客户端到服务器的通道是开放的;第二次握手确认了服务器到客户端的通道是开放的;第三次握手则确认了客户端接收到服务器的确认,从而确保了双方的通道都是可用的。</p><p>而如果仅使用两次握手,服务器可能无法确定客户端的接收能力是否正常,比如客户端可能已经关闭了连接,但之前发送的连接请求报文在网络上延迟到达了服务器,服务器就会主动去建立一个连接,但是客户端接收不到,导致资源的浪费。而四次握手可以优化为三次。</p><h3id="tcp连接四次挥手的过程为什么是四次">TCP连接四次挥手的过程,为什么是四次?</h3><p>(1)四次挥手的过程</p><p>第一次挥手:客户端发送一个FIN报文给服务端,表示自己要断开数据传送,报文中会指定一个序列号(seq=x)。然后,客户端进入FIN-WAIT-1 状态。第二次挥手:服务端收到FIN报文后,回复ACK报文给客户端,且把客户端的序列号值+1,作为ACK报文的序列号(seq=x+1)。然后,服务端进入CLOSE-WAIT(seq=x+1)状态,客户端进入FIN-WAIT-2状态。第三次挥手:服务端也要断开连接时,发送FIN报文给客户端,且指定一个序列号(seq=y+1),随后服务端进入LAST-ACK状态。第四次挥手:客户端收到FIN报文后,发出ACK报文进行应答,并把服务端的序列号值+1作为ACK报文序列号(seq=y+2)。此时客户端进入TIME-WAIT状态。服务端在收到客户端的ACK报文后进入CLOSE 状态。如果客户端等待2MSL没有收到回复,才关闭连接。</p><p>(2)为什么需要四次挥手</p><p>TCP是全双工通信,可以双向传输数据。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后才会完全关闭TCP连接。因此两次挥手可以释放一端到另一端的TCP连接,完全释放连接一共需要四次挥手。</p><p>只有通过四次挥手,才可以确保双方都能接收到对方的最后一个数据段的确认,主动关闭方在发送完最后一个ACK后进入TIME-WAIT状态,这是为了确保被动关闭方接收到最终的ACK,如果被动关闭方没有接收到,它可以重发FIN报文,主动关闭方可以再次发送ACK 。</p><p>而如果使用三次挥手,被动关闭方可能在发送最后一个数据段后立即关闭连接,而主动关闭方可能还没有接收到这个数据段的确认。</p><h3id="http的keep-alive是什么tcp-的-keepalive-和-http-的-keep-alive-是一个东西吗">HTTP的Keep-Alive是什么?TCP的 Keepalive 和 HTTP 的 Keep-Alive 是一个东西吗?</h3><p>HTTP 的 Keep-Alive,是由应用层实现的,称为 HTTP 长连接每次请求都要经历这样的过程:建立 TCP连接 -> HTTP请求资源 ->响应资源 ->释放连接,这就是HTTP短连接,但是这样每次建立连接都只能请求一次资源,所以HTTP的 Keep-Alive实现了使用同一个 TCP 连接来发送和接收多个 HTTP请求/应答,避免了连接建立和释放的开销,就就是 HTTP长连接。通过设置HTTP头Connection: keep-alive来实现。</p><p>TCP 的 Keepalive,是由TCP 层(内核态)实现的,称为 TCP保活机制,是一种用于在 TCP 连接上检测空闲连接状态的机制当TCP连接建立后,如果一段时间内没有任何数据传输,TCPKeepalive会发送探测包来检查连接是否仍然有效。</p><p>补充说明:</p><p>其实这里tcp的keepalive,不只是支持http,还可以支持ftp和smtp的,他是一个能力,类似于gc。</p><p>http的这个keepalive感觉更是一种策略吧,比如你有一个http用了keepalive,然后过了一会,你不传输数据了,这个时候没有通知对方close,这个时候tcp的keepalive就会起到用处去关闭这次链接。</p>]]></content>
<summary type="html"><h3
id="tcp连接三次握手的过程为什么是三次可以是两次或者更多吗">TCP连接三次握手的过程,为什么是三次,可以是两次或者更多吗?</h3>
<ol type="1">
<li>三次握手的过程</li>
</ol>
<p>第一次握手:客户端向服务器发送一个SYN</summary>
<category term="comp basic" scheme="https://mackz-maxw.github.io/categories/comp-basic/"/>
</entry>
<entry>
<title>操作系统基础 | 5.8 进程实验</title>
<link href="https://mackz-maxw.github.io/2025/09/26/oper_sys28lab_process/"/>
<id>https://mackz-maxw.github.io/2025/09/26/oper_sys28lab_process/</id>
<published>2025-09-27T01:27:32.248Z</published>
<updated>2025-09-30T16:27:46.725Z</updated>
<content type="html"><![CDATA[<h3 id="进程家族树-process-family-tree"><strong>进程家族树 (ProcessFamily Tree)</strong></h3><ol type="1"><li><p><strong>实验报告</strong>:准备好实验报告</p></li><li><p><strong>simple_fork.c</strong>:编写一个名为<code>simple_fork.c</code> 的简短程序,使用 <code>fork()</code>函数从父进程生成一个子进程。父进程应在 <code>fork</code>之前打印一条语句,并在 <code>fork</code> 之后打印出子进程的PID。子进程应在被生成后打印一条语句,使用 <code>getpid()</code> 和<code>getppid()</code> 函数打印出其自身的 PID 及其父进程的PID。在您的树莓派上编译并运行您的程序,并将程序的输出作为此问题的答案。提示:gcc编译<code>gcc [source] -o [destination]</code></p></li><li><p><strong>tree_fork.c</strong>:编写第二个名为<code>tree_fork.c</code>的程序,该程序将一个整数作为命令行参数,并在最多 10代的固定限制内,生成一个具有指定代数的“二进制家族树”进程。这将创建<code>2^n - 1</code> 个进程,其中 <code>n</code> 是代数。对于n=1,程序将创建 1 个进程;对于 n=5,将创建 31 个进程;对于 n=10,将创建1023 个进程,依此类推。我们将代数限制为最多10,因为更大的数字会运行很长时间,甚至可能冻结您的树莓派!</p><ul><li>如果未提供命令行参数,程序应输出有用的用法消息并退出。</li><li>否则,它应将命令行参数转换为整数并存储在 <code>generations</code>变量中。</li><li>如果 <code>generations</code> 变量小于 1 或大于10,程序应输出有用的用法消息并退出。</li><li>否则,它(以及它生成的任何子进程)应执行以下操作(原始程序为第一代):<ul><li>打印一行,说明它属于哪一代及其自身的 PID;</li><li>增加当前代数计数器;</li><li>如果已达到最后一代(根据 <code>generations</code> 变量),则直接返回0;否则,生成两个子进程;</li><li>使用 <code>wait(0)</code> 等待任何成功生成的子进程完成;</li><li>如果两个子进程都成功生成,则返回0;如果任一生成(或两者都)失败,则返回 -1。</li></ul></li><li><strong>提示</strong>:仔细检查每次调用<code>fork()</code>(或您用于生成每个子进程的任何调用)返回的<code>pid_t</code> 值非常重要,因为 (1)这些调用可能失败(由负返回值指示),(2) <code>0</code>表示子进程正在运行,(3) 正数表示父进程正在运行。</li><li><strong>提示</strong>:基于此,使用递归,或使用带有几个<code>pid_t</code> 变量(每个子进程一个)的 <code>while</code>循环并明智地在子进程中运行的代码分支使用 <code>continue</code>语句,可以很直接地实现本练习的逻辑。</li><li>作为此练习的答案,请展示您的程序在运行 4代时(如果正确实现,应总共生成 15 个进程)的输出。</li></ul></li><li><p><strong>修改 kobject示例模块</strong>:现在我们将回到使用这些思想进行内核模块设计。首先,从您的linux 源码目录中,找到并将文件<code>samples/kobject/kobject-example.c</code>复制到您保存内核模块代码的目录中。这是一个使用称为 <code>kobjects</code>的功能的内核模块,它提供了一个在内核和用户空间之间交换数据的接口。每个数据项称为一个属性(attribute),对于每个属性,您需要提供一个<code>show</code> 和 <code>store</code>函数,分别在用户空间读取和写入这些值时被调用。</p><ul><li>该特定模块提供三个属性:<code>foo</code>、<code>baz</code> 和<code>bar</code>。加载后,您可以在 sysfs 文件系统中的<code>/sys/kernel/kobject_example/</code> 目录下找到它们。</li><li>修改此文件,以便(通过使用 <code>printk</code>)在更新<code>foo</code>、<code>baz</code> 或 <code>bar</code>中的任何一个时打印一条系统日志消息,并在消息中显示被更新变量的旧值和新值。</li><li>现在我们可以像以前一样构建您修改后的模块。首先,更新您的<code>Makefile</code>,使其包含新模块的相应 <code>.o</code>文件目标,然后为您的模块生成 <code>.ko</code> 文件,如下所示:<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">module add arm-rpi</span><br><span class="line">LINUX_SOURCE=您的Linux内核源代码路径</span><br><span class="line">make -C <span class="variable">$LINUX_SOURCE</span> ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- M=<span class="variable">$PWD</span> modules</span><br></pre></td></tr></table></figure></li><li>然后,使用 <code>sftp</code> 将生成的 <code>.ko</code>文件复制到您的树莓派上,并使用 <code>sudo insmod</code>加载该模块。</li><li>在具有 root 权限的终端中,您可以使用 <code>cat</code>命令读取这些属性的值,并使用 <code>echo</code>命令将值写入这些属性,例如: <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sudo bash</span><br><span class="line"><span class="built_in">cd</span> /sys/kernel/kobject_example/</span><br><span class="line"><span class="built_in">cat</span> foo <span class="comment"># 显示 foo 属性的当前(原始)值</span></span><br><span class="line"><span class="built_in">echo</span> 42 > foo <span class="comment"># 将值 42 写入属性 foo</span></span><br><span class="line"><span class="built_in">cat</span> foo <span class="comment"># 显示属性 foo 的新值</span></span><br></pre></td></tr></table></figure> <em>注意:您必须有一个 root终端(<code>sudo bash</code> 可以给您)才能写入这些命令(即<code>sudo echo</code> 不起作用)。</em></li><li>作为此练习的答案,请展示使用这些命令成功更改<code>foo</code>、<code>bar</code> 和 <code>baz</code>值的输出演示。</li></ul></li><li><p><strong>family_reader.c模块</strong>:现在我们将编写一个内核模块,通过 sysfs 接口读取一个PID,并在系统日志中打印该进程的祖先谱系。</p><ul><li>创建一个基于您修改后的 <code>kobject-example.c</code>文件的新内核模块文件<code>family_reader.c</code>(即,首先请复制它)。该模块应在<code>/sys/kernel/fam_reader/</code>下创建单个系统属性(像上一个练习一样是整数值)。当您向此属性写入一个整数时,您的模块应尝试打印出该PID的祖先谱系(即,它的父进程,然后是父进程的父进程,依此类推,一直追溯到<code>init</code> 任务)。涉及几个步骤:<ul><li><strong>旁注</strong>:现代 Linux内核为了便于在不同虚拟主机之间迁移进程,区分了“真实”PID和“虚拟”PID。虚拟 PID 是进程从用户空间看到的 PID。</li><li>您需要将整数输入转换为合适的内核 PID。使用函数<code>find_vpid()</code>(请参阅 <code>include/linux/pid.h</code> 和<code>kernel/pid.c</code>),它返回一个<code>struct pid *</code>。此函数可能失败,因此在解引用指针之前务必检查其返回值。</li><li>接下来,您可以通过将 <code>struct pid *</code> 和标志<code>PIDTYPE_PID</code> 传递给函数 <code>get_pid_task()</code>(请参阅<code>include/linux/pid.h</code> 和<code>kernel/pid.c</code>)来将其转换为<code>struct task_struct *</code>。此函数可能失败,因此在解引用指针之前务必检查其返回值。</li><li>一旦您有了<code>struct task_struct *</code>,就可以访问它存储的任何数据。特别是,<code>real_parent</code>字段存储了生成它的进程的 <code>struct task_struct *</code>指针,<code>comm</code> 字段是给出命令名称的字符串。<ul><li><strong>注意</strong>:有一个单独的字段叫做<code>parent</code>,这不是我们本练习想要的。<code>parent</code>是共享进程组信号并允许父子进程之间等待的逻辑父进程。</li></ul></li><li>回溯家族树,打印出每个任务的 PID 和命令名称,一直回溯到 PID 为 1 的<code>init</code> 任务。</li></ul></li><li>像上一个模块一样编译您的新内核模块,然后使用 <code>sftp</code>将生成的 <code>.ko</code>文件复制到您的树莓派上。在您的树莓派上,安装该模块,然后使用<code>sudo bash</code> 为您的终端会话提供 root访问权限。在该模块的目录下(将在 <code>/sys/kernel/</code> 下),使用<code>cat</code> 和 <code>echo</code> 读取和写入该模块属性的值,然后使用<code>dmesg</code> 确认系统日志显示您的模块工作正常。</li><li>使用 <code>ps</code> 命令查找在您当前终端窗口中运行的<code>sudo</code> 进程的 PID,使用 <code>echo</code>将您的模块属性设置为该 PID,然后使用 <code>dmesg</code>查看该进程的祖先谱系。作为此练习的答案,请展示显示 <code>sudo</code>进程祖先谱系的系统日志消息。</li></ul></li></ol><hr /><h3 id="可选拓展练习-optional-enrichment-exercises"><strong>可选拓展练习(Optional Enrichment Exercises)</strong></h3><ol start="6" type="1"><li><strong>探索 task_struct</strong>:<code>task_struct</code>包含许多有趣的进程数据和进程记账信息。尝试将其他字段打印到系统日志中,并作为此练习的答案,请简要描述您打印了哪些内容,并展示执行此操作后的一些系统日志消息。</li><li><strong>探索调度程序功能</strong>:<code>kernel/sched</code> 和<code>include/linux/sched</code>中的文件包含许多用于处理任务的功能,包括修改特定任务或迭代系统中每个任务的能力。例如,<code>include/linux/sched/signal.h</code>定义了诸如 <code>for_each_process()</code>之类的宏。尝试使用其中一些功能,并作为此练习的答案,请简要描述您做了什么以及观察到了什么。</li></ol><hr />]]></content>
<summary type="html"><h3 id="进程家族树-process-family-tree"><strong>进程家族树 (Process
Family Tree)</strong></h3>
<ol type="1">
<li><p><strong>实验报告</strong>:准备好实验报告</</summary>
<category term="os basic" scheme="https://mackz-maxw.github.io/categories/os-basic/"/>
<category term="lab" scheme="https://mackz-maxw.github.io/categories/os-basic/lab/"/>
</entry>
<entry>
<title>代码随想录 | 刷题-动态规划10</title>
<link href="https://mackz-maxw.github.io/2025/09/26/kamacode37subseq/"/>
<id>https://mackz-maxw.github.io/2025/09/26/kamacode37subseq/</id>
<published>2025-09-27T01:25:21.777Z</published>
<updated>2025-09-26T22:47:29.000Z</updated>
<content type="html"><![CDATA[<h3 id="最长递增子序列">300.最长递增子序列</h3><p>这题我一开始想使用回溯找最长子序列,但是我发现需要记录的状态太多,回溯没办法解决;同时对于序列中的相同数字,如何记住遍历前后的状态也是个问题。题解中这题使用的是动态规划,对于每一个索引求它所在的最长递增子序列,再求所有子序列中最长的那个。</p><ul><li>外层循环:遍历序列中每一个索引<code>i</code></li><li>内层循环:求从开头到<code>i</code>之前的数字<code>j</code>中最长的子序列,如果<code>nums[i] > nums[j]</code>,则开头到<code>i</code>所在处最长子序列为<code>j</code>所在处+1</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">lengthOfLIS</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="function">vector<<span class="type">int</span>> <span class="title">dp</span><span class="params">(nums.size(), <span class="number">1</span>)</span></span>;</span><br><span class="line"> <span class="type">int</span> mx = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i<nums.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">0</span>; j<i; j++){</span><br><span class="line"> <span class="keyword">if</span>(nums[i] > nums[j])dp[i] = <span class="built_in">max</span>(dp[i], dp[j] + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// cout<<dp[i]<<' ';</span></span><br><span class="line"> }</span><br><span class="line"> mx = <span class="built_in">max</span>(mx, dp[i]);</span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> mx;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="最长连续递增序列">674. 最长连续递增序列</h3><p>这题不要想得太复杂了就行 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">findLengthOfLCIS</span><span class="params">(vector<<span class="type">int</span>>& nums)</span> </span>{</span><br><span class="line"> <span class="type">int</span> lng = <span class="number">1</span>;</span><br><span class="line"> <span class="type">int</span> max_l = <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i < nums.<span class="built_in">size</span>(); i++){</span><br><span class="line"> <span class="keyword">if</span>(nums[i] > nums[i<span class="number">-1</span>]){</span><br><span class="line"> lng++;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> lng = <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> max_l = <span class="built_in">max</span>(max_l, lng);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> max_l;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p><h3 id="最长重复子数组">718. 最长重复子数组</h3><p>这题我想可不可以分别求两个数组的最长相等前后缀表,再找重复子数组。看了动规解法我发现,一个两数组比较表能解决的事情,用前后缀表似乎有点浪费?</p><p>动规解法中,<code>dp[i][j]</code>表示两个数组下标i-1,j-1个数进行比较,这样0行和0列默认初始化为0即可。如果表示下标i,j个数,则需要再初始化一下0行和0列<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">int</span> <span class="title">findLength</span><span class="params">(vector<<span class="type">int</span>>& nums1, vector<<span class="type">int</span>>& nums2)</span> </span>{</span><br><span class="line"> <span class="type">int</span> len = <span class="number">0</span>;</span><br><span class="line"> vector<vector<<span class="type">int</span>>> <span class="built_in">dp</span>(nums1.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="built_in">vector</span><<span class="type">int</span>>(nums2.<span class="built_in">size</span>()+<span class="number">1</span>, <span class="number">0</span>));</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i < (nums1.<span class="built_in">size</span>()+<span class="number">1</span>); i++){</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> j = <span class="number">1</span>; j < (nums2.<span class="built_in">size</span>()+<span class="number">1</span>); j++){</span><br><span class="line"> <span class="keyword">if</span>(nums1[i<span class="number">-1</span>] == nums2[j<span class="number">-1</span>]){</span><br><span class="line"> dp[i][j] = dp[i<span class="number">-1</span>][j<span class="number">-1</span>] + <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span>(dp[i][j] > len)len = dp[i][j];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// for(auto line:dp){</span></span><br><span class="line"> <span class="comment">// for(int l:line){</span></span><br><span class="line"> <span class="comment">// cout<<l<<' ';</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="comment">// cout<<endl;</span></span><br><span class="line"> <span class="comment">// }</span></span><br><span class="line"> <span class="keyword">return</span> len;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="最长递增子序列">300.最长递增子序列</h3>
<p>这题我一开始想使用回溯找最长子序列,但是我发现需要记录的状态太多,回溯没办法解决;同时对于序列中的相同数字,如何记住遍历前后的状态也是个问题。
题解中这题使用的是动态规划,对于每一个索引求它所在的最长递</summary>
<category term="leetcode" scheme="https://mackz-maxw.github.io/categories/leetcode/"/>
</entry>
<entry>
<title>算法笔试 | 某厂测开复盘</title>
<link href="https://mackz-maxw.github.io/2025/09/26/intv_tyy/"/>
<id>https://mackz-maxw.github.io/2025/09/26/intv_tyy/</id>
<published>2025-09-27T01:24:28.393Z</published>
<updated>2025-09-27T01:24:16.064Z</updated>
<content type="html"><![CDATA[<p>今天笔试某厂测开岗,选择还行,算法全寄,遂复盘之</p><h3 id="操作系统八股-内存的分页和分段">操作系统八股内存的分页和分段</h3><h4 id="分页">分页</h4><p>分页是一种内存管理方案,它<strong>将程序的虚拟地址空间和物理内存都划分成固定大小的块</strong>。虚拟内存的块称为“页”,物理内存的块称为“页框”或“帧”。页和页框的大小通常相同(例如4KB)。</p><p>虚拟地址 = 页号 + 页内偏移量 物理地址 = 页框号 + 页内偏移量</p><h4 id="分段">分段</h4><p>分段是一种更符合程序员视角的内存管理方案。<strong>一个程序被划分为若干个逻辑段</strong>,例如:代码段、数据段、堆段、栈段等。每个段都有其特定的用途(如代码段可读可执行但不可写)。</p><p>操作系统为每个进程维护一张<strong>段表</strong>,该表记录了每个段的<strong>基地址</strong>(在物理内存中的起始地址)和<strong>段长</strong>(段的界限)。</p><table><colgroup><col style="width: 33%" /><col style="width: 33%" /><col style="width: 33%" /></colgroup><thead><tr><th style="text-align: left;">特性</th><th style="text-align: left;"><strong>分页(一维)</strong></th><th style="text-align: left;"><strong>分段(二维)</strong></th></tr></thead><tbody><tr><td style="text-align: left;"><strong>地址空间视图</strong></td><td style="text-align: left;"><strong>一个</strong>连续的线性空间</td><tdstyle="text-align: left;"><strong>多个</strong>独立的逻辑空间(段)</td></tr><tr><td style="text-align: left;"><strong>“号”的含义</strong></td><tdstyle="text-align: left;">系统管理的<strong>索引号</strong>(第几页)</td><tdstyle="text-align: left;">程序定义的<strong>逻辑标识</strong>(哪个段)</td></tr><tr><td style="text-align: left;"><strong>对程序员可见</strong></td><tdstyle="text-align: left;"><strong>透明</strong>(由硬件和OS管理)</td><tdstyle="text-align: left;"><strong>可见</strong>(通常由编译器管理,但理念上符合程序员思维)</td></tr><tr><td style="text-align: left;"><strong>类比</strong></td><tdstyle="text-align: left;"><strong>一本书</strong>:只需要一个页码和行号</td><tdstyle="text-align: left;"><strong>文件柜</strong>:需要先选抽屉,再选文件</td></tr></tbody></table><h3 id="操作系统八股-银行家算法bankers-algorithm">操作系统八股银行家算法(Banker's Algorithm)。</h3><p>银行家算法用于检查系统是否处于安全状态,即是否存在一个安全序列,每个进程按顺序执行,执行完后释放所有资源,使得所有进程都能完成而不导致死锁。</p><h3 id="算法题-分零食">算法题 分零食</h3><p>题目:小明和弟弟分零食,分零食的次数为c,每次会给到零食的数量n和最便宜的零食价格m,在价目表中,每个零食的价格是前一个零食的m倍(第一个零食价格为m),小明想给弟弟总和比自己贵的零食,但为了弟弟的自尊心他想自己和弟弟的零食价格差尽量小,每次请输出小明的零食数和小明每个零食的单价</p><h4 id="为什么贪心算法是最优的">为什么贪心算法是最优的?</h4><ol type="1"><li><strong>几何序列的超级递增性质</strong>:当 (m > 1)时,零食价格序列是超级递增的,即每个后续零食的价格都大于之前所有零食价格之和。例如,对于(m=2),序列为 (2, 4, 8, 16, ),其中 (4 > 2), (8 > 2+4), (16 >2+4+8),等等。这种性质意味着贪心算法从大到小选择零食可以确保得到最接近总价一半的子集和,从而最小化价格差。</li><li><strong>对于 (m = 1) 的情况</strong>:所有零食价格均为1。小明最多能拿的零食数为 (k = (n-1)/2 ),这样弟弟拿 (n-k)个零食,总价差为 1(最小可能值)。贪心算法在这里本质上是直接计算最大 (k)使得 (k < n/2)。</li></ol><p>因此,贪心算法在两种情况下都能保证找到最优解,使价格差最小。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><vector></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><algorithm></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">long</span> <span class="type">long</span> ll;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> c;</span><br><span class="line"> cin >> c;</span><br><span class="line"> <span class="keyword">while</span> (c--) {</span><br><span class="line"> <span class="type">int</span> n, m;</span><br><span class="line"> cin >> n >> m;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (m == <span class="number">1</span>) {</span><br><span class="line"> <span class="comment">// 所有零食价格均为1:小明拿k个,使得k < n/2,且k尽可能大</span></span><br><span class="line"> <span class="type">int</span> k = (n - <span class="number">1</span>) / <span class="number">2</span>;</span><br><span class="line"> cout << k << endl;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < k; i++) {</span><br><span class="line"> cout << <span class="number">1</span> << <span class="string">" "</span>;</span><br><span class="line"> }</span><br><span class="line"> cout << endl;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 计算零食价格序列和总价S</span></span><br><span class="line"> vector<ll> <span class="built_in">prices</span>(n);</span><br><span class="line"> ll total_sum = <span class="number">0</span>;</span><br><span class="line"> ll current = m;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < n; i++) {</span><br><span class="line"> prices[i] = current;</span><br><span class="line"> total_sum += current;</span><br><span class="line"> current *= m;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ll小明_sum = <span class="number">0</span>;</span><br><span class="line"> vector<ll> xiaoming_prices;</span><br><span class="line"> <span class="comment">// 从最贵的零食(序列末尾)开始贪心选择</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = n - <span class="number">1</span>; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> <span class="keyword">if</span> (<span class="number">2</span> * (小明_sum + prices[i]) <= total_sum) {</span><br><span class="line"> 小明_sum += prices[i];</span><br><span class="line"> xiaoming_prices.<span class="built_in">push_back</span>(prices[i]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 输出小明的零食数量和每个零食的单价(价格从大到小)</span></span><br><span class="line"> cout << xiaoming_prices.<span class="built_in">size</span>() << endl;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = xiaoming_prices.<span class="built_in">size</span>() - <span class="number">1</span>; i >= <span class="number">0</span>; i--) {</span><br><span class="line"> cout << xiaoming_prices[i] << <span class="string">" "</span>;</span><br><span class="line"> }</span><br><span class="line"> cout << endl;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><p>今天笔试某厂测开岗,选择还行,算法全寄,遂复盘之</p>
<h3 id="操作系统八股-内存的分页和分段">操作系统八股
内存的分页和分段</h3>
<h4 id="分页">分页</h4>
<p>分页是一种内存管理方案,它<strong>将程序的虚拟地址空间和物理内</summary>
<category term="interview" scheme="https://mackz-maxw.github.io/categories/interview/"/>
</entry>
<entry>
<title>操作系统基础 | 5.7 孤儿进程;错误检查</title>
<link href="https://mackz-maxw.github.io/2025/09/14/oper_sys27orphan_err/"/>
<id>https://mackz-maxw.github.io/2025/09/14/oper_sys27orphan_err/</id>
<published>2025-09-14T15:32:32.574Z</published>
<updated>2025-09-14T15:32:41.292Z</updated>
<content type="html"><![CDATA[<h3 id="孤儿进程与僵尸进程-orphans-and-zombies"><strong>26.2孤儿进程与僵尸进程 (Orphans and Zombies)</strong></h3><p>父进程和子进程的生命周期通常并不相同——要么父进程比子进程存活时间长,要么相反。这就引出了两个问题:</p><p><strong>1. 孤儿进程由谁接管?</strong>当一个进程的父进程终止后,该进程就变成了“孤儿进程”。这个孤儿进程会被<code>init</code> 进程(所有进程的祖先,其进程 ID 为1)收养。换句话说,在子进程的父进程终止后,调用 <code>getppid()</code>将返回值1。这可以作为一种判断子进程的真正父进程是否仍存活的方法(前提是该子进程并非由<code>init</code> 进程创建)。 使用 Linux 特有的 <code>prctl()</code>系统调用的 <code>PR_SET_PDEATHSIG</code>操作,可以设置一个进程在成为孤儿时接收到一个特定的信号。</p><p><strong>2. 父进程还未执行 wait()子进程就已终止,会发生什么?</strong>关键在于,尽管子进程已经完成了它的工作,但仍应允许父进程在之后的某个时间点执行<code>wait()</code>来获取子进程的终止状态。内核通过将子进程转变为“僵尸进程”(Zombie)来处理这种情况。这意味着子进程持有的大部分资源会被释放回系统,供其他进程重用。进程唯一保留的部分是内核进程表中的一个条目,该条目记录了子进程的进程ID、终止状态以及资源使用统计信息(参见 36.1 节)等信息。</p><p>关于僵尸进程,UNIX系统模仿了电影中的设定——僵尸进程无法被信号杀死,即使是(银弹)<code>SIGKILL</code>信号也不行。这确保了父进程最终总是能够执行 <code>wait()</code>。</p><p>当父进程执行了 <code>wait()</code>后,内核会清除该僵尸进程,因为关于该子进程的最后剩余信息不再需要。另一方面,如果父进程未执行<code>wait()</code> 就终止了,那么 <code>init</code>进程会收养该子进程,并自动执行<code>wait()</code>,从而将僵尸进程从系统中移除。</p><p>如果一个父进程创建了子进程,但未能执行<code>wait()</code>,那么内核进程表中将无限期地保留该僵尸子进程的条目。如果创建了大量这样的僵尸子进程,它们最终会填满内核进程表,从而阻止新进程的创建。由于僵尸进程无法用信号杀死,将它们从系统中移除的唯一方法是杀死它们的父进程(或等待其退出),届时<code>init</code> 进程会收养这些僵尸进程并对它们执行<code>wait()</code>,从而将它们从系统中清除。</p><p>这些语义对于需要创建大量子进程的长生命周期父进程(如网络服务器和Shell)的设计具有重要意义。换句话说,在此类应用程序中,父进程应执行<code>wait()</code>调用,以确保已终止的子进程总是能从系统中被移除,而不是变成长期存在的僵尸进程。如26.3.1 节所述,父进程可以同步执行这些 <code>wait()</code>调用,也可以异步地(例如响应 <code>SIGCHLD</code> 信号的递送)执行。</p><p>以下程序演示了僵尸进程的创建以及僵尸进程无法被 <code>SIGKILL</code>杀死的情况。 <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><signal.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><libgen.h></span> <span class="comment">/* For basename() declaration */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tlpi_hdr.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> CMD_SIZE 200</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span>{</span><br><span class="line"> <span class="type">char</span> cmd[CMD_SIZE];</span><br><span class="line"> <span class="type">pid_t</span> childPid;</span><br><span class="line"></span><br><span class="line"> setbuf(<span class="built_in">stdout</span>, <span class="literal">NULL</span>); <span class="comment">/* Disable buffering of stdout */</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Parent PID=%ld\n"</span>, (<span class="type">long</span>) getpid());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span> (childPid = fork()) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">-1</span>:</span><br><span class="line"> errExit(<span class="string">"fork"</span>);</span><br><span class="line"> <span class="keyword">case</span> <span class="number">0</span>: <span class="comment">/* Child: immediately exits to become zombie */</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Child (PID=%ld) exiting\n"</span>, (<span class="type">long</span>) getpid());</span><br><span class="line"> _exit(EXIT_SUCCESS);</span><br><span class="line"> <span class="keyword">default</span>: <span class="comment">/* Parent */</span></span><br><span class="line"> sleep(<span class="number">3</span>); <span class="comment">/* Give child a chance to start and exit */</span></span><br><span class="line"> <span class="built_in">snprintf</span>(cmd, CMD_SIZE, <span class="string">"ps | grep %s"</span>, basename(argv[<span class="number">0</span>]));</span><br><span class="line"> cmd[CMD_SIZE - <span class="number">1</span>] = <span class="string">'\0'</span>; <span class="comment">/* Ensure string is null-terminated */</span></span><br><span class="line"> system(cmd); <span class="comment">/* View zombie child */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Now send the "sure kill" signal to the zombie */</span></span><br><span class="line"> <span class="keyword">if</span> (kill(childPid, SIGKILL) == <span class="number">-1</span>)</span><br><span class="line"> errMsg(<span class="string">"kill"</span>);</span><br><span class="line"> sleep(<span class="number">3</span>); <span class="comment">/* Give child a chance to react to signal */</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"After sending SIGKILL to zombie (PID=%ld):\n"</span>, (<span class="type">long</span>) childPid);</span><br><span class="line"> system(cmd); <span class="comment">/* View zombie child again */</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> 当我们运行此程序时,会看到以下输出:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ ./make_zombie</span><br><span class="line">Parent PID=1013</span><br><span class="line">Child (PID=1014) exiting</span><br><span class="line"> 1013 pts/4 00:00:00 make_zombie</span><br><span class="line"> 1014 pts/4 00:00:00 make_zombie <defunct> (ps命令的输出)</span><br><span class="line">After sending SIGKILL to zombie (PID=1014):</span><br><span class="line"> 1013 pts/4 00:00:00 make_zombie</span><br><span class="line"> 1014 pts/4 00:00:00 make_zombie <defunct> (ps命令的输出)</span><br></pre></td></tr></table></figure> 在上面的输出中,我们看到 <code>ps(1)</code> 命令显示字符串<code><defunct></code> 来表示一个处于僵尸状态的进程。 示例程序使用<code>system()</code> 函数来执行其字符串参数中给出的 Shell命令。我们将在 27.6 节详细描述 <code>system()</code>。</p><h3 id="linux-进程源代码指引"><strong>Linux 进程源代码指引</strong></h3><p>进程和线程是操作系统中的基本抽象概念。为支持进程和线程,操作系统使用了两个关键数据结构:<code>task_struct</code>和<code>thread_info</code>;以及大量用于管理系统中进程和线程的辅助函数。</p><ul><li><strong><code>arch/arm/include/asm/thread_info.h</code></strong>文件声明了 <code>thread_info</code> 结构体。</li><li><strong><code>include/linux/sched.h</code></strong> 文件声明了<code>task_struct</code> 结构体以及许多进程管理函数。</li><li><strong><code>arch/arm/include/asm/switch_to.h</code></strong>文件为 <code>switch_to</code> 进程切换例程定义了一个特定于 ARM架构的宏,而 <strong><code>arch/arm/kernel/entry-armv.S</code></strong>文件实现了该宏所使用的汇编级进程切换例程 <code>__switch_to</code>。</li></ul><h3 id="linux-内核代码错误检查"><strong>Linux内核代码错误检查</strong></h3><p>在 Linux 用户空间编程中,函数通常返回整数,并约定返回值<code>0</code>表示函数调用成功,而不同的(通常为负的)非零值用于指示不同的错误。然而,在内核编程中,许多函数可能返回指针而非整数,这使问题变得复杂,因为指针可能使用非零值来编码作为函数成功调用结果而返回的有效内存地址。</p><p>Linux 通过利用以下事实来解决这一挑战: 1.有效(虚拟)地址范围的上半部分未被使用。 2.使用负值表示错误会使得其高位比特位非零,无论它们是作为指针还是整数返回。3. 在 <strong><code>include/linux/err.h</code></strong>文件中提供有用的宏和内联函数,这些函数可以以跨不同硬件架构的可移植方式处理指针、整数和布尔类型的不同组合:</p><ul><li><strong><code>IS_ERR_VALUE</code>宏</strong>:检查一个(指针或整数)值的高位范围是否非空(即,包含错误值)。</li><li><strong><code>ERR_PTR</code>函数</strong>:将一个(<code>long</code>类型)整数值转换为(<code>void *</code> 类型)指针值。</li><li><strong><code>PTR_ERR</code>函数</strong>:将一个(<code>const void *</code>类型)指针值转换为(<code>long</code> 类型)整数值。</li><li><strong><code>IS_ERR</code>函数</strong>:将一个(<code>const void *</code>类型)指针值转换为(<code>unsigned long</code> 类型)整数值,使用<code>IS_ERR_VALUE</code>宏检查该值的高位范围是否非空(即,包含错误值),并相应地返回一个<code>bool</code> 值。</li><li><strong><code>IS_ERR_OR_NULL</code> 函数</strong>:返回一个<code>bool</code> 值,如果 (1) 传入的(<code>const void *</code>类型)指针值为 <code>0</code> <strong>或</strong> (2)将其转换为(<code>unsigned long</code> 类型)整数值后使用<code>IS_ERR_VALUE</code>宏检查表明该值的高位范围非空(即,包含错误值),则该函数返回<code>true</code>。</li><li><strong><code>ERR_CAST</code>函数</strong>:将(<code>const void *</code>类型)指针转换为(<code>void *</code> 类型)指针(去除<code>const</code> 属性)。</li><li><strong><code>PTR_ERR_OR_ZERO</code> 函数</strong>:使用<code>IS_ERR</code> 函数检查传入的(<code>const void *</code>类型)指针值是否包含错误,并返回一个(<code>int</code>类型)整数值:如果不包含错误则返回<code>0</code>,如果包含错误则返回通过调用 <code>PTR_ERR</code>函数获取的错误值。</li></ul><h3 id="err_ptr-和-ptr_err-宏"><strong><code>ERR_PTR</code> 和<code>PTR_ERR</code> 宏</strong></h3><p>关于返回值的讨论,你现在明白了内核模块的 <code>init</code>例程必须返回一个整数。但如果你希望返回一个指针呢?<strong><code>ERR_PTR()</code></strong>内联函数为我们提供了解决方案,它允许我们通过将指针类型转换为<code>void *</code>来返回一个伪装成整数的指针。实际上还有更好的方法:你可以使用<strong><code>IS_ERR()</code></strong>内联函数检查错误(该函数实质上判断值是否在 <code>[-1, -4095]</code>范围内),通过 <strong><code>ERR_PTR()</code></strong>内联函数将负的错误值编码到指针中,并使用对应的例程<strong><code>PTR_ERR()</code></strong> 从指针中检索出这个错误值。</p><p>一个简单的例子,参见下面给出的被调用方代码。这次,我们让(示例)函数<code>myfunc()</code> 返回一个(指向名为 <code>mystruct</code>的结构体的)指针,而不是整数:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> mystruct * <span class="title function_">myfunc</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">{</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">mystruct</span> *<span class="title">mys</span> =</span> <span class="literal">NULL</span>;</span><br><span class="line"> mys = kzalloc(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> mystruct), GFP_KERNEL);</span><br><span class="line"> <span class="keyword">if</span> (!mys)</span><br><span class="line"> <span class="keyword">return</span> ERR_PTR(-ENOMEM); <span class="comment">/* 示例:返回编码为指针的错误码 */</span></span><br><span class="line"> <span class="comment">/* ... 其他操作 ... */</span></span><br><span class="line"> <span class="keyword">return</span> mys; <span class="comment">/* 返回有效的指针 */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="如何使用-is_err-和-ptr_err它们是什么意思"><strong>如何使用IS_ERR 和 PTR_ERR?它们是什么意思?</strong></h3><p>根据内核定义,有三个宏: * <strong><code>IS_ERR</code></strong> -用于检查。如果 <code>ptr</code> 是一个错误指针则返回非 <code>0</code>值。否则,如果不是错误则返回 <code>0</code>。 *<strong><code>PTR_ERR</code></strong> -用于打印。获取指针中当前(编码的错误)值。 *<strong><code>IS_ERR_VALUE</code></strong> - 在此有更详细的解释(here1)。</p><p>我发现这些宏对于内核空间编程非常有用。用法如下 - 如果<code>ptr</code> 是你要检查的指针,则按如下方式使用: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (IS_ERR(ptr))</span><br><span class="line"> printk(<span class="string">"Error here: %ld"</span>, PTR_ERR(ptr)); <span class="comment">/* 打印出错误码 */</span></span><br></pre></td></tr></table></figure>它们在内核中的代码定义如下: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)</span></span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">void</span> * __must_check <span class="title function_">ERR_PTR</span><span class="params">(<span class="type">long</span> error)</span>{</span><br><span class="line"> <span class="keyword">return</span> (<span class="type">void</span> *) error;</span><br><span class="line">}</span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">long</span> __must_check <span class="title function_">PTR_ERR</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *ptr)</span>{</span><br><span class="line"> <span class="keyword">return</span> (<span class="type">long</span>) ptr;</span><br><span class="line">}</span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">long</span> __must_check <span class="title function_">IS_ERR</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *ptr)</span>{</span><br><span class="line"> <span class="keyword">return</span> IS_ERR_VALUE((<span class="type">unsigned</span> <span class="type">long</span>)ptr);</span><br><span class="line">}</span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">long</span> __must_check <span class="title function_">IS_ERR_OR_NULL</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *ptr)</span>{</span><br><span class="line"> <span class="keyword">return</span> !ptr || IS_ERR_VALUE((<span class="type">unsigned</span> <span class="type">long</span>)ptr);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><h3 id="孤儿进程与僵尸进程-orphans-and-zombies"><strong>26.2
孤儿进程与僵尸进程 (Orphans and Zombies)</strong></h3>
<p>父进程和子进程的生命周期通常并不相同——要么父进程比子进程存活时间长,要么</summary>
<category term="os basic" scheme="https://mackz-maxw.github.io/categories/os-basic/"/>
</entry>
<entry>
<title>操作系统基础 | 5.6 等待子进程</title>
<link href="https://mackz-maxw.github.io/2025/09/09/oper_sys26wait/"/>
<id>https://mackz-maxw.github.io/2025/09/09/oper_sys26wait/</id>
<published>2025-09-09T17:03:53.123Z</published>
<updated>2025-09-09T17:04:04.283Z</updated>
<content type="html"><![CDATA[<h3 id="等待子进程-waiting-on-a-child-process"><strong>等待子进程(Waiting on a Child Process)</strong></h3><p>在许多由父进程创建子进程的应用程序中,父进程能够监视子进程以了解它们于何时以及如何终止是非常有用的。<code>wait()</code>系统调用及一系列相关的系统调用提供了这个功能。</p><h3 id="wait-系统调用-the-wait-system-call"><strong><code>wait()</code>系统调用 (The wait() System Call)</strong></h3><p><code>wait()</code> 系统调用等待调用进程的任一子进程终止,并在<code>status</code> 参数所指向的缓冲区中返回该子进程的终止状态。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/wait.h></span></span></span><br><span class="line"><span class="type">pid_t</span> <span class="title function_">wait</span><span class="params">(<span class="type">int</span> *status)</span>;</span><br></pre></td></tr></table></figure><p><strong>返回值:</strong> 成功则返回终止子进程的进程ID(PID),出错则返回 -1。</p><p><code>wait()</code> 系统调用执行以下操作:</p><ol type="1"><li>如果调用进程的(先前未被等待的)子进程中尚无一个终止,则该调用会<strong>阻塞</strong>,直到某个子进程终止为止。如果在调用时已有子进程终止,<code>wait()</code>则立即返回。</li><li>如果 <code>status</code> 参数不是<code>NULL</code>,则关于子进程如何终止的信息会通过 <code>status</code>指针所指向的整数返回。我们将在第 26.1.3 节描述 <code>status</code>返回的信息。</li><li>内核会将此子进程的 CPU 时间(第 10.7 节)和资源使用统计信息(第 36.1节)添加到其父进程所有子进程的运行总计时长中。</li><li>作为其函数结果,<code>wait()</code> 返回已终止子进程的进程 ID。</li></ol><p>出错时,<code>wait()</code> 返回-1。一个可能的错误是调用进程没有(先前未被等待的)子进程,这由<code>errno</code> 值 <code>ECHILD</code>指示。这意味着我们可以使用以下循环来等待调用进程的所有子进程终止:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> ((childPid = wait(<span class="literal">NULL</span>)) != <span class="number">-1</span>)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">if</span> (errno != ECHILD) <span class="comment">/* 发生意外错误... */</span></span><br><span class="line"> errExit(<span class="string">"wait"</span>); </span><br></pre></td></tr></table></figure><p>以下代码演示了 <code>wait()</code>的用法。该程序创建多个子进程,每个命令行整数参数对应一个子进程。每个子进程休眠其对应命令行参数所指定的秒数,然后退出。与此同时,在创建完所有子进程后,父进程反复调用<code>wait()</code> 来监视其子进程的终止。此循环持续直到<code>wait()</code> 返回-1。(这不是唯一的方法:我们也可以选择当终止的子进程数量<code>numDead</code> 匹配创建的子进程数量时退出循环。)</p><p><strong>创建并等待多个子进程</strong><code>––––––––––––––––––––––––––––––––––––––––––––––––––––– procexec/multi_wait.c</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/wait.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><time.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"curr_time.h"</span> <span class="comment">/* Declaration of currTime() */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tlpi_hdr.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span>{</span><br><span class="line"> <span class="type">int</span> numDead; <span class="comment">/* 目前已等待的子进程数量 */</span></span><br><span class="line"> <span class="type">pid_t</span> childPid; <span class="comment">/* 被等待的子进程的PID */</span></span><br><span class="line"> <span class="type">int</span> j;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (argc < <span class="number">2</span> || <span class="built_in">strcmp</span>(argv[<span class="number">1</span>], <span class="string">"--help"</span>) == <span class="number">0</span>)</span><br><span class="line"> usageErr(<span class="string">"%s sleep-time...\n"</span>, argv[<span class="number">0</span>]);</span><br><span class="line"> setbuf(<span class="built_in">stdout</span>, <span class="literal">NULL</span>); <span class="comment">/* 禁用 stdout 的缓冲 */</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (j = <span class="number">1</span>; j < argc; j++) { <span class="comment">/* 为每个参数创建一个子进程 */</span></span><br><span class="line"> <span class="keyword">switch</span> (fork()) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">-1</span>:</span><br><span class="line"> errExit(<span class="string">"fork"</span>);</span><br><span class="line"> <span class="keyword">case</span> <span class="number">0</span>: <span class="comment">/* 子进程:休眠一段时间后退出 */</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[%s] child %d started with PID %ld, sleeping %s "</span></span><br><span class="line"> <span class="string">"seconds\n"</span>, currTime(<span class="string">"%T"</span>), j, (<span class="type">long</span>) getpid(), argv[j]);</span><br><span class="line"> sleep(getInt(argv[j], GN_NONNEG, <span class="string">"sleep-time"</span>));</span><br><span class="line"> _exit(EXIT_SUCCESS);</span><br><span class="line"> <span class="keyword">default</span>: <span class="comment">/* 父进程:继续循环 */</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> numDead = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (;;) { <span class="comment">/* 父进程等待每个子进程退出 */</span></span><br><span class="line"> childPid = wait(<span class="literal">NULL</span>);</span><br><span class="line"> <span class="keyword">if</span> (childPid == <span class="number">-1</span>) {</span><br><span class="line"> <span class="keyword">if</span> (errno == ECHILD) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"No more children - bye!\n"</span>);</span><br><span class="line"> <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">/* 发生其他(意外)错误 */</span></span><br><span class="line"> errExit(<span class="string">"wait"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> numDead++;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[%s] wait() returned child PID %ld (numDead=%d)\n"</span>,</span><br><span class="line"> currTime(<span class="string">"%T"</span>), (<span class="type">long</span>) childPid, numDead);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面的 shell 会话日志展示了我们使用该程序创建三个子进程时发生的情况:<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">$ ./multi_wait 7 1 4</span><br><span class="line">[13:41:00] child 1 started with PID 21835, sleeping 7 seconds</span><br><span class="line">[13:41:00] child 2 started with PID 21836, sleeping 1 seconds</span><br><span class="line">[13:41:00] child 3 started with PID 21837, sleeping 4 seconds</span><br><span class="line">[13:41:01] wait() returned child PID 21836 (numDead=1)</span><br><span class="line">[13:41:04] wait() returned child PID 21837 (numDead=2)</span><br><span class="line">[13:41:07] wait() returned child PID 21835 (numDead=3)</span><br><span class="line">No more children - bye!</span><br></pre></td></tr></table></figure> 如果在某个特定时刻有多个子进程已终止,SUSv3 (Single UNIXSpecification, version 3) 未明确规定一系列 <code>wait()</code>调用回收这些子进程的顺序;也就是说,顺序依赖于实现。即使在不同的 Linux内核版本之间,该行为也有所不同。</p><h3id="waitpid-系统调用-the-waitpid-system-call"><strong><code>waitpid()</code>系统调用 (The waitpid() System Call)</strong></h3><p><code>wait()</code> 系统调用有一些局限性,<code>waitpid()</code>的设计正是为了应对这些局限性:</p><ul><li>如果一个父进程创建了多个子进程,使用 <code>wait()</code>无法等待<strong>特定某个子进程</strong>的完成;我们只能等待<strong>下一个终止</strong>的子进程。</li><li>如果尚无子进程终止,<code>wait()</code><strong>总是会阻塞</strong>。有时,更可取的是执行<strong>非阻塞的等待</strong>,这样如果尚无子进程终止,我们可以立即获得相应的指示。</li><li>使用<code>wait()</code>,我们只能获知那些<strong>已经终止</strong>的子进程的信息。无法在一个子进程被信号(如<code>SIGSTOP</code> 或<code>SIGTTIN</code>)<strong>停止</strong>时,或在一个被停止的子进程因收到<code>SIGCONT</code> 信号而<strong>恢复</strong>执行时得到通知。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/wait.h></span></span></span><br><span class="line"><span class="type">pid_t</span> <span class="title function_">waitpid</span><span class="params">(<span class="type">pid_t</span> pid, <span class="type">int</span> *status, <span class="type">int</span> options)</span>;</span><br></pre></td></tr></table></figure><p><strong>返回值:</strong> 成功则返回状态发生变化的子进程的进程ID(PID);如果指定了 <code>WNOHANG</code> 且未有子进程状态变化则返回0;出错则返回 -1。</p><p><code>waitpid()</code> 的返回值和 <code>status</code> 参数与<code>wait()</code> 相同。(关于通过 <code>status</code>返回值的解释,请参见第 26.1.3 节)。<code>pid</code>参数用于选择要等待的子进程,具体如下:</p><ul><li>如果 <code>pid</code> <strong>大于 0</strong>,则等待进程 ID 等于<code>pid</code> 的那个子进程。</li><li>如果 <code>pid</code> <strong>等于0</strong>,则等待与调用者(父进程)<strong>属于同一进程组</strong>的任何子进程。我们将在第34.2 节描述进程组。</li><li>如果 <code>pid</code> <strong>小于-1</strong>,则等待其<strong>进程组标识符</strong>等于 <code>pid</code>绝对值 (<code>abs(pid)</code>) 的任何子进程。</li><li>如果 <code>pid</code> <strong>等于-1</strong>,则等待<strong>任何</strong>子进程。调用<code>wait(&status)</code> 等价于调用<code>waitpid(-1, &status, 0)</code>。</li></ul><p><code>options</code> 参数是一个位掩码,可以包含(通过 OR操作)以下零个或多个标志(所有这些标志都在 SUSv3 中指定):</p><ul><li><strong><code>WUNTRACED</code></strong>除了返回关于已终止子进程的信息外,还会在子进程因收到信号而<strong>停止</strong>时返回其信息。</li><li><strong><code>WCONTINUED</code></strong> (自 Linux 2.6.10 起)还会在因收到 <code>SIGCONT</code>信号而<strong>恢复</strong>执行的、之前被停止的子进程的状态信息。</li><li><strong><code>WNOHANG</code></strong> 如果由 <code>pid</code>指定的子进程尚未改变状态,则立即返回而非阻塞(即执行一次“<strong>轮询</strong>”)。在这种情况下,<code>waitpid()</code>的返回值为 <code>0</code>。如果调用进程没有符合 <code>pid</code>指定条件的子进程,则 <code>waitpid()</code> 失败并返回错误<code>ECHILD</code>。</li></ul><p>我们将在清单 26-3 中演示 <code>waitpid()</code> 的用法。</p><hr /><p><strong>附加说明 (关于 WUNTRACED 名称的由来):</strong></p><p>在其关于 <code>waitpid()</code> 的原理说明中,SUSv3 指出名称<code>WUNTRACED</code> 是该标志源自 BSD 的一个历史产物,在 BSD中,一个进程可以通过两种方式之一被停止:一种是由于被<code>ptrace()</code>系统调用<strong>跟踪</strong>的结果,另一种是被信号<strong>停止</strong>(即未被跟踪)。当一个子进程被<code>ptrace()</code> 跟踪时,<strong>任何信号</strong>(除了<code>SIGKILL</code>)的送达都会导致该子进程被停止,并随之向父进程发送一个<code>SIGCHLD</code>信号。即使子进程<strong>忽略</strong>该信号,此行为也会发生。然而,如果子进程<strong>阻塞</strong>了该信号,则它不会被停止(除非该信号是<code>SIGSTOP</code>,它无法被阻塞)。</p>]]></content>
<summary type="html"><h3 id="等待子进程-waiting-on-a-child-process"><strong>等待子进程
(Waiting on a Child Process)</strong></h3>
<p>在许多由父进程创建子进程的应用程序中,父进程能够监视子进程以了解它们于何</summary>
<category term="os basic" scheme="https://mackz-maxw.github.io/categories/os-basic/"/>
</entry>
<entry>
<title>算法笔试 | acm模式输入输出指南</title>
<link href="https://mackz-maxw.github.io/2025/09/05/intv_io/"/>
<id>https://mackz-maxw.github.io/2025/09/05/intv_io/</id>
<published>2025-09-05T15:18:58.070Z</published>
<updated>2025-09-04T19:50:44.000Z</updated>
<content type="html"><![CDATA[<p>使用c++作为笔试语言 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><vector></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><string></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sstream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><algorithm></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><climits></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><cmath></span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// ====== 基础输入输出 ======</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 读取多个整数</span></span><br><span class="line"> <span class="type">int</span> a, b, c;</span><br><span class="line"> cout << <span class="string">"输入三个整数(用空格分隔): "</span>;</span><br><span class="line"> cin >> a >> b >> c;</span><br><span class="line"> cout << <span class="string">"三个整数分别是: "</span> << a << <span class="string">", "</span> << b << <span class="string">", "</span> << c << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 清除输入缓冲区</span></span><br><span class="line"> cin.<span class="built_in">ignore</span>(INT_MAX, <span class="string">'\n'</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ====== 读取整行输入 ======</span></span><br><span class="line"> cout << <span class="string">"\n整行输入示例:"</span> << endl;</span><br><span class="line"> string line;</span><br><span class="line"> cout << <span class="string">"输入一行文本: "</span>;</span><br><span class="line"> <span class="built_in">getline</span>(cin, line);</span><br><span class="line"> cout << <span class="string">"你输入的文本是: "</span> << line << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ====== 处理大数字 ======</span></span><br><span class="line"> cout << <span class="string">"\n大数字处理示例:"</span> << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 32位大整数 (使用long)</span></span><br><span class="line"> <span class="type">long</span> big32;</span><br><span class="line"> cout << <span class="string">"输入一个32位大整数: "</span>;</span><br><span class="line"> cin >> big32;</span><br><span class="line"> cout << <span class="string">"32位大整数: "</span> << big32 << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 64位大整数 (使用long long)</span></span><br><span class="line"> <span class="type">long</span> <span class="type">long</span> big64;</span><br><span class="line"> cout << <span class="string">"输入一个64位大整数: "</span>;</span><br><span class="line"> cin >> big64;</span><br><span class="line"> cout << <span class="string">"64位大整数: "</span> << big64 << endl;</span><br><span class="line"> </span><br><span class="line"> cin.<span class="built_in">ignore</span>(INT_MAX, <span class="string">'\n'</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ====== 字符串流处理 ======</span></span><br><span class="line"> cout << <span class="string">"\n字符串流处理示例:"</span> << endl;</span><br><span class="line"> cout << <span class="string">"输入多个用空格分隔的整数: "</span>;</span><br><span class="line"> <span class="built_in">getline</span>(cin, line); <span class="comment">// 使用逗号作为分隔符:getline(ss, line, ',')</span></span><br><span class="line"> <span class="function">stringstream <span class="title">ss</span><span class="params">(line)</span></span>;</span><br><span class="line"> vector<<span class="type">int</span>> numbers;</span><br><span class="line"> <span class="type">int</span> num;</span><br><span class="line"> <span class="keyword">while</span> (ss >> num) {</span><br><span class="line"> numbers.<span class="built_in">push_back</span>(num);</span><br><span class="line"> }</span><br><span class="line"> cout << <span class="string">"提取的数字: "</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> n : numbers) {</span><br><span class="line"> cout << n << <span class="string">" "</span>;</span><br><span class="line"> }</span><br><span class="line"> cout << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ====== 多组输入数据 ======</span></span><br><span class="line"> cout << <span class="string">"\n多组输入数据示例:"</span> << endl;</span><br><span class="line"> cout << <span class="string">"输入多行数据,每行两个整数(输入0 0结束):"</span> << endl;</span><br><span class="line"> <span class="type">int</span> x, y;</span><br><span class="line"> <span class="keyword">while</span> (cin >> x >> y) {</span><br><span class="line"> <span class="keyword">if</span> (x == <span class="number">0</span> && y == <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line"> cout << <span class="string">"和: "</span> << (x + y) << endl;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 清除状态并忽略剩余内容</span></span><br><span class="line"> cin.<span class="built_in">clear</span>();</span><br><span class="line"> cin.<span class="built_in">ignore</span>(INT_MAX, <span class="string">'\n'</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ====== 文件结束处理 ======</span></span><br><span class="line"> cout << <span class="string">"\n文件结束处理示例(输入Ctrl+Z或Ctrl+D结束):"</span> << endl;</span><br><span class="line"> cout << <span class="string">"输入多个整数:"</span> << endl;</span><br><span class="line"> vector<<span class="type">int</span>> eofNumbers;</span><br><span class="line"> <span class="type">int</span> input;</span><br><span class="line"> <span class="keyword">while</span> (cin >> input) {</span><br><span class="line"> eofNumbers.<span class="built_in">push_back</span>(input);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> cin.<span class="built_in">clear</span>();</span><br><span class="line"> cin.<span class="built_in">ignore</span>(INT_MAX, <span class="string">'\n'</span>);</span><br><span class="line"> </span><br><span class="line"> cout << <span class="string">"输入的数字: "</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> n : eofNumbers) {</span><br><span class="line"> cout << n << <span class="string">" "</span>;</span><br><span class="line"> }</span><br><span class="line"> cout << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ====== 格式化输出 ======</span></span><br><span class="line"> cout << <span class="string">"\n格式化输出示例:"</span> << endl;</span><br><span class="line"> <span class="type">double</span> pi = <span class="number">3.141592653589793</span>;</span><br><span class="line"> cout << <span class="string">"默认输出: "</span> << pi << endl;</span><br><span class="line"> cout.<span class="built_in">precision</span>(<span class="number">4</span>);</span><br><span class="line"> cout << <span class="string">"保留4位: "</span> << pi << endl;</span><br><span class="line"> cout.<span class="built_in">precision</span>(<span class="number">10</span>);</span><br><span class="line"> cout << <span class="string">"保留10位: "</span> << pi << endl;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 恢复默认精度</span></span><br><span class="line"> cout.<span class="built_in">precision</span>(<span class="number">6</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ====== 实战示例 ======</span></span><br><span class="line"> cout << <span class="string">"\n实战示例: 计算一系列数字的平均值"</span> << endl;</span><br><span class="line"> cout << <span class="string">"输入多个数字(用空格分隔): "</span>;</span><br><span class="line"> <span class="built_in">getline</span>(cin, line);</span><br><span class="line"> <span class="function">stringstream <span class="title">ss2</span><span class="params">(line)</span></span>;</span><br><span class="line"> <span class="type">double</span> sum = <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> count = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (ss2 >> num) {</span><br><span class="line"> sum += num;</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (count > <span class="number">0</span>) {</span><br><span class="line"> cout << <span class="string">"平均值: "</span> << (sum / count) << endl;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> cout << <span class="string">"没有输入数字"</span> << endl;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html"><p>使用c++作为笔试语言 <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><b</summary>
<category term="interview" scheme="https://mackz-maxw.github.io/categories/interview/"/>
</entry>
<entry>
<title>操作系统基础 | 5.5 终止进程</title>
<link href="https://mackz-maxw.github.io/2025/09/03/oper_sys25exit/"/>
<id>https://mackz-maxw.github.io/2025/09/03/oper_sys25exit/</id>
<published>2025-09-04T00:03:19.864Z</published>
<updated>2025-09-04T00:03:43.552Z</updated>
<content type="html"><![CDATA[<h3 id="终止进程_exit-和-exit">终止进程:<code>_exit()</code> 和<code>exit()</code></h3><p>进程可以通过两种通用方式终止。其中一种是<strong>异常终止</strong>,由接收到一个默认动作为终止进程(可能伴随核心转储)的信号引起。另一种方式是,进程可以使用<code>_exit()</code> 系统调用进行<strong>正常终止</strong>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"><span class="type">void</span> _exit(<span class="type">int</span> status);</span><br></pre></td></tr></table></figure><p>传递给 <code>_exit()</code> 的 <code>status</code>参数定义了进程的<strong>终止状态</strong>,该状态在此进程的父进程调用<code>wait()</code> 时可用。虽然定义为 <code>int</code>类型,但实际上只有 <code>status</code> 的低 <strong>8位</strong>会提供给父进程。按照惯例,终止状态 <strong>0</strong>表示进程<strong>成功完成</strong>,而<strong>非零</strong>状态值表示进程<strong>未成功终止</strong>。对于如何解释非零状态值没有固定规则;不同的应用程序遵循自己的惯例,这些惯例应在它们的文档中描述。SUSv3规定了两个常量 <code>EXIT_SUCCESS</code> (0) 和<code>EXIT_FAILURE</code> (1),本书中的大多数程序都使用它们。进程总是被<code>_exit()</code> <strong>成功终止</strong>(即 <code>_exit()</code>从不返回)。</p><p>尽管任何在 0 到 255 范围内的值都可以通过 <code>_exit()</code> 的<code>status</code> 参数传递给父进程,但指定大于 128 的值可能会在 shell脚本中引起混淆。原因是,当一个命令被信号终止时,shell 通过将变量<code>$?</code> 的值设置为 <strong>128 加上信号编号</strong>来表明这一事实,而这个值与进程以相同的状态值调用 <code>_exit()</code>所产生的值无法区分。</p><p>程序通常不直接调用 <code>_exit()</code>,而是调用 <code>exit()</code>库函数,该函数在调用 <code>_exit()</code> 之前会执行各种操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="type">void</span> <span class="title function_">exit</span><span class="params">(<span class="type">int</span> status)</span>;</span><br></pre></td></tr></table></figure><p><code>exit()</code> 执行以下操作: *调用<strong>退出处理程序</strong>(使用 <code>atexit()</code> 和<code>on_exit()</code>注册的函数),调用顺序与注册顺序<strong>相反</strong>。 *<strong>刷新</strong> stdio 流缓冲区。 * 使用 <code>status</code>中提供的值调用 <code>_exit()</code> 系统调用。</p><p>与 UNIX 特有的 <code>_exit()</code> 不同,<code>exit()</code>被定义为标准 C 库的一部分;也就是说,它在每个 C 实现中都可用。</p><p>进程终止的另一种方式是从 <code>main()</code> 返回,无论是通过显式<code>return</code> 语句,还是通过执行到 <code>main()</code>函数末尾而隐式返回。执行显式的 <code>return n</code> 通常等同于调用<code>exit(n)</code>,因为调用 <code>main()</code> 的运行时函数会在调用<code>exit()</code> 时使用 <code>main()</code> 的返回值。</p><p>在一种情况下,调用 <code>exit()</code> 和从 <code>main()</code>返回并不等效。如果在退出处理期间执行的任何步骤访问了 <code>main()</code>的局部变量,那么从 <code>main()</code>返回会导致<strong>未定义行为</strong>。例如,如果在调用<code>setvbuf()</code> 或 <code>setbuf()</code>(第13.2节)时指定了<code>main()</code> 的局部变量,就可能发生这种情况。</p><p>执行不指定值的 <code>return</code>,或者执行到 <code>main()</code>函数末尾,也会导致 <code>main()</code> 的调用者调用<code>exit()</code>,但结果会根据所支持的 C标准版本和所使用的编译选项而有所不同: * 在 <strong>C89</strong>中,这些情况下的行为是<strong>未定义的</strong>;程序可能以任意状态值终止。这是在Linux 上使用 <code>gcc</code> 时的默认行为,程序的退出状态取自栈上或特定CPU 寄存器中的某个随机值。应避免以这种方式终止程序。 *<strong>C99</strong> 标准要求执行到主程序末尾应等同于调用<code>exit(0)</code>。如果我们在 Linux 上使用 <code>gcc –std=c99</code>编译程序,就会得到这种行为。</p><h3 id="进程终止的细节">进程终止的细节</h3><p>在进程的正常和异常终止期间,会发生以下操作: *打开的<strong>文件描述符</strong>、<strong>目录流</strong>(第18.8节)、<strong>消息目录描述符</strong>(参见<code>catopen(3)</code> 和 <code>catgets(3)</code>手册页)和<strong>转换描述符</strong>(参见 <code>iconv_open(3)</code>手册页)被关闭。 *作为关闭文件描述符的后果,此进程持有的任何<strong>文件锁</strong>(第55章)都会被释放。* 任何附加的 <strong>System V共享内存段</strong>都会被分离(detach),并且相应每个段的<code>shm_nattch</code> 计数器减一(参见第48.8节)。 * 对于进程已设置了<code>semadj</code> 值的每个 <strong>System V 信号量</strong>,该<code>semadj</code> 值会被添加到信号量值中(参见第47.8节)。 *如果此进程是某个控制终端的<strong>控制进程</strong>,则<strong><code>SIGHUP</code>信号</strong>会被发送到该控制终端前台进程组中的每个进程,并且该终端与会话分离。我们将在第34.6节进一步讨论这一点。* 调用进程中打开的任何 <strong>POSIX命名信号量</strong>都会被关闭,就像调用了 <code>sem_close()</code>一样。 * 调用进程中打开的任何 <strong>POSIX消息队列</strong>都会被关闭,就像调用了 <code>mq_close()</code> 一样。 *如果由于此进程退出导致一个进程组变为<strong>孤儿进程组</strong>,并且该组中存在任何<strong>停止的(stopped)</strong> 进程,则该组中的所有进程都会收到一个<code>SIGHUP</code> 信号,随后是一个 <code>SIGCONT</code>信号。我们将在第34.7.4节进一步讨论这一点。 * 此进程使用<code>mlock()</code> 或<code>mlockall()</code>(第50.2节)建立的任何<strong>内存锁</strong>会被移除。* 此进程使用 <code>mmap()</code>建立的任何<strong>内存映射</strong>会被取消映射(unmapped)。</p><h3 id="退出处理程序-exit-handlers">退出处理程序 (Exit Handlers)</h3><p>有时,应用程序需要在进程终止时自动执行一些操作。考虑这样一个例子:一个应用程序库,如果在进程的生命周期中被使用,需要在进程退出时自动执行一些清理操作。由于该库无法控制进程何时以及如何退出,也不能强制主程序在退出前调用库特定的清理函数,因此无法保证清理一定会发生。在这种情况下,一种方法是使用<strong>退出处理程序</strong>(exithandler)(较老的 System V 手册使用术语“程序终止例程”)。</p><p>退出处理程序是由程序员提供的函数,在进程生命周期的某个时间点<strong>注册</strong>,然后在进程通过<code>exit()</code>正常终止时被<strong>自动调用</strong>。如果程序直接调用<code>_exit()</code>或者进程被信号异常终止,则<strong>不会调用</strong>退出处理程序。</p><p>在某种程度上,进程被信号终止时不调用退出处理程序这一事实限制了它们的实用性。我们能做的最好方式是为可能发送给进程的信号建立处理程序,并让这些处理程序设置一个标志,促使主程序调用<code>exit()</code>。(因为 <code>exit()</code>不在表21-1(第426页)列出的异步信号安全函数中,所以我们通常不能从信号处理程序中调用它。)即使这样,也无法处理<code>SIGKILL</code>的情况,因为它的默认动作无法更改。这是我们应避免使用<code>SIGKILL</code> 终止进程(如第20.2节所述)而应使用<code>SIGTERM</code>(这是 <code>kill</code>命令发送的默认信号)的又一个理由。</p><p><strong>注册退出处理程序</strong> GNU C库提供了两种注册退出处理程序的方法。第一种方法,由 SUSv3 规定,是使用<code>atexit()</code> 函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">atexit</span><span class="params">(<span class="type">void</span> (*func)(<span class="type">void</span>))</span>;</span><br></pre></td></tr></table></figure><p>成功返回 0,错误返回非零值</p><p><code>atexit()</code> 函数将 <code>func</code>添加到一个函数列表中,这些函数在进程终止时被调用。函数 <code>func</code>应定义为不接收参数且不返回值,因此具有以下一般形式: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">func</span><span class="params">(<span class="type">void</span>)</span> {</span><br><span class="line"> <span class="comment">/* 执行一些操作 */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>注意,<code>atexit()</code> 在出错时返回一个非零值(不一定是 -1)。</p><p>可以注册多个退出处理程序(甚至多次注册同一个退出处理程序)。当程序调用<code>exit()</code>时,这些函数按<strong>注册顺序的逆序</strong>被调用。这个顺序是合乎逻辑的,因为通常较早注册的函数执行更基本的清理类型,这些清理可能需要在后注册的函数之后执行。</p><p>本质上,可以在退出处理程序内部执行任何所需的操作,包括注册额外的退出处理程序(这些新处理程序会被放在待调用退出处理程序列表的头部)。但是,如果其中一个退出处理程序<strong>未能返回</strong>——要么是因为它调用了<code>_exit()</code>,要么是因为进程被信号终止(例如,退出处理程序调用了<code>raise()</code>)——那么剩余的退出处理程序将不会被调用。此外,<code>exit()</code>通常会执行的剩余操作(即刷新 stdio 缓冲区)也不会执行。</p><p>SUSv3 规定,如果退出处理程序自身调用<code>exit()</code>,结果是未定义的。在 Linux上,剩余的退出处理程序会正常调用。然而,在一些系统上,这会导致所有退出处理程序再次被调用,这可能引发无限递归(直到栈溢出杀死进程)。可移植的应用程序应避免在退出处理程序内部调用<code>exit()</code>。</p><p>SUSv3 要求实现允许一个进程至少能够注册 32 个退出处理程序。使用调用<code>sysconf(_SC_ATEXIT_MAX)</code>,程序可以确定实现定义的可以注册的退出处理程序数量的上限。(但是,无法查明已经注册了多少退出处理程序。)通过将注册的退出处理程序链入一个动态分配的链表,glibc允许注册几乎无限数量的退出处理程序。在 Linux上,<code>sysconf(_SC_ATEXIT_MAX)</code> 返回2,147,482,647(即最大的有符号 32位整数)。换句话说,在达到可注册函数数量的限制之前,其他东西(例如内存不足)就会先出问题。</p><p>通过 <code>fork()</code>创建的子进程<strong>继承</strong>其父进程的退出处理程序注册的一个副本。当进程执行<code>exec()</code>时,<strong>所有</strong>退出处理程序注册都会被<strong>移除</strong>。(这必然是如此的,因为<code>exec()</code>会替换掉退出处理程序的代码以及现有程序的其余代码。)</p><p>我们无法<strong>注销</strong>一个已经用<code>atexit()</code>(或下面描述的<code>on_exit()</code>)注册的退出处理程序。但是,我们可以让退出处理程序在执行其操作之前检查某个全局标志是否设置,并通过清除该标志来禁用该退出处理程序。</p><p>用 <code>atexit()</code>注册的退出处理程序有<strong>几个局限性</strong>。第一个是当被调用时,退出处理程序<strong>不知道</strong>传递给<code>exit()</code>的状态(status)是什么。偶尔,了解这个状态可能有用;例如,我们可能希望根据进程是成功退出还是不成功退出执行不同的操作。第二个局限性是,我们无法在调用退出处理程序时为其指定<strong>参数</strong>。这种功能可能有助于定义一个根据其参数执行不同操作的退出处理程序,或者用不同的参数多次注册同一个函数。</p><p>为了解决这些局限性,glibc提供了一种(非标准的)注册退出处理程序的替代方法:<code>on_exit()</code>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> _BSD_SOURCE <span class="comment">/* 或者: #define _SVID_SOURCE */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">on_exit</span><span class="params">(<span class="type">void</span> (*func)(<span class="type">int</span>, <span class="type">void</span> *), <span class="type">void</span> *arg)</span>;</span><br></pre></td></tr></table></figure><p>成功返回 0,错误返回非零值</p><p><code>on_exit()</code> 的 <code>func</code>参数是一个指向如下类型函数的指针: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">func</span><span class="params">(<span class="type">int</span> status, <span class="type">void</span> *arg)</span> {</span><br><span class="line"> <span class="comment">/* 执行清理操作 */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>当被调用时,<code>func()</code> 被传入两个参数:提供给<code>exit()</code> 的 <code>status</code> 参数,以及注册该函数时提供给<code>on_exit()</code> 的 <code>arg</code>参数的副本。虽然定义为指针类型,但 <code>arg</code>可由程序员自由解释。它可以被用作指向某个结构的指针;同样地,通过明智地使用类型转换,它可以被视为整数或其他标量类型。</p><p>与 <code>atexit()</code> 一样,<code>on_exit()</code>出错时返回非零值(不一定是 -1)。与 <code>atexit()</code> 一样,可以使用<code>on_exit()</code> 注册多个退出处理程序。使用 <code>atexit()</code>和 <code>on_exit()</code>注册的函数被放在同一个列表中。如果在同一个程序中同时使用这两种方法,则退出处理程序按使用这两种方法<strong>注册顺序的逆序</strong>调用。</p><p>虽然比 <code>atexit()</code> 更灵活,但 <code>on_exit()</code>在旨在可移植的程序中应<strong>避免使用</strong>,因为它不受任何标准涵盖,并且在其他UNIX 实现上很少可用。</p><p><strong>示例程序</strong> 以下代码演示了使用 <code>atexit()</code> 和<code>on_exit()</code> 注册退出处理程序。 <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> _BSD_SOURCE <span class="comment">/* 从 <stdlib.h> 获取 on_exit() 声明 */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tlpi_hdr.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">atexitFunc1</span><span class="params">(<span class="type">void</span>)</span>{</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"atexit function 1 called\n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">atexitFunc2</span><span class="params">(<span class="type">void</span>)</span>{</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"atexit function 2 called\n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">onexitFunc</span><span class="params">(<span class="type">int</span> exitStatus, <span class="type">void</span> *arg)</span>{</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"on_exit function called: status=%d, arg=%ld\n"</span>,</span><br><span class="line"> exitStatus, (<span class="type">long</span>) arg);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span>{</span><br><span class="line"> <span class="keyword">if</span> (on_exit(onexitFunc, (<span class="type">void</span> *) <span class="number">10</span>) != <span class="number">0</span>)</span><br><span class="line"> fatal(<span class="string">"on_exit 1"</span>);</span><br><span class="line"> <span class="keyword">if</span> (atexit(atexitFunc1) != <span class="number">0</span>)</span><br><span class="line"> fatal(<span class="string">"atexit 1"</span>);</span><br><span class="line"> <span class="keyword">if</span> (atexit(atexitFunc2) != <span class="number">0</span>)</span><br><span class="line"> fatal(<span class="string">"atexit 2"</span>);</span><br><span class="line"> <span class="keyword">if</span> (on_exit(onexitFunc, (<span class="type">void</span> *) <span class="number">20</span>) != <span class="number">0</span>)</span><br><span class="line"> fatal(<span class="string">"on_exit 2"</span>);</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">2</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>当我们运行这个程序时,会看到以下输出: <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ ./exit_handlers</span><br><span class="line">on_exit function called: status=2, arg=20</span><br><span class="line">atexit function 2 called</span><br><span class="line">atexit function 1 called</span><br><span class="line">on_exit function called: status=2, arg=10</span><br></pre></td></tr></table></figure></p><p><strong>(输出顺序解释)</strong>处理程序按<strong>注册顺序的逆序</strong>调用: * 最后注册的是<code>on_exit</code> (arg=20),所以最先调用。 * 然后是<code>atexitFunc2</code>。 * 然后是 <code>atexitFunc1</code>。 *最后是第一个注册的 <code>on_exit</code> (arg=10)。</p><h3 id="forkstdio-缓冲区与-_exit-之间的交互"><code>fork()</code>、stdio缓冲区与 <code>_exit()</code> 之间的交互</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tlpi_hdr.h"</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span>{</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Hello world\n"</span>);</span><br><span class="line"> write(STDOUT_FILENO, <span class="string">"Ciao\n"</span>, <span class="number">5</span>); <span class="comment">// 直接写入当前打开的文件描述符</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (fork() == <span class="number">-1</span>)</span><br><span class="line"> errExit(<span class="string">"fork"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 父子进程都会执行到这里 */</span></span><br><span class="line"> <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上程序的输出展示了一个起初令人费解的现象。当我们直接在终端运行此程序时,会看到预期的结果:<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ ./fork_stdio_buf</span><br><span class="line">Hello world</span><br><span class="line">Ciao</span><br></pre></td></tr></table></figure> 然而,当我们将标准输出重定向到一个文件时,却看到以下情况:<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ ./fork_stdio_buf > a</span><br><span class="line">$ <span class="built_in">cat</span> a</span><br><span class="line">Ciao</span><br><span class="line">Hello world</span><br><span class="line">Hello world</span><br></pre></td></tr></table></figure> 在上面的输出中,我们看到两件奇怪的事情:由<code>printf()</code> 写入的行出现了两次,并且 <code>write()</code>的输出先于 <code>printf()</code> 的输出出现。</p><p>要理解为什么用 <code>printf()</code>写入的消息会出现两次,需要回忆一下:<strong>stdio缓冲区是在进程的用户空间内存中维护的</strong>(参见第13.2节)。因此,这些缓冲区在<code>fork()</code> 时会被<strong>子进程复制</strong>。</p><p>当标准输出指向终端时,默认是<strong>行缓冲</strong>的,因此由<code>printf()</code>写入的以换行符终止的字符串会<strong>立即显示</strong>。然而,当标准输出重定向到文件时,默认是<strong>块缓冲</strong>的。因此,在我们的例子中,在<code>fork()</code> 发生时,由 <code>printf()</code>写入的字符串仍然位于父进程的 stdio缓冲区中,并且这个字符串被子进程复制。当父进程和子进程随后调用<code>exit()</code> 时,它们都会<strong>刷新</strong>各自的 stdio缓冲区副本,从而导致<strong>重复的输出</strong>。</p><p>我们可以通过以下方法之一来防止出现这种重复输出: * 作为解决 stdio缓冲问题的特定方案,我们可以在调用 <code>fork()</code><strong>之前</strong>使用 <code>fflush()</code> 来刷新 stdio缓冲区。或者,我们可以使用 <code>setvbuf()</code> 或<code>setbuf()</code> 来<strong>禁用</strong> stdio 流的缓冲。 *子进程可以调用 <code>_exit()</code> 而不是<code>exit()</code>,这样它就不会刷新 stdio缓冲区。这项技术阐明了一个更通用的原则:在创建子进程的应用程序中,通常<strong>只有一个进程</strong>(最常见的是父进程)应该通过<code>exit()</code> 终止,而其他进程应该通过 <code>_exit()</code>终止。这确保了只有一个进程调用退出处理程序并刷新 stdio缓冲区,这通常是可取的。</p><p>也存在其他允许父进程和子进程都调用 <code>exit()</code>的方法(有时是必要的)。例如,可以设计退出处理程序,使得即使从多个进程调用也能正确运行;或者让应用程序在调用<code>fork()</code><strong>之后</strong>才安装退出处理程序。此外,有时我们可能确实希望所有进程在<code>fork()</code> 后都刷新其 stdio缓冲区。在这种情况下,我们可以选择使用 <code>exit()</code>终止进程,或者根据情况在每个进程中使用显式的 <code>fflush()</code>调用。</p><p>示例程序中 <code>write()</code> 的输出没有出现两次,是因为<code>write()</code>将数据<strong>直接传输到内核缓冲区</strong>,而该缓冲区在<code>fork()</code> 期间<strong>不会被复制</strong>。</p><p>现在,程序输出重定向到文件时的第二个奇怪之处的原因应该很清楚了。<code>write()</code>的输出出现在 <code>printf()</code> 的<strong>输出之前</strong>,是因为<code>write()</code>的输出会<strong>立即</strong>传输到内核缓冲区缓存,而<code>printf()</code> 的输出只有在调用 <code>exit()</code> 刷新 stdio缓冲区时才会被传输。(通常,如第13.7节所述,在同一文件上混合使用 stdio函数和系统调用来执行 I/O 时需要小心。)</p>]]></content>
<summary type="html"><h3 id="终止进程_exit-和-exit">终止进程:<code>_exit()</code> 和
<code>exit()</code></h3>
<p>进程可以通过两种通用方式终止。其中一种是<strong>异常终止</strong>,由接收到一个默认动作为终止进</summary>
<category term="os basic" scheme="https://mackz-maxw.github.io/categories/os-basic/"/>
</entry>
<entry>
<title>操作系统基础 | 5.3 信号;fork</title>
<link href="https://mackz-maxw.github.io/2025/08/27/oper_sys24fork/"/>
<id>https://mackz-maxw.github.io/2025/08/27/oper_sys24fork/</id>
<published>2025-08-28T01:15:23.649Z</published>
<updated>2025-08-28T01:17:07.408Z</updated>
<content type="html"><![CDATA[<h3 id="信号的概念">信号的概念</h3><p>信号(Signal)是通知进程已发生某种事件的一种机制。信号有时被描述为<strong>软件中断</strong>(softwareinterrupts)。信号与硬件中断类似,因为它们会中断程序的正常执行流程;在大多数情况下,无法精确预测信号何时到达。一个进程(如果它具有适当的权限)可以向另一个进程发送信号。这种用途下,信号可以作为一种同步技术,甚至作为一种原始的<strong>进程间通信(IPC)</strong>形式。进程也可以向自己发送信号。</p><p>然而,传递给进程的许多信号的通常来源是<strong>内核</strong>(kernel)。导致内核为进程生成信号的事件类型包括:</p><ul><li><strong>发生硬件异常</strong>:这意味着硬件检测到故障条件并通知内核,内核随后向相关进程发送相应的信号。硬件异常的例子包括:执行格式错误的机器语言指令、除以0、或引用了无法访问的内存区域。</li><li><strong>用户键入了能生成信号的终端特殊字符</strong>。这些字符包括中断字符(通常是<code>Control-C</code>)和挂起字符(通常是<code>Control-Z</code>)。</li><li><strong>发生软件事件</strong>。例如:文件描述符上有输入可用、终端窗口大小改变、定时器超时、进程的CPU时间限制已超过、或该进程的一个子进程终止。</li></ul><p>每个信号都被定义为一个唯一的(小)整数,从1开始顺序编号。这些整数在<code><signal.h></code> 头文件中用 <code>SIGxxxx</code>形式的符号名定义。由于每个信号使用的实际数字因实现而异,因此在程序中总是使用这些符号名称。例如,当用户键入中断字符时,<code>SIGINT</code>(信号编号2)被传递给进程。</p><p>信号分为两大类。第一组构成了<strong>传统或标准信号</strong>(standardsignals),内核使用它们来通知进程事件。在Linux上,标准信号编号从1到31。本章我们描述标准信号。另一组信号由<strong>实时信号</strong>(realtimesignals)组成,其与标准信号的区别将在第22.8节描述。</p><p>信号被认为是<strong>由某个事件产生</strong>(generated)。一旦产生,信号随后会被<strong>递送</strong>(delivered)给一个进程,该进程随后会采取某些<strong>动作</strong>(action)来响应信号。在信号产生和递送之间的时间段,信号被称为<strong>处于等待状态</strong>(pending)。通常,一个等待中的信号会在进程下一次被调度运行时立即递送,如果进程已经在运行则立即递送(例如,进程向自己发送信号)。</p><p>然而,有时我们需要确保一段代码不会因信号的递送而中断。为此,我们可以将一个信号添加到进程的<strong>信号掩码</strong>(signalmask)中——这是一组当前<strong>被阻塞</strong>(blocked)递送的信号。如果一个信号在阻塞状态下产生,它将保持等待状态,直到后来被<strong>解除阻塞</strong>(unblocked)(从信号掩码中移除)。各种系统调用允许进程向其信号掩码中添加和移除信号。</p><p>根据信号的不同,信号被递送时,进程会执行以下<strong>默认动作</strong>(defaultactions)之一:</p><ul><li><strong>忽略信号</strong>(Ignored):即信号被内核丢弃,对进程没有影响。(进程甚至不知道它发生了。)</li><li><strong>进程被终止</strong>(Terminated)(杀死)。这有时被称为异常进程终止,与进程使用<code>exit()</code> 终止的正常进程终止相对。</li><li><strong>生成核心转储文件</strong>(Core dumpfile)且进程被终止。核心转储文件包含进程虚拟内存的一个映像,可以将其加载到调试器中,以检查进程终止时的状态。</li><li><strong>进程被停止</strong>(Stopped)——进程的执行被暂停。</li><li><strong>进程被恢复</strong>(Resumed)执行——在之前被停止后恢复执行。</li></ul><p>程序可以改变信号递送时发生的动作,而不是接受特定信号的默认动作。这被称为设置信号的<strong>处置方式</strong>(disposition)。程序可以为信号设置以下处置方式之一:</p><ul><li>发生<strong>默认动作</strong>。这对于撤销之前将信号处置方式更改为非默认值的操作很有用。</li><li><strong>忽略</strong>信号。这对于那些默认动作是终止进程的信号很有用。</li><li>执行一个<strong>信号处理程序</strong>(signalhandler)。信号处理程序是由程序员编写的函数,它执行适当的任务以响应信号的递送。例如,shell有一个用于 <code>SIGINT</code> 信号(由中断字符 <code>Control-C</code>产生)的处理程序,该处理程序使其停止当前正在做的事情并将控制权返回给主输入循环,从而再次向用户显示shell 提示符(用户按下 <code>Control-C</code>-shell中断当前处理-用户可以再次在shell中输入指令了)。通知内核应调用某个处理函数通常被称为<strong>安装</strong>(installing)或<strong>建立</strong>(establishing)一个信号处理程序。当信号处理程序因信号递送而被调用时,我们说信号已被<strong>处理</strong>(handled)或,同义词,被<strong>捕获</strong>(caught)。<em>注意:不可能将信号的处置方式设置为终止或转储核心(除非其中一个是该信号的默认处置方式)。最接近这一点的是为该信号安装一个处理程序,然后该处理程序调用<code>exit()</code> 或 <code>abort()</code>。<code>abort()</code>函数(第21.2.2节)为进程生成一个 <code>SIGABRT</code>信号,这会导致其转储核心并终止。</em></li></ul><p>Linux特有的 <code>/proc/PID/status</code>文件包含各种位掩码字段,可以检查这些字段以确定进程对信号的处理情况。位掩码以十六进制数显示,最低有效位代表信号1,左边下一位代表信号2,依此类推。这些字段是:* <code>SigPnd</code>(线程内等待信号,per-thread pending signals) *<code>ShdPnd</code>(进程范围内等待信号,process-wide pendingsignals;自Linux 2.6起) * <code>SigBlk</code>(阻塞信号,blockedsignals) * <code>SigIgn</code>(忽略信号,ignored signals) *<code>SigCgt</code>(捕获信号,caught signals)。(当我们第33.2节描述多线程进程中的信号处理时,<code>SigPnd</code> 和<code>ShdPnd</code> 字段之间的区别将变得清晰。)同样的信息也可以使用<code>ps(1)</code> 命令的各种选项来获取。</p><h3 id="fork-exit-wait-和-execve-概述"><code>fork()</code>,<code>exit()</code>, <code>wait()</code> 和 <code>execve()</code>概述</h3><ul><li><p><strong><code>fork()</code></strong> <code>fork()</code>系统调用允许一个进程(称为<strong>父进程</strong>)创建一个新的进程(称为<strong>子进程</strong>)。这是通过使新的子进程成为父进程的(近乎)完全副本来实现的:子进程获取父进程栈、数据、堆和文本段(第6.3节)的副本。“Fork”一词源于我们可以将父进程视为<strong>分裂(forking)</strong> 以产生自身的两个副本这一构想。</p></li><li><p><strong><code>exit(status)</code></strong> <code>exit()</code>库函数<strong>终止</strong>一个进程,使该进程使用的所有资源(内存、打开的文件描述符等)可供内核后续重新分配。<code>status</code>参数是一个整数,用于确定进程的<strong>终止状态</strong>。通过<code>wait()</code> 系统调用,父进程可以检索此状态。 <code>exit()</code>库函数是基于 <code>_exit()</code>系统调用构建的。在第25章,我们将解释这两个接口之间的区别。在此我们只需注意,在<code>fork()</code>之后,通常只有父进程和子进程中的<strong>一个</strong>通过调用<code>exit()</code> 终止;<strong>另一个</strong>进程应使用<code>_exit()</code> 终止。</p></li><li><p><strong><code>wait(&status)</code></strong><code>wait(&status)</code>系统调用有两个目的。首先,如果该进程的某个子进程尚未调用<code>exit()</code> 终止,那么 <code>wait()</code>会<strong>暂停</strong>该进程的执行,直到它的一个子进程终止为止。其次,子进程的终止状态通过<code>wait()</code> 的 <code>status</code>参数<strong>返回</strong>。</p></li><li><p><strong><code>execve(pathname, argv, envp)</code></strong><code>execve(pathname, argv, envp)</code>系统调用将一个新的程序(<code>pathname</code>,带有参数列表<code>argv</code> 和环境列表<code>envp</code>)<strong>加载</strong>到一个进程的内存中。现有的程序文本被丢弃,并为新程序<strong>全新创建</strong>栈、数据和堆段。此操作通常被称为<strong>execing</strong> 一个新程序。后面我们会看到,有几个库函数是基于<code>execve()</code>构建的,每个函数都在编程接口上提供了有用的变体。当我们不关心这些接口变体时,我们遵循通用惯例,将这些调用统称为<code>exec()</code>,但请注意,并没有叫这个名字的系统调用或库函数。</p></li></ul><p><strong>与其他系统的对比:</strong> 一些其他操作系统将<code>fork()</code> 和 <code>exec()</code>的功能组合到单个操作中——即所谓的<strong>spawn</strong>——该操作创建一个新进程然后执行指定的程序。相比之下,UNIX的方法通常更简单、更优雅。将这两个步骤分开使得 API更简单(<code>fork()</code>系统调用不需要参数),并且允许程序在两个步骤之间执行的操作具有极大的灵活性。此外,只进行<code>fork()</code> 而不接着执行 <code>exec()</code> 通常也很有用。</p><p>SUSv3 规定了可选的 <code>posix_spawn()</code> 函数,它结合了<code>fork()</code> 和 <code>exec()</code> 的效果。此函数以及 SUSv3规定的几个相关 API 已在 glibc 中为 Linux 实现。SUSv3 规定<code>posix_spawn()</code>是为了允许为那些不提供交换设施或内存管理单元(这在许多嵌入式系统中很典型)的硬件架构编写可移植应用程序。在此类架构上,传统的<code>fork()</code> 难以或无法实现。</p><p><strong>协同工作概述:</strong> <code>fork()</code>,<code>exit()</code>, <code>wait()</code>, 和 <code>execve()</code>通常是如何一起使用的。(shell持续执行一个循环,该循环读取命令、对其进行各种处理,然后 fork一个子进程来 exec 该命令。)</p><h3 id="创建新进程fork">创建新进程:<code>fork()</code></h3><p><code>fork()</code>系统调用创建一个新的进程,即<strong>子进程</strong>,它是调用进程,即<strong>父进程</strong>的一个几乎完全相同的副本。</p><p>理解 <code>fork()</code>的关键在于认识到,在它完成工作后,存在<strong>两个进程</strong>,并且在每个进程中,执行都从<code>fork()</code><strong>返回的地方继续</strong>。两个进程执行相同的程序代码,但它们拥有独立的栈、数据和堆段副本。子进程的栈、数据和堆段最初是父进程内存相应部分的精确副本。在<code>fork()</code>之后,每个进程都可以修改其栈、数据和堆段中的变量,而<strong>不会影响另一个进程</strong>。</p><p>在程序代码中,我们可以通过 <code>fork()</code>的返回值来区分这两个进程:</p><ul><li>对于<strong>父进程</strong>,<code>fork()</code>返回新创建子进程的<strong>进程ID(PID)</strong>。这很有用,因为父进程可能会创建多个子进程,并因此需要(通过<code>wait()</code> 或其相关函数)跟踪它们。</li><li>对于<strong>子进程</strong>,<code>fork()</code> 返回<strong>0</strong>。</li><li>如果无法创建新进程,<code>fork()</code> 返回<strong>-1</strong>。失败的可能原因包括:已达到允许该(真实)用户ID创建的进程数的资源限制(<code>RLIMIT_NPROC</code>,在第36.3节描述),或者已达到系统范围内可创建进程数的上限。</li></ul><p>必要时,子进程可以使用 <code>getpid()</code> 获取自身的进程ID,使用<code>getppid()</code> 获取其父进程的进程ID。</p><p>调用 <code>fork()</code> 时有时会使用以下惯用法:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">pid_t</span> childPid; <span class="comment">/* 在父进程中用于记录成功 fork() 后的子进程 PID */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> (childPid = fork()) {</span><br><span class="line"><span class="keyword">case</span> <span class="number">-1</span>: <span class="comment">/* fork() 失败 */</span></span><br><span class="line"> <span class="comment">/* 处理错误 */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> <span class="number">0</span>: <span class="comment">/* 成功 fork() 后的子进程进入此处 */</span></span><br><span class="line"> <span class="comment">/* 执行子进程特定的操作 */</span></span><br><span class="line"> <span class="keyword">break</span>; <span class="comment">// 或 exit()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">default</span>: <span class="comment">/* 成功 fork() 后的父进程进入此处 */</span></span><br><span class="line"> <span class="comment">/* 执行父进程特定的操作 */</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>重要的是要认识到,在 <code>fork()</code>之后,<strong>无法确定接下来是哪个进程被调度使用CPU</strong>。在编写不佳的程序中,这种不确定性可能导致称为<strong>竞争条件(race conditions)</strong> 的错误,我们将在第24.4节进一步描述。</p><p>代码清单24-1演示了 <code>fork()</code>的用法。该程序创建一个子进程,修改它在 <code>fork()</code>期间继承的全局变量和自动变量的副本。在程序中(由父进程执行的代码中)使用<code>sleep()</code>,是为了让子进程能在父进程之前被调度到CPU上,从而使子进程可以在父进程继续执行之前完成其工作并终止。使用<code>sleep()</code>这种方式并<strong>不是</strong>保证此结果的万无一失的方法;我们将在第24.5节探讨一种更好的方法。<strong>代码清单 24-1: 使用 fork()</strong> <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">"tlpi_hdr.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> idata = <span class="number">111</span>; <span class="comment">/* 分配在数据段 (data segment) */</span></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span><br><span class="line">{</span><br><span class="line"> <span class="type">int</span> istack = <span class="number">222</span>; <span class="comment">/* 分配在栈段 (stack segment) */</span></span><br><span class="line"> <span class="type">pid_t</span> childPid;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">switch</span> (childPid = fork()) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">-1</span>:</span><br><span class="line"> errExit(<span class="string">"fork"</span>);</span><br><span class="line"> <span class="keyword">case</span> <span class="number">0</span>: <span class="comment">/* 子进程分支 */</span></span><br><span class="line"> idata *= <span class="number">3</span>; <span class="comment">/* 修改继承的变量副本 */</span></span><br><span class="line"> istack *= <span class="number">3</span>; <span class="comment">/* 修改继承的变量副本 */</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>: <span class="comment">/* 父进程分支 */</span></span><br><span class="line"> sleep(<span class="number">3</span>); <span class="comment">/* 给子进程一个执行的机会 */</span></span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 父进程和子进程都会执行到这里 */</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"PID=%ld %s idata=%d istack=%d\n"</span>, (<span class="type">long</span>) getpid(),</span><br><span class="line"> (childPid == <span class="number">0</span>) ? <span class="string">"(child) "</span> : <span class="string">"(parent)"</span>, idata, istack);</span><br><span class="line"> <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>当我们运行清单24-1中的程序时,会看到以下输出: <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ ./t_fork</span><br><span class="line">PID=28557 (child) idata=333 istack=666</span><br><span class="line">PID=28556 (parent) idata=111 istack=222</span><br></pre></td></tr></table></figure>上面的输出证明,子进程在 <code>fork()</code>时获得了栈段和数据段的<strong>自有副本</strong>,并且它能够修改这些段中的变量而<strong>不影响父进程</strong>。</p>]]></content>
<summary type="html"><h3 id="信号的概念">信号的概念</h3>
<p>信号(Signal)是通知进程已发生某种事件的一种机制。信号有时被描述为<strong>软件中断</strong>(software
interrupts)。信号与硬件中断类似,因为它们会中断程序的正常执行流程;在大多</summary>
<category term="os basic" scheme="https://mackz-maxw.github.io/categories/os-basic/"/>
</entry>
<entry>
<title>操作系统基础 | 5.2 进程管理详解</title>
<link href="https://mackz-maxw.github.io/2025/08/23/oper_sys23processMgmt2/"/>
<id>https://mackz-maxw.github.io/2025/08/23/oper_sys23processMgmt2/</id>
<published>2025-08-23T15:49:20.949Z</published>
<updated>2025-08-27T02:13:53.407Z</updated>
<content type="html"><![CDATA[<h3 id="进程创建-process-creation"><strong>进程创建 (ProcessCreation)</strong></h3><p>Unix 中的进程创建方式是独特的。大多数操作系统实现一种 <strong>spawn机制</strong>来在新地址空间中创建新进程、读入可执行文件并开始执行。Unix则采用了一种不寻常的方法,将这些步骤分离成两个不同的函数:<code>fork()</code>和 <code>exec()</code>⁷。第一个函数 <code>fork()</code>创建一个作为当前任务副本的<strong>子进程</strong>。它与父进程的区别仅在于其PID(是唯一的)、其 PPID(父进程的 PID,被设置为原始进程的PID)以及某些资源和统计信息(如待处理的信号,这些不被继承)。第二个函数<code>exec()</code>将一个新的可执行文件加载到地址空间中并开始执行。<code>fork()</code> 后接<code>exec()</code> 这种组合,类似于大多数操作系统提供的单一函数。</p><p>⁷ 这里的 <code>exec()</code> 指的是 <code>exec()</code>函数家族中的任何成员。内核实现了 <code>execve()</code>系统调用,基于此实现了 <code>execlp()</code>, <code>execle()</code>,<code>execv()</code>, 和 <code>execvp()</code>。</p><h4 id="写时复制-copy-on-write"><strong>写时复制(Copy-on-Write)</strong></h4><p>传统上,在 <code>fork()</code>时,父进程拥有的所有资源都会被复制,并将副本交给子进程。这种方法很朴素且低效,因为它复制了许多本可以共享的数据。更糟糕的是,如果新进程要立即执行一个新的映像(image),所有这些复制都将被浪费。在Linux 中,<code>fork()</code> 是通过使用<strong>写时复制 (Copy-on-Write或 COW)</strong>页来实现的。写时复制是一种延迟或完全防止数据复制的技术。父进程和子进程可以共享一份单一的副本,而不是复制整个进程地址空间。</p><p>然而,数据会被标记,如果对其进行写入,就会创建一个副本,每个进程都会收到一个独一无二的副本。因此,资源的复制<strong>仅发生在它们被写入时</strong>;在此之前,它们以只读方式共享。这种技术将地址空间中每一页的复制延迟到实际被写入的时候。如果这些页永远不被写入——例如,如果在<code>fork()</code> 之后立即调用<code>exec()</code>——它们就永远不需要被复制。</p><p><code>fork()</code> 产生的唯一开销是复制父进程的<strong>页表 (pagetables)</strong>以及为子进程创建一个唯一的进程描述符。在常见的场景中(进程在 fork后立即执行一个新的可执行映像),这种优化避免了大量数据(整个地址空间,很容易达到几十兆字节)的浪费性复制。这是一个重要的优化,因为Unix 哲学鼓励快速的进程执行。</p><h4 id="forking"><strong>Forking</strong></h4><p>Linux 通过 <code>clone()</code> 系统调用来实现<code>fork()</code>。这个调用接受一系列标志(flags),用以指定父进程和子进程应共享哪些资源(如果有的话)。(关于这些标志的更多信息,请参阅本章后面的“Linux的线程实现”一节。)<code>fork()</code>, <code>vfork()</code>, 和<code>__clone()</code> 库调用都使用必要的标志来调用 <code>clone()</code>系统调用。而 <code>clone()</code> 系统调用又会调用<code>do_fork()</code>。</p><p>forking 的大部分工作由 <code>do_fork()</code> 处理,该函数定义在<code>kernel/fork.c</code> 中。此函数调用<code>copy_process()</code>,然后启动进程运行。有趣的工作是由<code>copy_process()</code> 完成的:</p><ol type="1"><li>它调用<code>dup_task_struct()</code>,该函数为新进程创建一个新的内核栈、<code>thread_info</code>结构和<code>task_struct</code>。新的值与当前任务的值完全相同。此时,子进程和父进程的描述符是完全相同的。</li><li>然后检查确保新的子进程不会超过当前用户所能拥有的进程数量资源限制。</li><li>子进程需要与父进程区分开来。进程描述符的各种成员被清除或设置为初始值。不被继承的进程描述符成员主要是统计信息。<code>task_struct</code>中的大部分值保持不变。</li><li>子进程的状态被设置为 <code>TASK_UNINTERRUPTIBLE</code>以确保它还不会运行。</li><li><code>copy_process()</code> 调用 <code>copy_flags()</code> 来更新<code>task_struct</code> 的 <code>flags</code>成员。<code>PF_SUPERPRIV</code>标志(表示任务是否使用了超级用户权限)被清除。<code>PF_FORKNOEXEC</code>标志(表示进程尚未调用 <code>exec()</code>)被设置。</li><li>它调用 <code>alloc_pid()</code> 为新任务分配一个可用的 PID。</li><li>根据传递给 <code>clone()</code> 的标志,<code>copy_process()</code>要么复制要么共享打开的文件、文件系统信息、信号处理程序、进程地址空间和命名空间。这些资源通常在给定进程的线程之间共享;否则,它们是唯一的,因此在这里被复制。</li><li>最后,<code>copy_process()</code>进行清理工作,并向调用者返回一个指向新子进程的指针。</li></ol><p>回到 <code>do_fork()</code>,如果 <code>copy_process()</code>成功返回,新的子进程会被唤醒并运行。内核有意地让<strong>子进程先运行</strong>⁸。在子进程通常只是立即调用<code>exec()</code>的常见情况下,这消除了如果父进程先运行并开始写入地址空间可能发生的任何写时复制开销。</p><blockquote><p>⁸虽然目标是让子进程先运行,但这目前(指原书写作时)并不能正确运行。</p></blockquote><h4 id="vfork"><strong>vfork()</strong></h4><p><code>vfork()</code> 系统调用与 <code>fork()</code>效果相同,但<strong>不会复制父进程的页表项 (page tableentries)</strong>。相反,子进程作为父进程地址空间中的唯一线程执行,并且<strong>父进程被阻塞</strong>,直到子进程调用<code>exec()</code>或退出。不允许子进程写入地址空间。在引入这个调用的旧版 3BSD时代,这是一个受欢迎的优化,因为当时还没有使用写时复制页来实现<code>fork()</code>。如今,有了写时复制和子进程优先运行的语义,<code>vfork()</code>的唯一好处就是<strong>不复制父进程的页表项</strong>。</p><p>如果 Linux 有一天实现了写时复制页表项,那么 <code>vfork()</code>将不再有任何好处⁹。由于 <code>vfork()</code> 的语义很棘手(例如,如果<code>exec()</code> 失败了会发生什么?),理想情况下系统不需要<code>vfork()</code>,内核也不必实现它。完全可以将 <code>vfork()</code>实现为一个普通的 <code>fork()</code> —— 这就是 Linux 在 2.2版本之前所做的。</p><p><code>vfork()</code> 系统调用是通过向 <code>clone()</code>系统调用传递一个特殊标志来实现的:</p><ol type="1"><li>在 <code>copy_process()</code> 中,<code>task_struct</code> 的成员<code>vfork_done</code> 被设置为 <code>NULL</code>。</li><li>在 <code>do_fork()</code>中,如果给定了特殊标志,<code>vfork_done</code>会被指向一个特定的地址。</li><li>子首次运行后,父进程——不会立即返回——而是等待子进程通过<code>vfork_done</code> 指针向其发出信号。</li><li>在 <code>mm_release()</code>函数中(该函数在任务退出内存地址空间时使用),会检查<code>vfork_done</code> 是否为<code>NULL</code>。如果不是,则向父进程发送信号。</li><li>回到 <code>do_fork()</code>,父进程被唤醒并返回。</li></ol><p>如果这一切都按计划进行,那么子进程现在在新的地址空间中执行,而父进程则在其原始地址空间中恢复执行。开销是降低了,但实现并不优雅。</p><blockquote><p>⁹ 页表写时复制可作为补丁,已大概率加入主流内核</p></blockquote><h3id="linux-的线程实现-the-linux-implementation-of-threads"><strong>Linux的线程实现 (The Linux Implementation of Threads)</strong></h3><p>线程是一种流行的现代编程抽象。它们在同一程序的共享内存地址空间内提供多个执行线程。它们还可以共享打开的文件和其他资源。线程实现了并发编程,并且在多处理器系统上实现了真正的并行。</p><p>Linux 拥有一个独特的线程实现。<strong>对 Linux内核而言,没有“线程”的概念</strong>。Linux将所有线程实现为标准进程。Linux内核并不提供任何特殊的调度语义或数据结构来表示线程。相反,一个线程仅仅是一个与其他进程共享某些资源的进程。每个线程都有一个唯一的<code>task_struct</code>,并且在内核看来就是一个普通的进程——线程只是恰巧与其他进程共享了资源(例如地址空间)。</p><p>这种线程实现方法与 Microsoft Windows 或 Sun Solaris等操作系统形成了巨大对比,这些操作系统在内核中提供了对线程的明确支持(有时称线程为<strong>轻量级进程(Lightweight Processes)</strong>)。“轻量级进程”这个名字概括了 Linux与其他系统在哲学上的差异。对这些其他操作系统而言,线程是一种抽象,旨在提供比笨重进程更轻量、更快速的执行单元。而对Linux而言,线程仅仅是进程之间共享资源的一种方式(而进程本身已经相当轻量)¹⁰。</p><p>例如,假设一个包含四个线程的进程。在具有显式线程支持的系统中,可能会存在一个进程描述符,该描述符又指向四个不同的线程。进程描述符描述共享资源,如地址空间或打开的文件。线程则描述它们独自拥有的资源。相反,在Linux 中,简单地存在四个进程,因而有四个普通的 <code>task_struct</code>结构。这四个进程被设置为共享某些资源。结果非常优雅。</p><blockquote><p>¹⁰ 例如,可以对比 benchmark 一下 Linux的进程创建时间与其他操作系统的进程(甚至线程!)创建时间。结果对 Linux有利。</p></blockquote><h4 id="创建线程-creating-threads"><strong>创建线程 (CreatingThreads)</strong></h4><p>线程的创建方式与普通任务相同,区别在于需要向 <code>clone()</code>系统调用传递一些标志(flags),这些标志指定了要共享的特定资源:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, <span class="number">0</span>);</span><br></pre></td></tr></table></figure> 上述代码产生的行为与普通的 <code>fork()</code>相同,但<strong>地址空间、文件系统资源、文件描述符和信号处理程序是共享的</strong>。换句话说,新任务和其父进程就是通常所说的线程。相比之下,一个普通的<code>fork()</code> 可以实现为: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">clone(SIGCHLD, <span class="number">0</span>);</span><br></pre></td></tr></table></figure> 而 <code>vfork()</code>可以实现为: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">clone(CLONE_VFORK | CLONE_VM | SIGCHLD, <span class="number">0</span>);</span><br></pre></td></tr></table></figure> 提供给 <code>clone()</code>的标志有助于指定新进程的行为,并详细说明父进程和子进程将共享哪些资源。表3.1 列出了在 <code><linux/sched.h></code> 中定义的 clone标志及其作用。</p><p><strong>(表 3.1 Clone 标志及含义)</strong> | 标志 (Flag) | 含义(Meaning) | | :----------------------- |:-----------------------------------------------------------------------------| | <code>CLONE_FILES</code> | 父子进程共享打开的文件。 | |<code>CLONE_FS</code> |父子进程共享文件系统信息(如根目录、当前工作目录)。 | |<code>CLONE_IDLETASK</code> | 将 PID 设置为零(仅由空闲任务使用)。 | |<code>CLONE_NEWNS</code> | 为子进程创建新的命名空间 (namespace)。 | |<code>CLONE_PARENT</code> |子进程与父进程拥有相同的父进程(即调用者的父进程)。 | |<code>CLONE_PTRACE</code> | 继续跟踪子进程。 | |<code>CLONE_SETTID</code> | 将 TID(线程 ID)写回用户空间。 | |<code>CLONE_SETTLS</code> | 为子进程创建新的 TLS(线程本地存储)。 | |<code>CLONE_SIGHAND</code> | 父子进程共享信号处理程序和阻塞的信号掩码。| | <code>CLONE_SYSVSEM</code> | 父子进程共享 System V SEM_UNDO 语义。 || <code>CLONE_THREAD</code> |父子进程位于同一个线程组中。这是将新进程标识为线程而非普通进程的关键标志。| | <code>CLONE_VFORK</code> | 使用了<code>vfork()</code>,父进程将睡眠直到子进程唤醒它。 | |<code>CLONE_UNTRACED</code> | 不允许跟踪进程强制对子进程使用<code>CLONE_PTRACE</code>。 | | <code>CLONE_STOP</code> | 以<code>TASK_STOPPED</code> 状态启动进程。 | |<code>CLONE_CHILD_CLEARTID</code> | 在子进程中清除 TID。 | |<code>CLONE_CHILD_SETTID</code> | 在子进程中设置 TID。 | |<code>CLONE_PARENT_SETTID</code> | 在父进程中设置 TID。 | |<code>CLONE_VM</code> |父子进程共享地址空间。这是创建线程的关键标志之一。 |</p><h4 id="内核线程-kernel-threads"><strong>内核线程 (KernelThreads)</strong></h4><p>内核在后台执行某些操作通常很有用。内核通过<strong>内核线程 (kernelthreads)</strong>来实现这一点——内核线程是<strong>仅存在于内核空间的标准进程</strong>。内核线程与普通进程的显著区别在于内核线程<strong>没有地址空间</strong>(它们的<code>mm</code> 指针指向它们的地址空间,为<code>NULL</code>)。它们只在内核空间中运行,不会上下文切换到用户空间。然而,内核线程与普通进程一样,是可调度和可抢占的。</p><p>Linux 将一些任务委托给内核线程,最著名的是 <code>flush</code> 任务和<code>ksoftirqd</code> 任务。您可以在 Linux 系统上运行<code>ps -ef</code> 命令来查看内核线程。数量很多!</p><blockquote><p>-ef 是 every full(info) 的简写。在所有进程中,CMD 带 [],TTY 为?,PPID 为 2 或 0, UID是0或者root,是内核线程的特征</p></blockquote><p>内核线程在系统启动时由其他内核线程创建。实际上,内核线程只能由另一个内核线程创建。内核通过从<code>kthreadd</code> 内核进程 <strong>fork</strong>出所有新的内核线程来自动处理此事。在<code><linux/kthread.h></code>中声明的,用于从现有内核线程生成新内核线程的接口是: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> task_struct *<span class="title function_">kthread_create</span><span class="params">(<span class="type">int</span> (*threadfn)(<span class="type">void</span> *data),</span></span><br><span class="line"><span class="params"> <span class="type">void</span> *data,</span></span><br><span class="line"><span class="params"> <span class="type">const</span> <span class="type">char</span> namefmt[], ...)</span>;</span><br></pre></td></tr></table></figure>新任务由 kthread 内核进程通过 <code>clone()</code>系统调用创建。新进程将运行 <code>threadfn</code> 函数,该函数接收<code>data</code> 参数。进程将被命名为<code>namefmt</code>,该参数接受在可变参数列表中的 printf风格格式化参数。进程创建时处于<strong>不可运行状态 (unrunnablestate)</strong>;只有在通过 <code>wake_up_process()</code>显式唤醒后,它才会开始运行。可以使用单个函数 <code>kthread_run()</code>来创建并使进程可运行: <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> task_struct *<span class="title function_">kthread_run</span><span class="params">(<span class="type">int</span> (*threadfn)(<span class="type">void</span> *data),</span></span><br><span class="line"><span class="params"> <span class="type">void</span> *data,</span></span><br><span class="line"><span class="params"> <span class="type">const</span> <span class="type">char</span> namefmt[], ...)</span>;</span><br></pre></td></tr></table></figure> 这个例程(作为宏实现)简单地调用了<code>kthread_create()</code> 和 <code>wake_up_process()</code>:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> kthread_run(threadfn, data, namefmt, ...) \</span></span><br><span class="line"><span class="meta">({ \</span></span><br><span class="line"><span class="meta"> struct task_struct *k; \</span></span><br><span class="line"><span class="meta"> \</span></span><br><span class="line"><span class="meta"> k = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \</span></span><br><span class="line"><span class="meta"> <span class="keyword">if</span> (!IS_ERR(k)) \</span></span><br><span class="line"><span class="meta"> wake_up_process(k); \</span></span><br><span class="line"><span class="meta"> k; \</span></span><br><span class="line"><span class="meta">})</span></span><br></pre></td></tr></table></figure> 启动后,内核线程会继续存在,直到它调用<code>do_exit()</code>,或者内核的另一部分调用<code>kthread_stop()</code>(传入由 <code>kthread_create()</code> 返回的<code>task_struct</code> 结构体的地址): <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">kthread_stop</span><span class="params">(<span class="keyword">struct</span> task_struct *k)</span>;</span><br></pre></td></tr></table></figure></p><h3 id="进程终止-process-termination"><strong>进程终止 (ProcessTermination)</strong></h3><p>这很遗憾,但进程最终都会消亡。当一个进程终止时,内核会释放该进程所拥有的资源,并通知其父进程关于子进程消亡的消息。</p><p>通常,进程销毁是<strong>自我诱导 (self-induced)</strong>的。当进程调用 <code>exit()</code>系统调用时就会发生,这可以是在它准备终止时显式调用,也可以是在从任何程序的main 子程序返回时隐式调用(即 C 编译器会在 <code>main()</code>返回后放置一个对 <code>exit()</code>的调用)。进程也可能<strong>非自愿地 (involuntarily)</strong>终止。这发生在进程收到一个它无法处理或忽略的信号或异常时。</p><p>无论进程如何终止,大部分工作都由 <code>do_exit()</code> 处理(定义在<code>kernel/exit.c</code> 中),它完成一系列收尾工作:</p><ol type="1"><li>它在 <code>task_struct</code> 的 <code>flags</code> 成员中设置<code>PF_EXITING</code> 标志。</li><li>它调用 <code>del_timer_sync()</code>来移除任何内核定时器。确保返回时没有定时器在排队,也没有定时器处理程序在运行。</li><li>如果启用了 BSD 进程记账 (process accounting),<code>do_exit()</code>会调用 <code>acct_update_integrals()</code> 来写出记账信息。</li><li>它调用 <code>exit_mm()</code> 来释放该进程持有的<code>mm_struct</code>。如果没有其他进程在使用这个地址空间(即地址空间未被共享),内核就会销毁它。</li><li>它调用 <code>exit_sem()</code>。如果进程正在排队等待一个 IPC信号量,它在这里被移出队列。</li><li>然后它调用 <code>exit_files()</code> 和 <code>exit_fs()</code>来分别递减与文件描述符和文件系统数据相关的对象的引用计数。如果某个引用计数降为零,说明该对象不再被任何进程使用,随即被销毁。</li><li>它将任务的退出码(存储在 <code>task_struct</code> 的<code>exit_code</code> 成员中)设置为 <code>exit()</code>提供的代码或任何强制终止它的内核机制所提供的代码。退出码存储在这里,供父进程选择性检索。</li><li>它调用 <code>exit_notify()</code>来向任务的父进程发送信号,将该任务的任何子进程<strong>重新设定父进程(reparent)</strong> 给其线程组中的另一个线程或 init进程,并将任务的退出状态(存储在 <code>task_struct</code> 结构的<code>exit_state</code> 中)设置为 <code>EXIT_ZOMBIE</code>。</li><li><code>do_exit()</code> 调用 <code>schedule()</code>来切换到新进程(参见第 4章)。因为该进程现在已不可调度,这是该任务将执行的最后代码。<code>do_exit()</code>永不返回。</li></ol><p>至此,与任务关联的所有对象(假设该任务是唯一使用者)都已释放。该任务不可运行(并且不再有地址空间可供运行),并处于<code>EXIT_ZOMBIE</code>(僵尸)退出状态。它占用的唯一内存是它的内核栈、<code>thread_info</code>结构和 <code>task_struct</code>结构。该任务存在的唯一目的是向其父进程提供信息。在父进程检索到信息,或通知内核它不感兴趣之后,进程持有的剩余内存将被释放并返回给系统使用。</p><h4id="移除进程描述符-removing-the-process-descriptor"><strong>移除进程描述符(Removing the Process Descriptor)</strong></h4><p>在 <code>do_exit()</code>完成后,已终止进程的进程描述符仍然存在,但该进程已成为僵尸 (zombie)且无法运行。如前所述,这使得系统能够在子进程终止后获取其信息。因此,清理进程之后和移除其进程描述符是分开的两个步骤。在父进程获取了已终止子进程的信息,或向内核表示不关心之后,子进程的<code>task_struct</code> 才会被释放。</p><p><code>wait()</code> 函数族是通过一个单一(且复杂)的系统调用<code>wait4()</code>实现的。标准行为是<strong>挂起调用任务的执行</strong>,直到它的一个子进程退出,此时函数返回退出子进程的PID。此外,还提供一个指针给该函数,该指针在返回时持有已终止子进程的退出码。</p><p>当最终要释放进程描述符时,会调用<code>release_task()</code>。它执行以下操作: 1. 它调用<code>__exit_signal()</code>,后者又调用<code>__unhash_process()</code>,继而调用 <code>detach_pid()</code>来将进程从 pidhash 中移除,并从任务列表中移除该进程。 2.<code>__exit_signal()</code>释放这个已死亡进程使用的任何剩余资源,并完成统计和簿记工作。 3.如果该任务是线程组的最后一个成员,并且领导者 (leader) 是僵尸进程,那么<code>release_task()</code> 会通知僵尸领导者的父进程。 4.<code>release_task()</code> 调用 <code>put_task_struct()</code>来释放包含进程内核栈和 <code>thread_info</code> 结构的内存页,并释放包含<code>task_struct</code> 的 slab 缓存。</p><p>至此,进程描述符以及仅属于该进程的所有资源都已被释放。</p><h4id="无父任务的困境-the-dilemma-of-the-parentless-task"><strong>无父任务的困境(The Dilemma of the Parentless Task)</strong></h4><p>如果一个父进程在其子进程之前退出,必须存在某种机制来将任何子任务<strong>重新设定父进程(reparent)</strong>给一个新进程,否则,没有父进程的已终止进程将永远保持僵尸状态,浪费系统内存。解决方案是在退出时将一个任务的子进程重新设定父进程给当前线程组中的另一个进程,如果失败,则设定给init 进程。<code>do_exit()</code> 调用<code>exit_notify()</code>,后者调用<code>forget_original_parent()</code>,该函数又调用<code>find_new_reaper()</code> 来执行重新设定父进程的操作:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="keyword">struct</span> task_struct *<span class="title function_">find_new_reaper</span><span class="params">(<span class="keyword">struct</span> task_struct *father)</span></span><br><span class="line">{</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">pid_namespace</span> *<span class="title">pid_ns</span> =</span> task_active_pid_ns(father);</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">task_struct</span> *<span class="title">thread</span>;</span></span><br><span class="line"></span><br><span class="line"> thread = father;</span><br><span class="line"> while_each_thread(father, thread) {</span><br><span class="line"> <span class="keyword">if</span> (thread->flags & PF_EXITING)</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> <span class="keyword">if</span> (unlikely(pid_ns->child_reaper == father))</span><br><span class="line"> pid_ns->child_reaper = thread;</span><br><span class="line"> <span class="keyword">return</span> thread;</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> pid_ns->child_reaper; <span class="comment">// 通常是 init 进程</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>(代码已简化,核心逻辑是查找同一线程组内未退出的线程,或最终返回 init进程)</p><p>这段代码尝试在进程的线程组中查找并返回另一个任务。如果线程组中没有其他任务,它就查找并返回init进程。找到合适的新父进程后,需要找到每个子进程并将其重新设定父进程给这个新的“收割者”(reaper):</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">reaper = find_new_reaper(father);</span><br><span class="line">list_for_each_entry_safe(p, n, &father->children, sibling) {</span><br><span class="line"> p->real_parent = reaper;</span><br><span class="line"> <span class="keyword">if</span> (p->parent == father) {</span><br><span class="line"> BUG_ON(p->ptrace);</span><br><span class="line"> p->parent = p->real_parent;</span><br><span class="line"> }</span><br><span class="line"> reparent_thread(p, father);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后调用 <code>ptrace_exit_finish()</code>做同样的事情,但是针对一个被 ptrace 跟踪 (ptraced) 的子进程列表。</p><p>同时拥有一个子进程列表 (child list) 和一个被跟踪子进程列表 (ptracedlist) 的基本原理很有趣;这是 2.6 内核的一个新特性。当一个任务被<code>ptrace</code>跟踪时,它被临时重新设定父进程给调试进程。然而,当该任务的原始父进程退出时,它必须与其兄弟姐妹一起被重新设定父进程。在之前的内核中,这会导致需要循环遍历系统中的每个进程来查找子进程。解决方案就是简单地维护一个进程被<code>ptrace</code>跟踪的子进程的独立列表——将查找子进程的范围从系统中的每个进程缩小到仅仅两个相对较小的列表。</p><p>随着进程成功被重新设定父进程,就不再存在 stray zombie processes(stray zombie processes)的风险。init 进程会例行地对其子进程调用<code>wait()</code>,清理分配给它的任何僵尸进程。</p>]]></content>
<summary type="html"><h3 id="进程创建-process-creation"><strong>进程创建 (Process
Creation)</strong></h3>
<p>Unix 中的进程创建方式是独特的。大多数操作系统实现一种 <strong>spawn
机制</strong>来在</summary>
<category term="os basic" scheme="https://mackz-maxw.github.io/categories/os-basic/"/>
</entry>
</feed>