-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathUnix_Linux.c
2288 lines (1740 loc) · 101 KB
/
Unix_Linux.c
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
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
一、课程介绍
UNIX /Linux环境C语言,借助学习操作系统的接口的方式来学习、理解操作系统的运行机制以及一些网络协议。 C / C++ 数据结构和算法
与平台无关,重点是算法逻辑 UNIX / Linux / Android / iOS 平台相关,系统接口 嵌入式 / 驱动 / 移植 硬件相关,硬件接口 环境介绍
内存管理 文件操作 文件管理 信号处理 进程管理 进程通信 网络信息 线程同步 线程管理
二、UNIX操作系统
丹尼斯·里奇、肯·汤普逊于1971左右在美国贝尔实验室,使用C语言开发了这款操作系统。 系统的特点是多用户、多任务,支持多种处理器
架构,高安全性、高可靠性、高稳定性。 既可以构建大型关键业务系统的商用服务器,也可以构建面向移动终端的、手持设备的三大衍生版本
System V : 银行、电信在使用的服务器系统 Berkley:MacOS、iOS带界面的 Hybrid:Minix、Linux
三、Linux操作系统
类UNIX系统,免费开源,它指的是系统的内核,凡是使用这种内核的操作系统都叫做Linux系统(发行版),严格意义上讲
Linux指的是内核, 隶属于GNU工程。(林纳斯·托瓦兹) 手机、平板电脑、路由器、视频游戏控制台、PC、大型计算机、超级计算机。 标志是
一只企鹅,因为企鹅是南极的标志性动物,根据国际公约南极为全人类共同所有,所以Linux使用企鹅作为标志也表明:开源的Linux 为全人类共
用所有,任何公司或个人无权将其私有。 Minix操作系统是一个微型的类UNIX系统、免费开源,而Linux之父就是在参照这款操作系统,才写出了
第一个版本的Linux内核代码。 GNU工程是自由软件基金会所创立的一个开源组织,基本原则就是共享,主旨是发展出一个有别于商业UNIX的免费
且完整的类UNIX系统———— GNU Not NUIX。目前Linux内核由它进行维护,所以Linux也叫GNU Linux。 GPL通用公共许可证: 允许对某些成果及
派生成果重用、修改、复制,对所有人都是自由的,但不能声明做了原始工作,或声明由他人所做。 POSIX标准:Portable Operating System
Interface,缩写为POSIX,统一的系统编程接口规范,它规定了操作系统以接口形式提供的功能的 名字、参数、返回值,它保障了应用程序源码
级的可移植性,而Linux完全遵循了这个标准。
版本管理:
早期版本:0 .01、0.02、...、0.09、1.0
旧计划:
A.B.C 1.0 -2.6
A 主版本号
B 次版本号
C 补丁序号
新计划:A.B.C.D.E 2.6 -
D 构建次数
E 描述信息
特点:
多用户、多任务
遵循GNU /GPL具有开放性
设备独立性
丰富的网络功能
可靠的系统安全
良好的可移植性
发行版:
Debian
Ubuntu
Fedora
Redhat
CentOS
四、GUN编译器
1、支持众多编程语言、平台
2、构建过程(C代码是如何变成可执行文件的)。
第一步,预处理.这一步处理 头文件、条件编译指令和宏定义。
第二步,编译.将第一步产生的文件连同其他源文件一起编译成汇编代码。
第三步,汇编。将第二步产生的汇编源码转换为 object file.
第四步,链接.将第三步产生的一些object file 连接成一个可执行的文件。
预处理:把程序员所编译的C代码翻译成标准的C代码
汇编:把预处理后的C代码翻译成汇编代码
编译:把会标代码翻译成二进制指令
链接:把若干个目标文件合并成一个可执行文件
3、gcc -v 查看版本
4、文件后缀
.h 头文件
.gch 头文件的编译结果,一般不要保留。
.c 源文件
.i 预处理文件
.s 汇编文件
.o 目标文件
.a 静态库文件
.so 共享库文件
5、参数
-E 预处理
- S 汇编
- c 编译(只生成目标文件)
- o 指定编译结果的名字
- Wall 产生尽可能多的警告
- Werror 把警告当作错误处理
- x 指定编译的语言
- g 生成调试信息
- On 优化等级
- D 编译时定义宏
- l 连接里加库
- I 指定头文件的查找路径,配置环境变量
1、打开 vim ~ /.bashrc
2、在文件末尾,添加一行 export C_INCLUDE_PATH = $C_INCLUDE_PATH : NEW_PATH
3、重新加载配置文件 source ~ /.bashrc 注意:如果要删除环境变量需要在 ~ /.bashrc文件中删除环境变量后, 退出终端重新打开。
考题1: #include<> / #include "" 区别?
考题2:头文件中可以编写哪些内容?
考题3:头文件的作用?
1、说明对应的.c文件的内容有哪些(声明函数、全局变量)。
2、定义结构、联合、枚举、宏
3、类型重定义 虽然函数可以隐式声明,但并不一定准确,而且非常有可能造成严重错误。
6、预处理指令
#include 文件包含,区别 ""和 < > 的区别
#define 定义宏常量或宏函数
#把标识符转换成字符串
##合并标识符
#undef 删除宏
#line 指定当前行的行号
#if
#ifndef
#ifdef
#elif
#endif
#error 在编译期间产生错误
#warning 在编译期间产生警告
#pragma
#pragma GCC dependency "文件" 用于监控文件,防止所依赖的文件,修改后而不知道
#pragma GCC poison 标识符 用于禁用某些标识符
#pragma pack(n) 设置结构、联合的补齐和对齐字节数
n的值必须比默认的要小 对齐边界必须是2的较小次方
头文件的作用是什么? 编译时头文件找不到怎么办。
五、库
库就是目标文件的集合,我们把不需要升级更新维护的代码打包合并在一起方便使用,也可以对源文件进行保密。 静态库在使用时是把
被调用的代码复制到调用模块中,然后再执行程序时,静态库就不需要了。
静态库的执行速度快,但占用空间大,当库中的内容发生变化时,需要重新编译出新的程序,因此不能轻易修改库中的内容,而共享库只是
在调用模块中嵌入调用代码的在库的相对位置的地址, 当执行程序时,共享库会把程序一起加载到内存中, 当执行到调用共享库中代码的指
令时跳转到共享中执行,执行完毕后再跳转回来。
占用空间小,方便更新(共享库发生变化后,程序不需要再次编译),相对于静态库执行效率略低。
静态库的扩展名为.a,共享库(动态库)的扩展名为.so
当静态库和动态库同时存在,优先调用动态库,调用静态库需要在编译时加 - static
六、静态库:
1、创建静态库
编写源代码:vi.c / .h
编译源代码:gcc -c xxx.c->xxx.o
打开生成静态库:ar -r libxxx.a x1.o x2.o...
ar命令的一些参数:
- r 把目标文件添加到静态库中,已经存在的更新
- q 将目标文件追加到静态库的末尾
- d 从静态库中删除目标文件
- t 显示静态库中有哪些目标文件
- x 把静态库拆分成目标文件
2、调用静态库 直接调用:调用者要和库在同一路径下 gcc main.c libxxx.a
设置环境:设置方法与C_INCLUDE_PATH类似
1、打开vim ~ /.bashrc 文件
2、在文件末尾,添加一行 export LIBRARY_PATH = $LIBRARY_PATH : 库文件的路径
3、重新加载配置文件 source ~ /.bashrc
4、编译时要指定库名 gcc main.c -lmath
设置编译参数: -L路径gcc main.c -L路径 - lmath
3、运行 在编译时已经把被调函数的二进制复制到可执行文件中了,在执行时不在需要静态库文件。
七、共享库
1、创建共享库
编写源代码 : vi.c /.h 编译出位置无关目标文件: gcc -c - fpic xxx.c->xxx.o
连接生成共享库: gcc - shared x1.o x2.o... - o libxxx.so
2、调用共享库
直接调用:调用者要和库在同一路径下 gcc main.c libxxx.so
设置环境:设置方法与C_INCLUDE_PATH类似
1、打开vim ~ /.bashrc 文件
2、在文件末尾,添加一行 export LIBRARY_PATH = $LIBRARY_PATH : 库文件的路径
3、重新加载配置文件 source ~ /.bashrc
4、编译时要指定库名 gcc main.c -lmath设置编译参数: -L路径gcc main.c -L路径 - lmath
3、运行 在使用共享库时,调用者只是记录了被调代码在库的位置,因此在执行时需要共享库同时被加载。
操作系统会根据LD_LIBRARY_PATH环境变量的设置来加载共享库
八、动态加载共享库
#include <dlfcn.h>
1、加载共享库
void * dlopen(const char *filename, int flag);
filename:共享库的库名,或路径
flag : RTLD_LAZY 使用时才加载
RTLD_NOW 立即加载
返回值:共享库的句柄(类似文件指针)
2、获取标识符地址并使用
void *dlsym(void *handle, const char *symbol);
handle:共享库的句柄
symbol:标识符的名字
返回值:标识符在共享库中的位置(地址,可以解引用,或跳转过去)。
3、卸载共享库
int dlclose(void *handle);
handle:共享库的句柄
返回值:成功返回0,失败返回 -1
4、获取错误信息 char *dlerror(void);
返回值:会把在使用共享库的过程中出现的错误,以字符串形式返回
九、辅助工具
nm:查看目标文件、可执行文件、静态库、共享库的中的符号列表
ldd:查看可执行程序所依赖的共享库有哪些
strip:减肥,去除掉目标文件、可执行文件、静态库和共享库中的符号列表、调试信息。 objdump 显示二进制模块的反汇编信息
十、作业
把链表、栈、队列、有序二叉树、查找、排序封装成算法共享库。 把之前做项目常用的函数,封装成工具共享库(libtools.so)。
内存管理
一、错误处理
1、通过函数返回值表示错误
返回值合法表示成功,非法表示失败
返回有效指针表示成功,空指针(NULL/ 表示失败0xFFFFFFFF)
返回0表示成功,- 1表示失败 永远成功,printf
练习1:str_len 求字符串长度,若指针为空则报错。
练习2:str_cpy(char *dest, size_t dlen, char *src) 字符串拷贝函数,考虑目标的溢出问题,如果目标位置无效或超出则报错。
练习3:intmin 求两个整数的最小值,二者相等,则报错。
练习4:intagv 求两个整数的平均值,该函数永远成功。
2、通过errno表示错误
errno是一个全局变量,它的声明在errno.h文件中,是错误的编号,它的值随时可能发生变化。 可以将它转换成有意义的字符串,
strerror(errno) <=> perror("msg");
注意:在函数执行成功的情况下,不会修改errno的值。 因此不能以errno的值不等于0就判断函数执行出错了。 所以通常会和函数的返回值配合,通过返回值判断是否出错,而通过perror查询出了什么类型的错误。
二、环境变量
以字符串形式存在的,绝大多数记录的是路径信息,它表示了当前操作系统的资源配置,环境设置等相关信息。
1、环境变量表
每个程序运行时,操作系统都会把所有的环境变量记录到一张表中,传给程序。 通过main函数参数获取
int main(int argc, char *argv[], char *environ[])
通过声明为全局变量获取 extern char **environ
2、环境变量函数
char *getenv(const char *name);
功能:根据环境变量名,获取环境变量的值
int putenv(char *string);
功能:以name = value形式设置环境变量,如果环境变量存在则更新,不存在则添加。 返回值:成功返回0,失败返回 - 1
int setenv(const char *name, const char *value, int overwrite);
功能:设置name环境变量的值为value,如果name存在且overwrite不为0则更新,否则不变。 返回值:成功返回0,失败返回 - 1
int unsetenv(const char *name);
功能:从环境变量表中删除name
返回值:成功返回0,失败返回 -1
int clearenv(void);
功能:清空环境变量表
操作系统记录的环境变量的数据一块特殊的存储空间,而在程序自己添加的环境变量需要自己准备存储空间。
注意:对于环境变量的修改,只能影响自己,不能影响别人
返回值:成功返回0,失败返回-1
练习5、从文件中读取一个程序的配置信息
ServerIP = 192.168.0.1
Port = 8899
MaxSize = 100
ContinueSec = 3
LogPath = /zhizhen/temp/
DataPath = /zhizhen/data/
练习6、给LIBRARY_PATH添加一个路径(/home/zhizhen/lib)。
LIBRARY_PATH = /home/zhizhen
LIBRARY_PATH = /home/zhizhen:/home/zhizhen/lib
三、内存管理
自动分配/释放内存(智能指针) STL 调用标准C++中的new/delete
new/delete 构造/析构 C++ 标准C中的malloc/free
malloc/free 标准C 调用POSIX
brk/sbrk POSIX 调用Linux系统接口
mmap/munmap Linux 调用内核接口
kmalloc/vmalloc 内核 调用驱动
get_free_page 驱动 ......
四、进程映像
程序是保存在磁盘上的可执行文件,加载到内存中被操作系统调用执行的程序叫进程(一个程序可以被同时执行多次形成身份不同的进程)。
进程在内存空间中的分布情况叫进程映像,从低地址到高地址依次排列的是:
代码段/只读段:
二进制指令、字符串字面值,具有const属性且被初始化过的全局、静态变量
数据段:被初始化过的全局变量和静态变量
BSS段:没有初始化过的全局变量和静态变量,进程一旦加载成功就会把这段内存清理为零。
堆:动态的分配、管理,需要程序员手动操作。从低地址向高地址扩展
栈:非静态的局部变量,包括函数的参数、返回值
从高地址向低地址使用,和堆内存之间存在一段空隙,
命令行参数及环境变量表:命令行参数、环境变量
练习7:在一个程序中打印各段内存的一个地址,然后与操作系统中的内存分配情况表比较,然后一一对应内存的分配情况。
getpid() 可以获取进程的编号
cat /proc/xxxx/maps
size 程序名 查看text data bss各段的大小
五、虚拟内存
每个进程都有各自独立的4G字节的虚拟地址空间,我们在编程时使用的永远都是这4G的虚拟地址空间中的地址,
永远无法对直接访问物理地址。
操作系统不让程序直接访问物理内存而只能使用虚拟地址空间,一方面为了操作系统自身的安全,另一方面可以让
程序使用到比物理内存更大的地址空间(把硬盘上的特殊文件与虚拟地址空间进行映射),操作系统动态维护。
4G的虚拟地址空间被分为两个部分:
0 ~ 3G 用户空间 [0,3G)
3G ~ 4G 内核空间 [3G,4G)
注意:用户空间的代码不能直接访问内核空间的代码和数据,但可以通过系统调用(不是函数。但以函数形式调用)
进入到内核态间接与内核交换数据。
如果使用了没有映射过的或者访问没有权限的虚拟内存地址,就会产生段错误(非法内存访问)。
一个进程对应一个用户空间,进程一旦切换,用户空间也会发生变化,但内核空间由操作系统管理,它不会随着进程
的切换而变化,内核空间由内核所管理的一张独立且唯一的init_mm表进行内存映射,而用户空间的表时每一个进程一张。
注意:每个进程的内存空间完全独立,不同的进程间交换虚拟内存地址没有任何意义,所以进程之间不能直接进行通信,
需要由内核中转、协调。
虚拟内存到物理内存的映射是以页为单位(1页=4K=4096字节)。
malloc首次分配内存时,至少映射33页。即使通过free释放全部内存,最初映射的33页还存在
六、内存管理API
它们都可以进行映射内存的取消映射(系统级的内存管理)。
void *sbrk(intptr_t increment);
increment:
0 获取未分配前的内存首地址(也就是已经分配尾地址)
>0 增加内存空间
<0 释放内存空间
返回值:未分配前的内存首地址,以字节为单位。
int brk(void *addr);
功能:设置未分配内存的首地址
返回值:成功返回0,失败返回-1。
它们都可以进行映射内存的取消映射(系统级的内存管理),它们背后维护着一个指针,该指针记录的是未分配的
内存的首地址(当前堆内存的最后一个字节的下一个位置)。
它们都可以进行映射内存的取消映射的功能(系统级的内存管理),但为了方便起见,sbrk一般用于分配内存,brk
用于释放内存。
注意:sbrk/brk分配和释放的都是使用权,真正的映射工作由其他系统调用完成(mmap/munmap)。
练习8:计算1000以内的素数,存储到堆内存中,不要浪费内存(sbrk/brk)。
练习9:使用sbrk和brk实现顺序栈,容量无限。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:把虚拟内存地址与物理内存或者文件建立映射关系。
addr:要映射的虚拟内存地址,如果为NULL操作系统会自动选择一个虚拟地址与物理内存映射。
length:要映射的字节数
prot:权限
flags:映射标志
fd:文件描述符(与内存映射没有关系)
offset:文件映射偏移值
返回值:映射成功后的虚拟内存地址,如果出错返回值是0xffffffff而不是NULL。
int munmap(void *addr, size_t length);
功能:取消映射
addr:需要取消映射的内存首地址
length:需要映射的字节数
返回值:成功返回0,失败返回-1
一、系统调用
UNIX/Linux系统绝大部分功能都是通过系统调用实现,比如:open/close...
UNIX/Linux把系统调用都封装成了C函数的形式,但他们不是标准C的一部分。
标准库中的函数绝大部分时间都工作在用户态,但部分时间也需要切换到内核(进行了系统调用),比如:malloc/free/fread/fwirte
/malloc/free。
我们自己所编写的代码也可以直接调用系统接口进入内核态(进行系统调用),比如:brk/sbrk/mmap/munmap
系统调用的功能代码存在于内核中,接口定义在C库中,该接口通过系统中断实现调用,而不是普通函数进行跳转。
注意:从用户态切换到内核态或从内核态返回到用户态都会消耗时间。
time a.out
real 0m0.137s 总执行时间 = 用户态 + 内核态 + 切换消耗的时间
user 0m0.092s 用户态执行时间
sys 0m0.040s 内核态执行时间
strace 程序 可以跟踪系统调用
二、一切皆文件
在UNIX/Linux系统下,几乎所有资源都是以文件形式提供的,所以在UNIX/Linux系统下一切皆文件,操作系统把
它的服务、功能、设备抽象成简单的文件,提供一套简单统一的接口,这样程序就可以像访问磁盘上的文件一样访问串
口、终端、打印机、网络等功能。
大多数情况下只需要 open/read/write/ioctl/close 就可以实现对各种设备的输入、输出、设置、控制等。
UNIX/Linux下几乎任何对象都可以当作特殊类型的文件,可以以文件的形式访问。
目录文件 里面记录的是一些文件信息,相关条目。
设备文件 在系统的/dev目录下存储了所有的设置文件
stderr
stdin
stdout
普通文件
连接文件
管道文件
socket文件
三、文件相关系统调用
open 打开或创建文件
create 创建文件
close 关闭文件
read 读文件
write 写文件
lseek 设置文件读写位置
unlink 删除连接
remove 删除文件
四、文件描述符
文件描述符是一个非负整数,表示一个打开的文件,由系统调用open/create/socket返回值。
为什么使用文件描述符而不像标准库那样使用文件指针?
因为记录文件相关信息的结构存储在内核中,为了不暴露内存的地址,因此文件结构指针不能直接给用户操作,内核
中记录一张表,其中一列是文件描述符,对应一列文件结构指针,文件描述符就相当于获取文件结构指针的下标。
内核中已经有三个已经打开的文件描述符,它们的宏定义在:
stdin 0 STDIN_FILENO
stdout 1 STDOUT_FILENO
stderr 2 STDERR_FILENO
0,1,2 都代表的是终端
dup 复制文件描述符
dup2 复制指定的文件描述符
五、open/creat/close
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
功能:打开文件
pathname:文件的路径
flags:打开的权限
O_RDONLY, 只读
O_WRONLY, 只写
O_RDWR, 读写
O_NOCTTY, 当打开的是终端设备文件,不要把该文件当作主控终端。
O_TRUNC, 清空
O_APPEND, 追加
返回值:成功则返回打开文件的描述符,失败则返回-1。
int open(const char *pathname, int flags, mode_t mode);
flags:打开的权限
O_CREAT, 文件不存在则创建
O_EXCL, 如果文件存在,则创建失败
mode:设置文件的权限
S_IRWXU 00700 user (file owner) has read, write and execute permission
S_IRUSR 00400 user has read permission
S_IWUSR 00200 user has write permission
S_IXUSR 00100 user has execute permission
S_IRWXG 00070 group has read, write and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others have read, write and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
int close(int fd);
功能:关闭打开的文件
六、read/write
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存
fd:文件描述符,open函数的返回值
buf:数据的存储位置
count:读取的字节数
返回值:成功读取到的字节数
ssize_t write(int fd,const void *buf, size_t count);
功能:把数据写入到文件
fd:文件描述符,open函数的返回值
buf:要写入的数据内存首地址
count:要写入的字节数
返回值:成功写入的字节数
注意:如果把结构体以文本形式写入到文件,需要先把结构体转换成字符串
七、lseek
off_t lseek(int fd, off_t o_fset, int whence);
功能:设置文件位置指针
返回值:文件指针的位置
练习1:实现一个Linux系统下计算文件大小的函数,使用系统调用完成。
练习2:实现带覆盖检查的cp命令。
八、dup/dup2
int dup(int oldfd);
功能:复制文件描述符,操作系统会从末的文件描述符中选择一个返回。
oldfd:被复制的文件描述符
int dup2(int oldfd, int newfd);
功能:复制指定的文件描述符,如果newfd已经被使用,则先关闭,再复制。
九、标准IO与系统IO比较
练习3:分别使用标准IO(文件读写open)比较系统IO(fopen等)随机写入1000000个整数到文件,比较哪一种更快,
为什么?
因为标准IO使用了缓冲技术,当数据写入时并没有立即把数据交给内核,而是先存放在缓冲区中,当缓冲区满时,会
一次性把缓冲区中的数据交给内核写到文件中,这样就减少了内核态与用户态的切换次数。
而系统IO每写一次数据就要进入一次内核态,这样就浪费了大量时间进行内核态与用户态的切换,因此用时更长
如果为系统IO,设置更大的缓冲区,它会比标准IO更快
一、sync/fsync/fdatasync
1、硬盘上一般都有一些缓冲区以此来提高数据的写入效率,操作系统写入数据其实只是写入缓冲区,直到缓冲区满才
排队写入硬盘中。
2、这种操作降低了写入的次数,但是提高了数据写入的延时,导致缓冲区中的数据与磁盘中的内容不同步。
void sync(void);
功能:把所有缓冲区中的数据全部同步到磁盘
注意:只是方便(发布)将数据同步到到磁盘的命令,并不等待执行完比才返回,而是命令发布后立即返回。
int fsync(int fd);
功能:指定fd文件的缓冲区数据同步到磁盘,只针对一个文件,数据同步到磁盘后才返回。
int fdatasync(int fd);
功能:指定fd文件的缓冲区数据同步到磁盘,但仅是文件的数据并不同步文件属性,数据同步到磁盘后才返回。
二、fcntl
int fcntl(int fd, int cmd, ... /* arg */ );
fd:文件描述符
cmd:操作指令,不同的操作指令决定后续参数的个数和类型。
注意:这是个变长参数的函数
int fcntl(int fd, int cmd, long newfd)
cmd:F_DUPFD
功能:复制文件描述符,与fd操作同一个文件
返回值:如果newfd没有使用则返回newfd,如果newfd已经被占用,则返回一个不小于nuewfd的文件描述符。
int fcntl(int fd, int cmd, void/long)
功能:设置或获取文件描述符标志
cmd:
F_GETFD void
返回值:0新进程保持打开状态,1新进程中关闭该文件描述符。
F_SETFD long
目前只能设置FD_CLOEXEC标志位。
返回值:0新进程保持打开状态,1新进程中关闭该文件描述符。
int fcntl(int fd, int cmd, void/long)
功能:获取文件状态标志(此文件打开的权限以及打开的方式)
cmd:
F_GETFL void
O_CREAT,O_EXCL,O_NOCTTY,O_TRUNC 不能获取到
返回值:带有文件状态标志的int类型变量,需要与各标志相与得到。
F_SETFL long
仅能设置的有
O_APPEND,O_ASYNC,O_DIRECT,O_NOATIME,O_NONBLOCK
返回值:成功返回0,失败返回-1。
int fcntl(int fd, int cmd,struct* flock)
功能:为文件加锁,能锁整个文件,或锁一部分内容。
一旦进程结束或文件描述符关闭,会自动解锁。
cmd:
F_GETLK 获取锁的信息
F_SETLK 设置锁或解除锁
F_SETLKW 测试锁
注意:加锁并不能让其他进程打不开文件或不能操作,而是使用者都要遵守锁的约定,确保文件不混乱(劝诫锁)
多进程共享文件的一种操作方式
struct flock {
short l_type; /* 锁的类型: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* 偏移值Starting offset for lock */
off_t l_len; /* 锁的长度Number of bytes to lock */
pid_t l_pid; /* 加锁的进程号 PID of process blocking our lock (F_GETLK only) */
...
};
写锁:不许写,不许看
读锁:可以看,不许写
读锁 与 读锁 成功
读锁 与 写锁 失败
写锁 与 写锁 失败
三、stat/fstat/l_stat
#include <sys/stat.h>
功能:用来获取文件属性,返回值:成功返回0,失败返回-1
int stat(const char *path, struct stat *buf);
path:需要文件路径
int fstat(int fd, struct *buf);
fd:需要打开后的文件描述符
int lstat(const char *path, struct stat *buf);
path:需要文件路径,根据路径获取软链接文件信息
stat/fstat会跟踪连接目标,而lstat不跟踪连接目标。
struct stat {
dev_t st_dev; // 设备id
ino_t st_ino; // 节点号
mode_t st_mode; // 文件类型和权限
nlink_t st_nlink; // 硬连接数
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 特殊设备ID
off_t st_size; // 文件的总字节数
blksize_t st_blksize; // IO块数
blkcnt_t st_blocks; // 占用块(512字节)数
time_t st_atime; // 最后访问时间
time_t st_mtime; // 最后修改时间
time_t st_ctime; // 最后的文件属性修改时间
};
S_ISREG(m) 测试是否是标准文件 is it a regular file?
S_ISDIR(m) 目录 directory?
S_ISCHR(m) 字符设备文件 character device?
S_ISBLK(m) 块设备文件 block device?
S_ISFIFO(m) 管道文件 FIFO (named pipe)?
S_ISLNK(m) 连接文件 symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket文件 socket?
S_IFMT 0170000 获取文件类型出错 bit mask for the file type bit fields
S_IFSOCK 0140000 socket文件
S_IFLNK 0120000 软连接 symbolic link
S_IFREG 0100000 标准文件 regular file
S_IFBLK 0060000 块设备 block device
S_IFDIR 0040000 目录 directory
S_IFCHR 0020000 字符设备 character device
S_IFIFO 0010000 管道文件 FIFO
S_ISUID 0004000 set UID bit
S_ISGID 0002000 set-group-ID bit (see below)
S_ISVTX 0001000 粘滞位 sticky bit (see below)
S_IRWXU 00700 获取权限 mask for file owner permissions
S_IRUSR 00400 读权限 owner has read permission
S_IWUSR 00200 写权限 owner has write permission
S_IXUSR 00100 属主的执行权限 owner has execute permission
S_IRWXG 00070 属主的读写执行权限 mask for group permissions
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 mask for permissions for others (not in group)
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
练习2:
struct tm *localtime(const time_t *timep);
功能:使用一个记录秒数据的变量,获取当前时间
四、access
int access(const char *pathname, int mode);
功能:测试当前用户对文件的访问权限,或文件是否存在
mode:
R_OK 是否有读权限
W_OK 写权限
X_OK 执行权限
F_OK 是否存在
返回值:0表示有,-1表示没有
五、umask
mode_t umask(mode_t mask);
功能:设置并获取权限屏蔽码,功能与umask命令一样
返回值:旧的权限屏蔽码
注意:该权限屏蔽码只对当前进程有效,进程结束后就会变成默认的,一旦设置成功,新创建的文件
就不会具有mask中的权限。
六、chmod/fchmod
功能:修改文件的权限,返回值:成功返回0,失败返回-1
int chmod(const char *path,mode_t mode);
int fchmod(int fd, mode_t mode);
注意:它们不受权限屏蔽码的干扰
七、truncate/ftruncate
功能:修改文件的大小,如果文件的,成功返回0,失败返回-1
int truncate(const char *path, ooff_t length);
int ftruncate(int fd, off_t length);
八、link/unlink/remove/rename
int link(const char *oldpath,const char *newpath);
功能:创建硬链接文件,硬链接指向的是文件的内存,因此当链接目标被删除后,依然可以访问文件的内容。
int unlink(const char *pathname);
功能:删除硬链接文件
注意:普通文件就是硬链接数量为1的文件,当把一个文件的硬链接数删除到0个时,这个文件就被删除了。
int remove(const char *pathname);
功能:删除文件,该函数是标准库中的删除文件函数,底层调用了unlink系统调用。
int remame(const char *oldpath, const char *newpath);
功能:文件重命名
九、symlink/readlink
int symlink(const char *oldpath, const char *new-path);
功能:创建软链接(目录文件只能创建软链接)
oldpath:链接目标
new-path:链接文件
返回值:成功返回0,失败返回-1
注意:可以跨文件系统,目标文件可以不存在,也可以位于另一个文件系统中
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
功能:读取软链接文件的内容而非链接目标(open打开软链接文件,打开的是目标文件)
path:链接文件的路径
buf:读取数据的存储位置
bufsiz:读取多少个字节
返回值:成功读取到的字节数
十、mkdir/rmdir
int mkdir(const char *pathname, mode_t mode);
功能:创建目录,目录一定要有执行权限,否则无法进入
返回值:成功返回0,失败返回-1
int rmdir(const char *pathname);
功能:删除空目录(只能删除空目录)
十一、chdir/fchdir/getcwd
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作目录,工作目录是指当不加路径信息时,创建/打开时从那个目录下查找,工作目录默认是
程序所在的目录。
int chdir(const char *path);
功能:修改进程的工作目录
返回值:成功返回0,失败返回-1
int fchdir(int fd)
功能:修改进程的工作目录
fd:被open打开的目录文件的fd
返回值:成功返回0,失败返回-1
十二、opendir/fdopendir/closedir/readdir/rewinddir/telldir/seekdir
#include <dirent.h>
#include <sys/types.h>
DIR *opendir(const char *name);
功能:打开一个目录流
返回值:目录流(链表)
DIR *fdopendir(int fd);
功能:使用文件描述获取目录流,fd必须是目录流
struct dirent *readdir(DIR *dirp);
功能:从目录流中读取一个文件结点信息
返回值:成功则返回下个目录进入点. 有错误发生或读取到目录文件尾则返回NULL.
struct dirent {
ino_t d_ino; /* inode number */ i节点号
off_t d_off; /* offset to the next dirent */下一个文件结点信息的偏移量
unsigned short d_reclen; /* length of this record */当前文件结点信息的长度
unsigned char d_type; /* type of file; not supported by all file system types */文件类型
char d_name[256]; /* filename */文件的名字
};
DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a UNIX domain socket.
DT_UNKNOWN The file type is unknown.
void rewinddir(DIR *dirp);
功能:把目录流的位置指针调整到开头
long telldir(DIR *dirp);
功能:获取当前目录流的位置指针在第几个结点
void seekdir(DIR *dirp, long offset);
功能:调整当前目录流的位置指针
offset:telldir(dir)的返回值
int closedir(DIR *dirp);
功能:关闭目录流
作业1:实现ls -l的功能,高仿
struct passwd *getpwuid(uid_t uid);
struct group *getgrgid(gid_t gid);
作业2:实现rm -rf的功能删除非空目录
****信号处理****
一、信号的基本概念
1、中断:中止(注意不是终止)当前正在执行的任务,转而执行其他任务(可能返回也可能不返回),中断分为硬件中断
(硬件设备产生的中断)和软件中断(其他程序产生的中断)。
2、信号:是一种软件中断,提供了一种异步执行任务的机制。
3、常见的信号
SIGINT(2) 终端中断符信号 用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程
SIGQUIT(3) 终端退出符信号 用户按退出键(Ctrl+\),产生此信号,并送至前台进程组的所有进程
SIGABRT(6) 异常终止信号 调用abort函数,产生此信号
SIGFPE(8) 算术异常信号 表示一个算术运算异常,例如除以0、浮点溢出等
SIGKILL(9) 终止信号 不能被捕获或忽略。常用于杀死进程
SIGSEGV(11) 段错误信号 试图访问未分配的内存,或向没有写权限的内存写入数据
SIGTSTP(20) 终端停止符信号 用户按停止键(Ctrl+Z),产生此信号,并送至前台进程组的所有进程
SIGCHLD(17) 子进程状态改变信号 在一个进程终止或停止时,将此信号发送给其父进程
注意:在终端中执行 kill -l 可以显示出所有信号
4、不可靠信号
建立在早期机制上的信号被称为不可靠信号,SIGHUP(1)~SIGSYS(31),不支持排队,可能会丢失,同一个
信号产生多次,进程可能只接收到一次。
5、可靠信号
采用新的机制产生的信号,SIGRTMIN(34)~SIGRTMAX(64)
支持排队,不会丢失
6、信号的来源
硬件产生:除0,非法内存访问。
这些异常是硬件(驱动)检查到,并通知内核,然后内核再向引发这些异常的进程发送相应信号。
软件产生:通过kill/raise/alarm/setitmer/sigqueue函数产生。
7、信号的处理
1、忽略
2、终止进行
3、终止进程并产生core文件
4、捕获信号并处理
补充:由代码产生的信号,当信号处理函数执行完成后,会再回到产生信号的代码,如果错误没有解决,会再次触发信号。
二、信号的捕获
#include <signal.h>
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理注册函数
signum:信号的编号,1~31, 也可以是宏
handler:
SIG_IGN 忽略该信号
SIG_DFL 默认处理
函数指针
返回值:成功返回原来信号函数处理指针,或者返回SIG_IGN、SIG_DFL,失败返回SIG_ERR
注意:在某些UNIX系统上,signal注册的函数只执行一次,执行完后就恢复成默认处理方式,如果长期使用该函数处理
信号,可以在函数结束前再注册一次。
SIGSTOP/SIGKILL 既不能被捕获(注册),也不能被处理
SIGTSTP 可以捕获不能处理
SIGSTOP信号会让进程暂停,当再次受到SIGCONT信号时会继续执行。
普通用户只能给自己的进程发送信号,而root可以给任何进程发送信号。
练习1:实现一个“死不掉的进程”,当收到信号后,给出信号产生的原因。
三、子进程信息的处理
四、发送信号
1、键盘
Ctrl+c SIGINT(2)
Ctrl+\ SIGQUIT(3)
Ctrl+z SIGTSTP(20)
2、错误
除零 SIGFPE(8)
非法访问内存 SIGSEGV(11)
3、命令
kill -signum pid
终端发送ps -aux查看所有进程
4、函数
int kill(pid_t pid, int sig);
功能:向指定的进程发送信号
pid:进程id
pid > 0 向进程号为pid的进程发送信号
pid = 0 向同组的进程发送信号
pid = -1 向所有(有权利发送信号的)进程发送信号
pid < -1 向进程号为abs(pid)的进程组发送信号
sig:信号的编号
sig值为0时,kill不会发送信号,但会进行错误检查(检查进程号或进程组id号是否存在)。
1<=sig&&sig<=64 发送指定信号
sig>64 不会发送,检查非法信号
int raise(int sig);
功能:向当前进程发送信号
五、暂停和休眠
int pause(void);
功能:一旦执行,进程就会进入无限的休眠(暂停),直到遇到信号。被信号中断返回-1。
先执行信号处理函数才会从休眠中醒来。
unsigned int sleep(unsigned int seconds);
功能:休眠指定的秒数,当有信号来临时会提前醒来,提前醒来会返回剩余的秒数,或者睡够了,返回0。
六、闹钟
unsigned int alarm(unsigned int seconds);
功能:告诉内核在seconds秒之后,向当前进程发送SIGALRM(14)信号。进程处理时钟信号默认是终止
返回值:如果之前设定的时间还没有到,则会重新设置(覆盖),并返回之前设置的剩余秒数。
七、信号集与信号屏蔽
信号集:信号的集合,由128位二进制组成,每一位代表一个信号。
int sigemptyset(sigset_t *set);
功能:清空信号集,把所有位设置为0。
int sigfillset(sigset_t *set);
功能:填满信号集,把所有位设置为1。
int sigaddset(sigset_t *set, int signum);
功能:往信号集中添加一个信号。置为1
int sigdelset(sigset_t *set, int signum);
功能:从信号集中删除一个信号。置为0
int sigismember(const sigset_t *set, int signum);
功能:判断信号集中是否存在该信号。存在返回1
信号屏蔽:当做一些特殊操作时,会希望有些信号来,而有些信号不要来,而与设置信号忽略不同的是,信号屏蔽只是暂时不来,而可以获取到
这段时间发生了哪些信号。
每个进程都有一个信号掩码(信号集),其中包括了需要屏蔽的信号,可以通过sigprocmask函数,检查、修改进程的信号掩码。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:检查、修改进程的信号掩码。
how:
SIG_BLOCK
设置当前信号集与set的并集为新的信号掩码,添加。
SIG_UNBLOCK
新的信号掩码是当前掩码与set补集的交集,删除。
SIG_SETMASK
把set当作新的信号掩码,重新设置
set:可以为空,则获取信号掩码。
oldset:旧的信号屏蔽掩码
int sigpending(sigset_t *set);
功能:获取信号屏蔽期间未决(未处理)的信号,当信号屏蔽解除后就没有了。
注意:在信号屏蔽期间发生的信号,无论多少次(不可靠信号),只能捕获一次。可靠信号依照先后顺序排队,依次排队处理
练习2:学生信息管理系统,在保存数据和加载数据时,屏蔽Ctrl+c和Ctrl+\,等数据加载、保存完成后再处理该信号。
八、带附加信息的信号
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:向内核注册信号处理函数
signum:信号编码