C++基础入门【5】- Unix操作系统
指针12字真言:由近及远,从右向左,括号优先
1. 什么是UC
U是指unix操作系统.Unix操作系统是使用C语言实现的系统级软件.
UC是指使用C语言在操作系统Unix上的用户编程.unix系统向用户提供了大量的接口.用户通过系统提供的接口,使用操作系统提供的服务.
想要写出功能强大的程序,一定要借助操作系统提供的功能.比如:网络通信,进程管理,线程管理等.
Unix背景介绍
Linux背景介绍
Linux是一款Unix操作系统,免费开源,Linux的不同发行版本都使用相同的内核.
Linux可以运行在手机、平板、路由器、视频游戏控制器、个人电脑、大型计算机、超级计算机等多种硬件平台上.
严格意义上的Linux仅指操作系统内核,但一般被用于指称某个具体的发行版本.
Linux隶属于GNU工程,整套GNU工具包从一开始就内置其中,可提供高质量的开发工具。
Linux的发明人是LInus Torvalds,同时他也是Linux商标的合法持有者。
Linux系统的特点
遵循GNU/GPL
开放性
多用户
多任务
设备独立性
丰富的网络功能
可靠的系统安全
良好的可移植性
Linux的发行版本
大众的Ubuntu
优雅地Linux Mint
锐意的Fedora
华丽的openSUSE
自由的Debian
简洁的Slackware
老牌的RedHat
计算机系统的分层
什么是操作系统
操作系统
是管理计算机硬件资源
和软件资源
的一款系统软件.
操作系统
简称OS
.
操作系统通过驱动程序管理着计算机的硬件资源.
通过系统调用和用户进行交互.
环境变量
环境变量表
environ.c
//environ.c
//环境变量
#include <stdio.h>
int main(int argc, char* argv[], char* envp[]){
extern char** environ;//通过全局变量表指针environ可以访问所有环境变量
for(char** pp = environ; *pp; pp++){
printf("%s\n", *pp);
}
return 0;
}
运行结果:
XDG_VTNR=7
LC_PAPER=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
XDG_SESSION_ID=c2
XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/tarena
LC_MONETARY=zh_CN.UTF-8
CLUTTER_IM_MODULE=ibus
SESSION=ubuntu
GPG_AGENT_INFO=/home/tarena/.gnupg/S.gpg-agent:0:1
TERM=xterm-256color
VTE_VERSION=4205
XDG_MENU_PREFIX=gnome-
SHELL=/bin/bash
LIBRARY_PATH=:.
QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
WINDOWID=54525962
LC_NUMERIC=zh_CN.UTF-8
UPSTART_SESSION=unix:abstract=/com/ubuntu/upstart-session/1000/2003
GNOME_KEYRING_CONTROL=
GTK_MODULES=gail:atk-bridge:unity-gtk-module
USER=tarena
LD_LIBRARY_PATH=:.
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
QT_ACCESSIBILITY=1
LC_TELEPHONE=zh_CN.UTF-8
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0
UNITY_HAS_3D_SUPPORT=true
XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
SESSION_MANAGER=local/TNV:@/tmp/.ICE-unix/2210,unix/TNV:/tmp/.ICE-unix/2210
DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path
XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg
UNITY_DEFAULT_PROFILE=unity
DESKTOP_SESSION=ubuntu
PATH=/home/tarena/bin:/home/tarena/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:.
QT_IM_MODULE=ibus
QT_QPA_PLATFORMTHEME=appmenu-qt5
C_INCLUDE_PATH=:.:../include
LC_IDENTIFICATION=zh_CN.UTF-8
XDG_SESSION_TYPE=x11
PWD=/home/tarena/stdc02/day01
JOB=unity-settings-daemon
XMODIFIERS=@im=ibus
GNOME_KEYRING_PID=
LANG=zh_CN.UTF-8
GDM_LANG=zh_CN
MANDATORY_PATH=/usr/share/gconf/ubuntu.mandatory.path
LC_MEASUREMENT=zh_CN.UTF-8
COMPIZ_CONFIG_PROFILE=ubuntu
IM_CONFIG_PHASE=1
PAPERSIZE=a4
GDMSESSION=ubuntu
SESSIONTYPE=gnome-session
GTK2_MODULES=overlay-scrollbar
SHLVL=1
HOME=/home/tarena
XDG_SEAT=seat0
LANGUAGE=zh_CN:en_US:en
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
UPSTART_INSTANCE=
UPSTART_EVENTS=xsession started
XDG_SESSION_DESKTOP=ubuntu
LOGNAME=tarena
COMPIZ_BIN_PATH=/usr/bin/
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-AvRK43kecy
XDG_DATA_DIRS=/usr/share/ubuntu:/usr/share/gnome:/usr/local/share:/usr/share:/var/lib/snapd/desktop:/var/lib/snapd/desktop
QT4_IM_MODULE=ibus
LESSOPEN=| /usr/bin/lesspipe %s
INSTANCE=
UPSTART_JOB=unity7
XDG_RUNTIME_DIR=/run/user/1000
DISPLAY=:0
XDG_CURRENT_DESKTOP=Unity
GTK_IM_MODULE=ibus
LESSCLOSE=/usr/bin/lesspipe %s %s
LC_TIME=zh_CN.UTF-8
LC_NAME=zh_CN.UTF-8
XAUTHORITY=/home/tarena/.Xauthority
_=./environ
OLDPWD=/home/tarena/stdc02
main函数的第三个参数envp[]
//环境变量
//environ.c
#include <stdio.h>
int main(int argc, char* argv[], char* envp[]){
extern char** environ;//通过全局变量表指针environ可以访问所有环境变量
/*
for(char** pp = environ; *pp; pp++){
printf("%s\n", *pp);
}
*/
printf("environ =%p\n", environ);
printf("envp =%p\n", envp);
for(char** p = environ; *p; p++){
printf("%s\n", *p);
}
return 0;
}
tarena@TNV:day01$ vim environ.c
tarena@TNV:day01$ gcc environ.c -o environ
tarena@TNV:day01$ ./environ
environ =0x7fff4b5e58f8
envp =0x7fff4b5e58f8
2. 库文件的制作和使用
什么是库文件?
- 单一模型:将程序中所有功能全部实现于一个单一的源文件内部。编译时间长,不易于维护和升级,不易于协作开发。
- 分离模型:将程序的不同功能模块划分到不同的源文件中。缩短编译时间,易于维护和升级,易于协作和开发。
- 对多个目标文件的管理比较麻烦
- 将多个目标文件统一整理合成一个库文件。
- 为何要把一个程序分成多个源文件,并由每个源文件编译生成独立的目标文件?
- 化整为零、易于维护、便于协作。
- 为何要把多个目标文件合成一个库文件?
- 集零为整、方便使用、易于复用。
- 可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类
- 库文件一般指计算机上的一类文件,分两种,一种是静态库,另一种是动态库
2.1 静态库
- 静态库的本质就是将多个目标文件打包成一个文件。
- 链接静态库就是将库中被调用的代码复制到调用模块中
- 静态库的拓展名是.a 例如: libxxx.a
以构建数学库为例,静态库的构建顺序如下:
A.编辑库的实现代码和接口声明
- 计算模块:calc.h 、calc.c
- 显示模块:show.h、show.c
- 接口文件:math.h
B.编译成目标文件
- gcc -c calc.c
- gcc -c show.c
C.打包成静态库
- ar -r libmath.a calc.o show.o
ar命令
ar[选项] <静态库文件> <目标文件列表>
-r 将目标插入到静态库中,已存在则更新
-q 将目标文件追加到静态库尾
-d 从静态库中删除目标文件
-t 列表显示静态库中的目标文件
-x 将静态库展开为目标文件
编辑库的使用代码
main.c
编译并链接静态库
- 直接链接静态库
- gcc main.c libmath.a
- 用-l指定库名,用-L指定库路径
- gcc main.c -lmath -L.
- 用-l指定库名,用LIBRARY_PATH环境变量指定库路径
- export LIBRARY_PATH=$LIBRARY_PATH:.
- gcc main.c -lmath
calc.h
//calc.h
//计算机模块头文件
#ifndef _CALC_H
#define _CALC_H
#include <stdio.h>
//函数声明
//加法计算
int add(int a, int b);
//减法函数
int sub(int a, int b);
#endif
calc.c
//calc.c
//计算模块的实现
#include "calc.h"
//加法函数
int add(int a, int b){
return a + b;
}
//减法函数
int sub(int a, int b){
return a - b;
}
show.h
//show.h
//显示模块头文件
#ifndef _SHOW_H
#define _SHOW_H
#include <stdio.h>
//显示函数 3 + 5 = 8 4 - 1 = 3
void show(int l, char op, int r, int res);
#endif
show.c
//show.c
//显示模块实现
#include "show.h"
//显示函数
void show(int l, char op, int r, int res){
printf("%d %c %d = %d\n",l, op, r, res);
}
main.c
//main.c
//调用模块
#include <stdio.h>
#include "calc.h"
#include "show.h"
int main(void){
int a =123, b =456;
int res = add(a, b);
show(a, '+', b, res);
show(a, '-', b, sub(a, b));
return 0;
}
运行结果:
tarena@TNV:static$ ls
calc.c calc.h calc.o main.c show.c show.h show.o
tarena@TNV:static$ ar -r libmath.a calc.c show.c
ar: 正在创建 libmath.a
tarena@TNV:static$ ls
calc.c calc.h calc.o libmath.a main.c show.c show.h show.o
tarena@TNV:static$ rm libmath.a
tarena@TNV:static$ ls
calc.c calc.h calc.o main.c show.c show.h show.o
tarena@TNV:static$ ar -r libmath.a *.o
ar: 正在创建 libmath.a
tarena@TNV:static$ ls
calc.c calc.h calc.o libmath.a main.c show.c show.h show.o
tarena@TNV:static$ gcc main.c
/tmp/ccKLDga9.o:在函数‘main’中:
main.c:(.text+0x21):对‘add’未定义的引用
main.c:(.text+0x39):对‘show’未定义的引用
main.c:(.text+0x48):对‘sub’未定义的引用
main.c:(.text+0x5c):对‘show’未定义的引用
collect2: error: ld returned 1 exit status
tarena@TNV:static$ gcc main.c libmath.a -o main
tarena@TNV:static$ ls
calc.c calc.h calc.o libmath.a main main.c show.c show.h show.o
tarena@TNV:static$ ./main
123 + 456 = 579
123 - 456 = -333
tarena@TNV:static$ nm libmath.a
calc.o:
0000000000000000 T add
0000000000000014 T sub
show.o:
U printf
0000000000000000 T show
math.h
//math.h
//接口文件
#ifndef _MATH_H
#define _MATH_H
#include "calc.h"
#include "show.h"
#endif // _MATH_H
main.c
//main.c
//调用模块
#include <stdio.h>
//#include "calc.h"
//#include "show.h"
#include "math.h"
int main(void){
int a =123, b =456;
int res = add(a, b);
show(a, '+', b, res);
show(a, '-', b, sub(a, b));
return 0;
}
运行结果:
tarena@TNV:static$ vi math.h
tarena@TNV:static$ gcc main.c libmath.a -o main
tarena@TNV:static$ ./main
123 + 456 = 579
123 - 456 = -333
tarena@TNV:static$ ls
calc.c calc.h calc.o libmath.a main main.c math.h show.c show.h show.o
2.2 动态库
- 动态库和静态库不同链接动态库不需要将调用的函数代码复制到包含调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,在该程序执行到这段指令时,会加载动态库并寻找调用函数的入口地址并执行之。
- 如果动态库中的代码同时为多个进程所用,动态库在内存的实例仅需一份,为所有使用该库的进程所共享,因此动态库亦称共享库。
- 动态库的拓展名是 .so 例如:libxxx.so
tarena@TNV:day02$ ls
static
tarena@TNV:day02$ mkdir shared
tarena@TNV:day02$ ls
shared static
tarena@TNV:day02$ cd shared/
tarena@TNV:shared$ cp ../static/*.c .
tarena@TNV:shared$ cp ../static/*.h .
tarena@TNV:shared$ ls
calc.c calc.h main.c math.h show.c show.h
tarena@TNV:shared$ gcc -c -fpic calc.c
tarena@TNV:shared$ gcc -c -fpic show.c
tarena@TNV:shared$ ls
calc.c calc.h calc.o main.c math.h show.c show.h show.o
tarena@TNV:shared$ gcc -shared calc.o show.o -o libmath.so
tarena@TNV:shared$ ls
calc.c calc.h calc.o libmath.so main.c math.h show.c show.h show.o
tarena@TNV:shared$ gcc main.c libmath.so
tarena@TNV:shared$ ls
a.out calc.c calc.h calc.o libmath.so main.c math.h show.c show.h show.o
tarena@TNV:shared$ ./a.out
123 + 456 = 579
123 - 456 = -333
tarena@TNV:shared$ echo $LD_LIBRARY_PATH
:.
tarena@TNV:
链接动态库
以构建数学库为例,动态库的构建顺序如下:
A.编辑库的实现代码和接口声明
- 计算模块:calc.h 、calc.c
- 显示模块:show.h、show.c
- 接口文件:math.h
B.编译成目标文件
- gcc -c -fpic calc.c
- gcc -c -fpic show.c
C.打包成动态库
-
gcc -shared calc.o show.o -o libmath.so
-
编译链接也可以合并成一步完成
-
gcc -shared -fpic calc.c show.c -o libmath.so
-
PIC (Position Independent Code,位置无关代码)
-
调用代码通过相对地址标识调用代码的位置,模块中的指令与该模块被加载到内存中的位置无关
-
fPIC:大模式,生成代码比较大,运行速度比较慢,所有平台都支持
-
fpic:小模式,生成代码比较小,运行速度比较快,仅部分平台支持
-
编辑库的使用代码:main.c
-
编辑并链接动态库
-
直接链接动态库
-
gcc main.c libmath.so
-
用-l指定库名,用-L指定库路径
-
gcc main.c -lmath -L.
-
用-l指定库名,用LIBRARY_PATH环境变量指定库路径
-
export LIBRARY_PATH=$LIBRARY_PATH:.
-
gcc main.c -lmath
-
运行时需要保证LD_LIBRARY_PATH环境变量中包含共享库所在的路径用以告知链接器在运行时链接动态库
-
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
-
在可执行程序的链接阶段,并不将所有调用函数的二进制代码复制到可执行程序中,而只是将该函数在共享库中的地址嵌入到调用模块中,因此运行时需要依赖共享库
动态库和静态库比较
优缺点 | 静态库 | 动态库 |
---|---|---|
优点 | 执行速度快 | 可执行文件体积小,节省空间 |
1 | 可执行程度不依赖库的存在 | 易于链接,便于更新维护 |
缺点 | 文件提交相对较大 | 文件执行速度相对较慢 |
2 | 更新困难,维护成本高 | 可执行程序依赖库文件的存在 |
动态库的 动态加载
- 在程序执行的过程中,开发人员可以动态加载共享库(什么时候用什么时候加载)
- 在程序中动态加载共享库需要调用一组特殊的函数,它们被声明于一个专门的头文件中,并在一个独立的库中予以实现。
- 使用这组函数需要包含此头文件,并链接该库
– #include <dlfcn.h>
– -ldl
void* dlopen(char const* filename, int flag)
- void* dlopen(char const* filename, int flag);
– 功能:将共享库载入内存并获得其访问句柄
– 参数:filename 动态库路径,若只给文件名不带目录,则根据LD_LIBRARY_PATH环境变量的值搜索动态库
– flag 加载方式 可取以下值: - RTLD_LAZY -延迟加载,使用动态库中的符号时才真的加载进内存。
- RTLD_NOW -立即加载。
– 返回值:成功返回动态库的访问句柄,失败返回NULL。
– 句柄:句柄唯一的标识了系统内核所维护的共享库对象,将作为后续函数调用的参数
void* dlsym(void* handle,char const* symbol);
- void* dlsym(void* handle,char const* symbol);
– 功能:从已被加载到的动态库中获取特定名称的符号地址
– 参数: - handle 动态库访问句柄
- symbol 符号名
– 返回值:成功返回给定符号的地址,失败返回NULL。
– 该函数所返回的指针为void*类型,需要造型为与实际目标类型相一致的指针,才能使用。
例如:
int (*p_add)(int,int) = ( int(*)(int,int) )dlsym(handle," add");
if(!p_add){
fprintf(stderr," 获取地址失败!\n");
exit(EXIT_FAILURE);
}
int sum = p_add(30,20);
int dclose(void* handle);
- 功能:从内存中卸载动态库
- 参数:handle 动态库句柄
- 返回值:成功返回0,失败返回非0.
- 所卸载的共享库未必会真的从内存中立即消失,因为其他程序可能还需要使用该库
- 只有所有使用该库的程序都显示或隐式地卸载了该库,该库所占用的内存空间才会真正得到释放
- 无论所卸载的共享库是否真正被释放,传递给dlclose函数的句柄都会在该函数成功返回后立即失效
char* dlerror(void)
- char* dlerror(void)
- 功能:获取在加载使用和卸载共享库过程中所发生的错误
- 返回值:有错误则返回指向错误信息字符串的指针,否则返回NULL。
- 例如:
void* handle = dlopen("libmath.so",RTLD_NOW);
if(!handle){
fprintf(stderr,"dlopen:%s\n",dlerror());
exit(EXIT_FAILURE);
}
辅助工具
- 查看符号表:nm
– 列出目标文件、可执行程序、静态库、或共享库中的符号
– 例:nm libmath.a - 查看依赖:ldd
– 查看可执行文件或者共享库所依赖的共享库
– 例:ldd a.out
tarena@TNV:day02$ nm libmath.a
calc.o:
0000000000000000 T add
0000000000000014 T sub
show.o:
U printf
0000000000000000 T sho
tarena@TNV:day02$ ldd load
linux-vdso.so.1 => (0x00007ffe9c955000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7bf9ef7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7bf9b2d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7bfa0fb000)
tarena@TNV:day02$ nm libmath.so
00000000000006e0 T add
0000000000201028 B __bss_start
0000000000201028 b completed.7594
w __cxa_finalize@@GLIBC_2.2.5
00000000000005e0 t deregister_tm_clones
0000000000000670 t __do_global_dtors_aux
0000000000200e08 t __do_global_dtors_aux_fini_array_entry
0000000000201020 d __dso_handle
0000000000200e18 d _DYNAMIC
0000000000201028 D _edata
0000000000201030 B _end
0000000000000744 T _fini
00000000000006b0 t frame_dummy
0000000000200e00 t __frame_dummy_init_array_entry
0000000000000828 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
000000000000075c r __GNU_EH_FRAME_HDR
0000000000000588 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000200e10 d __JCR_END__
0000000000200e10 d __JCR_LIST__
w _Jv_RegisterClasses
U printf@@GLIBC_2.2.5
0000000000000620 t register_tm_clones
0000000000000706 T show
00000000000006f4 T sub
0000000000201028 d __TMC_END__
2.3 错误处理
错误的处理和表示
- 针对因为运行环境、人为操作等原因导致的错误,程序的设计者需要提前有所考虑,向函数的调用者提供的必要的信息,以明确发生了错误,以及具体是什么错误。
- 习俗:如果一个函数的返回值是一个指针,那么就用返回NULL指针的方式表示错误;如果一个函数的合法返回值属于某个有限的值域,那么就用该值域以外的值表示错误;如果一个函数不需要返回值,那么就用返回0表示成功,返回-1表示失败。
通过错误号了解具体的错误原因
- 系统于定义的整数类型全局变量errno中存储了最近一次系统调用的错误编号
- 头文件errno.h包含了对errno全局变量的外部声明和各种错误号的宏定义
- /usr/include/errno.h
- /usr/include/asm-generic/errno.h
- /usr/include/asm-generic/errno-base.h
errno-base.h
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#endif
errno.h
#ifndef _ASM_GENERIC_ERRNO_H
#define _ASM_GENERIC_ERRNO_H
#include <asm-generic/errno-base.h>
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Function not implemented */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale NFS file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#define ECANCELED 125 /* Operation Canceled */
#define ENOKEY 126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED 129 /* Key was rejected by service */
/* for robust mutexes */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
#define ERFKILL 132 /* Operation not possible due to RF-kill */
#endif
#include <string.h>
char* strerror(int errnum)
- 功能:将整数形式的错误号转换为有意义的字符串
- 参数:errnum 错误号
- 返回值:返回与参数错误号对应的描述字符串
#include <stdio.h>
void perror(char const* tag)
- 功能:在标准出错设备上打印最近一次函数调用的错误信息
- 参数:tag 为用户自己制定的提示内容,输出时,会自动在该提示内容和错误信息之外添加冒号进行分隔。
注意
- 虽然所有的错误编号都不是零,但是因为在函数执行成功的情况下存储错误编号的全局变量errno并不被清零,所以不能用该变量的值是否为零作为最近一次函数调用是否成功的判断条件。正确的做法是,先根据函数的返回值判断其是否出错,在确定出错的前提下,再根据errno的值判断具体出了什么错。
//错误处理
#include <stdio.h>
#include <string.h>// strerror();
#include <stdlib.h>
#include <errno.h> // 全局变量 errno
int main(void){
void* p = malloc(0xffffffffffffffff);
if(p == NULL){//malloc 失败 --> 原因 --> 编号 --> errno
fprintf(stderr, "errno = %d\n", errno);
fprintf(stderr, "malloc:%s\n", strerror(errno));
perror("malloc");
return -1;
}
free(p);
return 0;
}
tarena@TNV:day02$ vim error.c
tarena@TNV:day02$ gcc error.c -o error
tarena@TNV:day02$ ./error
error = 12
malloc:Cannot allocate memory
malloc: Cannot allocate memory
//错误处理
#include <stdio.h>
#include <string.h>// strerror();
#include <stdlib.h>
#include <errno.h> // 全局变量 errno
int main(void){
void* p = malloc(0xffffffffffffffff);
/*if(p == NULL){//malloc 失败 --> 原因 --> 编号 --> errno
fprintf(stderr, "errno = %d\n", errno);
fprintf(stderr, "malloc:%s\n", strerror(errno));
perror("malloc");
return -1;
}
free(p);*/
fprintf(stderr, "errno = %d\n", errno);
File* fp = fopen("test.c","r");
//if(errno)
if(fp == NULL){
perror("fopen");
return -1;
}
fclose(fp);
return 0;
}
tarena@TNV:day02$ vim error.c
tarena@TNV:day02$ gcc error.c -o error
tarena@TNV:day02$ ./error
error = 12
load.c
//动态库的动态加载
#include <stdio.h>
#include <dlfcn.h>//dlopen dlsym dlclose dlerror
int main(void){
//将动态库载入内存
void* handle = dlopen("./shared/libmath.so", RTLD_NOW);//立即加载 RTLD_LAZY:延迟加载
if(handle == NULL){//dlopen 失败
fprintf(stderr, "dlopen:%s\n", dlerror());
return -1;
}
//获取库中函数的地址
int (*add)(int, int) = dlsym(handle, "add");
if(add == NULL){
fprintf(stderr, "dlsym:%s\n", dlerror());
return -1;
}
int (*sub)(int, int) = dlsym(handle, "sub");
if(sub == NULL){
fprintf(stderr, "dlsym:%s\n", dlerror());
return -1;
}
void (*show)(int, char, int, int) = dlsym(handle, "show");
if(show == NULL){
fprintf(stderr, "dlsym:%s\n", dlerror());
return -1;
}
//使用
int a = 123;
int b = 456;
show(a, '+', b, add(a, b));
show(a, '-', b, sub(a, b));
//卸载动态库
if(dlclose(handle)){
fprintf(stderr, "dlclose:%s\n", dlerror());
return -1;
}
return 0;
}
tarena@TNV:day02$ vim load.c
tarena@TNV:day02$ gcc load.c -o load
/tmp/ccmaSYhS.o:在函数‘main’中:
load.c:(.text+0x13):对‘dlopen’未定义的引用
load.c:(.text+0x23):对‘dlerror’未定义的引用
load.c:(.text+0x5a):对‘dlsym’未定义的引用
load.c:(.text+0x6a):对‘dlerror’未定义的引用
load.c:(.text+0xa1):对‘dlsym’未定义的引用
load.c:(.text+0xb1):对‘dlerror’未定义的引用
load.c:(.text+0xe8):对‘dlsym’未定义的引用
load.c:(.text+0xf8):对‘dlerror’未定义的引用
load.c:(.text+0x17e):对‘dlclose’未定义的引用
load.c:(.text+0x187):对‘dlerror’未定义的引用
collect2: error: ld returned 1 exit status
tarena@TNV:day02$ gcc load.c -ldl -o load
tarena@TNV:day02$ ./load
123 + 456 = 579
123 - 456 = -333
tarena@TNV:day02$ ls
env load load.c shared static test test.c
tarena@TNV:day02$ cp ./shared/libmath.so libmath.so
tarena@TNV:day02$ ls
env libmath.so load load.c shared static test test.c
tarena@TNV:day02$ vim load.c
tarena@TNV:day02$ gcc load.c -o load
/tmp/ccxriOgC.o:在函数‘main’中:
load.c:(.text+0x13):对‘dlopen’未定义的引用
load.c:(.text+0x23):对‘dlerror’未定义的引用
load.c:(.text+0x5a):对‘dlsym’未定义的引用
load.c:(.text+0x6a):对‘dlerror’未定义的引用
load.c:(.text+0xa1):对‘dlsym’未定义的引用
load.c:(.text+0xb1):对‘dlerror’未定义的引用
load.c:(.text+0xe8):对‘dlsym’未定义的引用
load.c:(.text+0xf8):对‘dlerror’未定义的引用
load.c:(.text+0x17e):对‘dlclose’未定义的引用
load.c:(.text+0x187):对‘dlerror’未定义的引用
collect2: error: ld returned 1 exit status
tarena@TNV:day02$ gcc load.c -ldl -o load
tarena@TNV:day02$ ./load
123 + 456 = 579
123 - 456 = -333
2.4. 虚拟地址空间
- 对于32位操作系统而言,每个进程都有4G大小的虚拟地址空间。
- 所谓的虚拟地址空间本质就是一个地址范围,表示程序的寻址能力。我们所看到的虚拟地址,都是在这个4G范围内。
- 对于32位操作系统而言,其虚拟地址空间范围从0x0000 0000到0xFFFF FFFF
- 其中0 ~ 3G-1的范围归用户所使用,称为用户地址空间,3G ~ 4G-1的 范围归内核使用,称为内核地址空间。
- 对于64位的操作系统而言,目前应用程序没有那么大的内存需求,所以不支持完全的64位虚拟地址。
- 64位操作系统上,其用户地址空间范围是:0x0000 0000 0000 0000 ~ 0x0000 FFFF FFFF FFFF
- 64位操作系统上,其内核地址空间范围是:0xFFFF 0000 0000 0000 ~ 0xFFFF FFFF FFFF FFFF
- 内核地址空间和用户地址空间之间是不规范地址空间,不允许使用。
- 用户地址空间的代码不能直接访问内核空间的代码和数据,但可以通过系统调用进入内核态,间接与系统内核交互。
- 用户地址空间从低地址到高地址布局如下:
代码区:只读常量区
//虚拟地址空间布局
#include <stdio.h>
#include <stdlib.h>
const int const_global = 1;//常全局变量
int init_global = 2;//初始化全局变量
int uninit_global;//未初始化全局变量
int main(int argc, char* argv[], char* envp[]){
const static int const_static = 3;//常静态变量
static int init_static = 4;//初始化静态变量
static int uninit_static;//未初始化静态变量
const int const_local = 5;//常局部变量
int local;//局部变量
int* heap = malloc(sizeof(int));//堆变量
*heap = 6;
char* string = "abc";//字面值常量
printf("------------参数环境变量----------命令行参数和环境变量\n");
printf(" 命令行参数:%p\n",argv);
printf(" 环境变量:%p\n",envp);
printf("----------------栈区--------------非静态局部变量\n");
printf(" 常局部变量:%p\n",&const_local);
printf(" 局部变量:%p\n",&local);
printf("----------------堆区--------------动态内存分配\n");
printf(" 堆变量:%p\n",heap);
printf("-----------------BSS区------------未初始化的全局和静态局部变量\n");
printf(" 未初始化全局变量:%p\n",&uninit_global);
printf(" 未初始化静态变量:%p\n",&uninit_static);
printf("----------------数据区------------不具常属性且被初始化的全局和静态局部变量\n");
printf(" 初始化全局变量:%p\n",&init_global);
printf(" 初始化静态变量:%p\n",&init_static);
printf("----------------代码区------------可执行指令、字面值常量、具有常属性且被初始化的全局和静态局部变量\n");
printf(" 函数:%p\n",main);
printf(" 常全局变量:%p\n",&const_global);
printf(" 常静态变量:%p\n",&const_static);
printf(" 字面值常量:%p\n",string);
printf("----------------------------------\n");
return 0;
}
tarena@TNV:day02$ vim map.c
tarena@TNV:day02$ gcc map.c -o map
tarena@TNV:day02$ ./map
------------参数环境变量----------命令行参数和环境变量
命令行参数:0x7ffcc89a83c8
环境变量:0x7ffcc89a83d8
----------------栈区--------------非静态局部变量
常局部变量:0x7ffcc89a82c0
局部变量:0x7ffcc89a82c4
----------------堆区--------------动态内存分配
堆变量:0x86e010
-----------------BSS区------------未初始化的全局和静态局部变量
未初始化全局变量:0x601060
未初始化静态变量:0x60105c
----------------数据区------------不具常属性且被初始化的全局和静态局部变量
初始化全局变量:0x601050
初始化静态变量:0x601054
----------------代码区------------可执行指令、字面值常量、具有常属性且被初始化的全局和静态局部变量
函数:0x400626
常全局变量:0x400868
常静态变量:0x400c54
字面值常量:0x40086c
----------------------------------
2.5 内存壁垒
- 每个进程的用户空间都是0 ~ 3G-1,但它们所对应的物理内存却是各自独立的,系统为每个进程的用户空间维护一张专属于该进程的内存映射表,记录虚拟内存地址到物理内存的对应关系,因此在不同进程之间交换虚拟内存地址是毫无意义的
- 所有进程的内核空间都是3G ~ 4G-1,它们所对应的物理内存只有一份,系统为所有进程的内核空间维护一张映射表init_mn.pgd,记录虚拟内存到物理内存的对应关系,因此不同进程通过系统调用所访问的内核代码和数据是同一份
- 用户空间的内存映射表会随着进程的切换而切换,内核空间的内存映射表则无需随着进程的切换而切换
2.6 段错误
- 一切对虚拟内存的越权访问,都会导致段错误
– 试图访问没有映射到物理内存的虚拟地址
– 视图以非法方式访问虚拟内存,如对只读内存做写操作等。
3. 内存管理
- 从底层硬件到上层应用,各层都提供了各自的内存管理接口,身处不同的开发层次,会使用不同层次的功能函数。
tarena@TNV:sys$ pwd
/usr/include/x86_64-linux-gnu/sys
tarena@TNV:sys$ ls
acct.h epoll.h gmon.h klog.h perm.h queue.h sem.h soundcard.h sysctl.h timerfd.h uio.h vfs.h
auxv.h errno.h gmon_out.h mman.h personality.h quota.h sendfile.h statfs.h sysinfo.h times.h ultrasound.h vlimit.h
bitypes.h eventfd.h inotify.h mount.h poll.h raw.h shm.h stat.h syslog.h timex.h un.h vm86.h
cdefs.h fanotify.h ioctl.h msg.h prctl.h reboot.h signalfd.h statvfs.h sysmacros.h ttychars.h unistd.h vt.h
debugreg.h fcntl.h io.h mtio.h procfs.h reg.h signal.h stropts.h termios.h ttydefaults.h user.h vtimes.h
dir.h file.h ipc.h param.h profil.h resource.h socket.h swap.h timeb.h types.h ustat.h wait.h
elf.h fsuid.h kd.h pci.h ptrace.h select.h socketvar.h syscall.h time.h ucontext.h utsname.h xattr.h
3.1 内存映射的建立与解除
建立内存映射
- #include <sys/mman.h>
- void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
– 功能:建立虚拟内存到物理内存或磁盘文件的映射;
– 参数:
– start:映射区虚拟内存的起始地址,NULL系统自动选定后返回。
– length:映射区字节数,自动按页圆整。
– prot:映射区操作权限,可取以下值:
---- PROT_READ -映射区可读
---- PROT_WRITE -映射区可写
---- PROT_EXEC -映射区可执行
---- PROT_NONE -映射区不可访问
– 参数:flags:映射标志,可取以下值:
---- MAP_ANONYMOUS -匿名映射,将虚拟内存映射到物理内存而非文件,忽略fd和offset参数。
---- MAP_PRIVATE - 对映射区的写操作只反应到缓冲区中并不会真正写入文件
---- MAP_SHARED - 对映射区的写操作直接反映到文件中
---- MAP_DENYWRITE - 拒绝其它对文件的操作
---- MAP_FIXED - 若在start上无法创建映射,则失败(无此标志系统会自动调整)
---- fd:文件描述符
---- offset:文件偏移量,自动按页(4K)对齐
– 返回值:成功返回映射区虚拟内存的起始地址,失败返回MAP_FAILED(-1)。
内存映射地址 - mmap
//内存映射地址
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main(void){
//内存映射
char* start = (char*)mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if(start == MAP_FAILED){
perror("mmap");
return -1;
}
//使用
strcpy(start, "小鸡顿蘑菇");//写入数据
printf("%s\n", start);//读取数据
//解除限制
if(munmap(start, 8192) == -1){
perror("munmap");
return -1;
}
//printf("%s\n", start);//解除映射后,虚拟地址访问段错误
return 0;
}
tarena@TNV:day03$ vim mmap.c
tarena@TNV:day03$ gcc mmap.c -o mmap
tarena@TNV:day03$ ./mmap
小鸡顿蘑菇
解除内存映射 - munmap
- #include <sys/mman.h>
- int munmap(void* start,size_t length);
– 功能:解除虚拟内存到物理内存或磁盘文件的映射。
– 参数:
– start:映射区虚拟内存的起始地址。
– length:映射区字节数,自动按页调整。
– 返回值:成功返回0,失败返回-1 - munmap允许对映射区的一部分解映射,但必须按页处理
//内存映射地址
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main(void){
//内存映射
char* start = (char*)mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if(start == MAP_FAILED){
perror("mmap");
return -1;
}
//使用
strcpy(start, "小鸡顿蘑菇");//写入数据
printf("%s\n", start);//读取数据
//解除一个页的映射限制
if(munmap(start, 4096) == -1){
perror("munmap");
return -1;
}
//第二个页 可用
char* p2 = start + 4096;
strcpy(p2, "铁锅炖大鹅");
printf("%s\n", p2);
//解除第二个页的映射
if(munmap(p2, 4096) == -1){
perror("munmap");
return -1;
}
return 0;
}
tarena@TNV:day03$ vim mmap1.c
tarena@TNV:day03$ gcc mmap1.c -o mmap1
tarena@TNV:day03$ ./mmap1
小鸡顿蘑菇
铁锅炖大鹅
虚拟内存的分配与释放
-
#include <unistd.h>
-
void* sbrk(intptr_t increment);
– 功能:以相对方式分配和释放虚拟内存
– 参数:increment 堆内存的字节增量(以字节为单位)
– >0 - 分配内存
– <0 - 释放内存
– =0 - 当前堆尾 -
返回值:成功返回调用该函数前的堆尾指针,失败返回-1。
-
系统内部维护一个指针,指向当前堆尾,即堆区最后一个字节的下一个位置,sbrk函数根据增量参数调整该指针的位置,同时返回该指针在调整前的位置,其间若发现内存页耗尽或空闲,则自动追加或取消内存页的映射。
-
-
-
#include <unistd.h>
-
int brk(void* end_data_segment);
– 功能:以绝对方式分配和释放虚拟内存
– 参数:end_data_segment 堆尾指针的目标位置
– > 堆尾指针的原位置 - 分配内存
– < 堆尾指针的原位置 - 释放内存
– = 堆尾指针的原位置 - 空操作 -
返回值:成功返回0,失败返回-1
-
-
-
事实上, sbrk和brk不过是移动堆尾指针的两种不同方法,移动过程中还要
兼顾虚拟内存和物理内存之间映射关系的建立和解除(以页为单位) -
用sbrk分配内存比较方便,用多少内存就传多少增量参数,同时返回指向新分配内存区域的指针,但用sbrk做一次性内存释放比较麻烦,因为必须将所有的既往增量进行累加
-
用brk释放内存比较方便,只需将堆尾指针设回到一开始的位置即可一次性释放掉之前分多次分配的内存,但用brk分配内存比较麻烦,因为必须根据所需要的内存大小计算出堆尾指针的绝对位置
-
用sbrk分多次分配适量内存,最后用brk—次性整体释放
//sbrk函数演示
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("当前堆尾%p\n", sbrk(0));
int* p1 = sbrk(4);
*p1 = 123;
printf("p1 = %p\n", p1);
double* p2 = sbrk(8);
*p2 = 5.12;
printf("p2 = %p\n", p2);
printf("%d %lg", *p1, *p2);
sbrk(-(4 + 8));
printf("当前堆尾%p\n", sbrk(0));
return 0;
}
tarena@TNV:day03$ vim sbrk.c
tarena@TNV:day03$ gcc sbrk.c -o sbrk
tarena@TNV:day03$ ./sbrk
当前堆尾0x932000
p1 = 0x953000
p2 = 0x953004
123 5.12当前堆尾0x953000
tarena@TNV:day03$ cat sbrk.c
//brk函数演示
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("当前堆尾:%p\n", sbrk(0));
int* p1 = sbrk(0);
brk(p1 + 1);
printf("p1 = %p\n", p1);
*p1 = 123;
double* p2 = sbrk(0);
brk(p2 + 1);
printf("p2 = %p\n", p2);
*p2 = 4.56;
printf("%d %lg\n", *p1, *p2);
brk(p1);
printf("当前堆尾:%p\n", sbrk(0));
return 0;
}
tarena@TNV:day03$ vi brk.c
tarena@TNV:day03$ gcc brk.c -o brk
tarena@TNV:day03$ ./brk
当前堆尾:0x677000
p1 = 0x698000
p2 = 0x698004
123 4.56
当前堆尾:0x698000
内存管理
- mmap/munmap底层不维护任何东西,只是返回一个首地址,所分配内存位于堆中
- brk/sbrk底层维护一个指针,记录所分配的内存结尾,所分配内存位于堆中,底层调用mmap/munmap
- malloc底层维护一个线性链表和必要的控制信息,不可越界访问,所分配内存位于堆中,底层调用brk/sbrk
- 每个进程都有4G的虚拟内存空间,虚拟内存地址只是一个数字,在与实际物理内存建立映射之前是不能访问的
- 所谓内存分配与释放,其本质就是建立或解除从虚拟内存到物理内存的映射,并在底层维护不同形式的数据结构,以把虚拟内存的占用与空闲情况记录下来
系统调用
- Unix/Linux系统的大部分功能都是通过系统调用实现的,如open、close等
- Unix/Linux的系统调用已被封装成C函数的形式,但它们并不是C语言标准库的一部分
- 标准库函数大部分时间运行在用户态,但部分函数偶尔也会调用系统调用,进入内核态,如malloc、free等
- 程序员自己编写的代码也可以跳过标准库,直接使用系统调用,如brk、sbrk、mmap和munmap等,与操作系统内核交互,进入内核态
- 系统调用在内核中实现,其外部接口定义在C库中,该接口的实现借助软中断进入内核
- 从应用程序到操作系统内核需要经历如下调用链
tarena@TNV:day05$ touch a.txt
tarena@TNV:day05$ echo 铁锅炖大鹅 >> a.txt
tarena@TNV:day05$ cat a.txt
铁锅炖大鹅
tarena@TNV:day05$ ls -l
总用量 4
-rw-rw-r-- 1 tarena tarena 16 7月 30 16:53 a.txt
tarena@TNV:day05$ ls -l -i
总用量 4
1855138 -rw-rw-r-- 1 tarena tarena 16 7月 30 16:53 a.txt
tarena@TNV:day05$ ln a.txt b.txt
tarena@TNV:day05$ ls -l -i
总用量 8
1855138 -rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 a.txt
1855138 -rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 b.txt
tarena@TNV:day05$ ln -s a.txt xyz.txt
tarena@TNV:day05$ ls
a.txt b.txt xyz.txt
tarena@TNV:day05$ ls -l -i
总用量 8
1855138 -rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 a.txt
1855138 -rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 b.txt
1855128 lrwxrwxrwx 1 tarena tarena 5 7月 30 16:55 xyz.txt -> a.txt
软链接 文件的一种
1855128 lrwxrwxrwx 1 tarena tarena 5 7月 30 16:55 xyz.txt -> a.txt
硬链接 名号对应关系
1855138 -rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 a.txt
1855138 -rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 b.txt
//文件的打开和关闭
#include <stdio.h>
#include <unistd.h>//close()
#include <fcntl.h>//open()
int main(void){
//打开文件
int fd = open("./open.txt",O_RDWR | O_CREAT | O_TRUNC, 0777);
if(fd == -1){
perror("open");
return -1;
}
printf("fd = %d\n", fd);
//关闭文件
if(close(fd) == -1){
perror("close");
return -1;
}
return 0;
}
tarena@TNV:day05$ ls
a.txt b.txt open open.c xyz.txt
tarena@TNV:day05$ gcc open.c -o open
tarena@TNV:day05$ ./open
fd = 3
tarena@TNV:day05$ ls
a.txt b.txt open open.c open.txt xyz.txt
tarena@TNV:day05$ ls -l
总用量 24
-rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 a.txt
-rw-rw-r-- 2 tarena tarena 16 7月 30 16:53 b.txt
-rwxrwxr-x 1 tarena tarena 8760 7月 30 19:08 open
-rw-rw-r-- 1 tarena tarena 361 7月 30 18:36 open.c
-rwxrwxr-x 1 tarena tarena 0 7月 30 19:09 open.txt
lrwxrwxrwx 1 tarena tarena 5 7月 30 16:55 xyz.txt -> a.txt
tarena@TNV:day05$ umask
0002
tarena@TNV:day05$ umask 0000
tarena@TNV:day05$ umask
0000
4. 文件管理和文件系统
4.1 文件系统的物理结构和逻辑结构
- 硬盘的物理结构
– 驱动臂
– 盘片
– 主轴
– 磁头
- 磁盘的读写原理
- 磁道和扇区
- 柱面、柱面组、分区和磁盘驱动器
- 文件系统的逻辑结构
- 一个磁盘驱动器被划分成一到多个分区,其中每个分区上都建有独立的文件系统,每个文件系统包括
- i节点,即索引节点(index node,inode)
- 数据块(data block),512/1024/4096字节
- 通过i节点索引数据块
- 文件访问流程
4.2 文件类型
- 普通文件
- 目录文件
- 符号链接文件
- 特殊文件
- 普通ls -l命令可以查看文件的类型
4.3 文件的打开与关闭
- 打开创建文件
- 关闭文件
4.4 文件的内核结构
4.5 文件描述符
4.6. 文件读写
-
#include <unistd.h>
-
ssize_t write(int fd, void const* buf, size_t count);
– 功能:向指定的文件写入数据
– 参数:
– fd 文件描述符
– buf 内存缓冲区,即要写入的数据
– count 期望写入的字节数 -
返回值:成功返回实际写入的字节数,失败返回-1.
-
redir.c
//重定向
#include <stdio.h>
#include <fcntl.h> //open
#include <unistd.h> //close
int main(void){
close(STDOUT_FILENO);
int fd = open("./redir.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(fd == -1){
perror("open");
return -1;
}
printf("fd = %d\n", fd);
return 0;
}
tarena@TNV:day06$ vi redir.c
tarena@TNV:day06$ gcc redir.c -o redir
tarena@TNV:day06$ ./redir
tarena@TNV:day06$ ls
redir redir.c redir.txt
tarena@TNV:day06$ cat redir.txt
fd = 1
- redir.c
//重定向
#include <stdio.h>
#include <fcntl.h> //open
#include <unistd.h> //close
int main(void){
close(STDOUT_FILENO);//关1
int fd = open("./redir.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(fd == -1){
perror("open");
return -1;
}
printf("fd = %d\n", fd);
close(STDIN_FILENO);//关0
int fd1 = open("./in.txt", O_RDONLY);
if(fd1 == -1){
perror("open");
return -1;
}
int a,b;
scanf("%d%d", &a, &b);
printf("%d + %d = %d", a, b, a + b);
return 0;
}
tarena@TNV:day06$ vi in.txt
tarena@TNV:day06$ cat in.txt
123 789
tarena@TNV:day06$ vi redir.c
tarena@TNV:day06$ gcc redir.c -o redir
tarena@TNV:day06$ ./redir
tarena@TNV:day06$ cat redir.txt
fd = 1
123 + 789 = 912
- write.c
//向文件中写入数据
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(void){
//打开文件
int fd = open("./shared.txt", O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
//写入数据
char* buf = "hello world";
ssize_t size = write(fd, buf, strlen(buf));
if(size == -1){
perror("write");
return -1;
}
printf("实际写入的字节数为:%ld\n", size);
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day06$ ls
in.txt redir redir.c redir.txt write.c
tarena@TNV:day06$ vi write.c
tarena@TNV:day06$ gcc write.c -o write
tarena@TNV:day06$ ./write
实际写入的字节数为:11
tarena@TNV:day06$ ls
in.txt redir redir.c redir.txt shared.txt write write.c
- #include <unistd.h>
- ssize_t read(int fd, void* buf, size_t count);
– 功能:向指定的文件读取数据
– 参数:
– fd 文件描述符
– buf 内存缓冲区,存读取到的数据
– count 期望读取的字节数 - 返回值:成功返回实际读取的字节数,失败返回-1.
- read.c
//读取文件
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void){
//打开文件
int fd = open("./shared.txt", O_RDONLY);
if(fd == -1){
perror("open");
return -1;
}
//读取文件
char buf[128];
ssize_t size = read(fd, buf, sizeof(buf));
if(size == -1){
perror("read");
return -1;
}
printf("%s\n", buf);
printf("实际写入的字节数%ld\n", size);
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day06$ vim read.c
tarena@TNV:day06$ gcc read.c -o read
tarena@TNV:day06$ ./read
hello world
实际写入的字节数11
tarena@TNV:day06$ gcc write.c -o write
tarena@TNV:day06$ gcc read.c -o read
tarena@TNV:day06$ ./write
实际写入的字节数为:11
tarena@TNV:day06$ ./read
hello world
实际写入的字节数11
4.7 顺序与随机读写
- #include <unistd.h>
- off_t lseek(int fd, off_t offset, int whence);
– 功能:人为调整文件读写位置
– 参数:
– fd:文件描述符
– offset:文件读写位置偏移字节数
– whence:offset参数的偏移起点,可以如下取值:
– SEEK_SET - 从头文件(首字节)开始
– SEEK_CUR - 从当前位置(最后被读写字节的下一个字节)开始
– SEEK_END - 从文件尾(最后一个字节的下一个字节)开始 - 返回值:成功返回调整后的文件读写位置,失败返回-1
- lseek函数的功能仅仅是修改保存在文件表项中的文件读写位置,并不实际引发任何I/O动作
– lseek(fd, -7, SEEK_CUR);//从当前位置向文件头偏移7字节
– lseek(fd, 0, SEEK_CUR);//返回当前文件读写位置
– lseek(fd, 0, SEEK_END);//返回文件总字节数 - 可以通过lseek函数将文件读写位置设置到文件尾之后,在超过文件尾的位置上写入数据,将在文件中形成空洞,位于文件中但没有被写过的字节都被设为0,文件空洞不占用磁盘空间,但被计算在文件大小之内
文件读写位置-本质-整数
含义:相对于文件首的偏移量
作用:文件读写位置决定了从哪往哪读
文件读写位置随着读写操作同步变化
- lseek.c
//文件读写位置
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(void){
//打开文件
int fd = open("./lseek.txt", O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
//向文件中写入数据 Hello World! 12
char* buf = "hello world";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//修改文件读写位置
if(lseek(fd,-6,SEEK_END) == -1){
perror("lseek");
return -1;
}
//再次向文件中写入数据 linux!
buf = "linux!";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day06$ vi lseek.c
tarena@TNV:day06$ gcc lseek.c -o lseek
tarena@TNV:day06$ ./lseek
tarena@TNV:day06$ ls
in.txt lseek.c read read.i redir.c shared.txt write.c
lseek lseek.txt read.c redir redir.txt write
tarena@TNV:day06$ cat lseek.txt
hellolinux!
//文件读写位置
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(void){
//打开文件
int fd = open("./lseek.txt", O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
//向文件中写入数据 Hello World! 12
char* buf = "hello world!";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//修改文件读写位置
if(lseek(fd,-6,SEEK_END) == -1){
perror("lseek");
return -1;
}
//再次向文件中写入数据 linux!
buf = "linux!";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//再次修改文件读写位置
if(lseek(fd, 8, SEEK_END) == -1){
perror("lseek");
return -1;
}
//第三次写入数据
buf = "铁锅炖大鹅";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day06$ vi lseek.c
tarena@TNV:day06$ gcc lseek.c -o lseek
tarena@TNV:day06$ ./lseek
tarena@TNV:day06$ cat lseek.txt
hello linux!铁锅炖大鹅
- len.c
//sizeof和strlen
#include <stdio.h>
#include <string.h>
int main(void){
char buf[16] = "abc";
printf("strlen(buf) = %ld\n", strlen(buf));//write 串的大小
printf("sizeof(buf) = %ld\n", sizeof(buf));//read 存储区的大小
return 0;
}
tarena@TNV:day06$ vi len.c
tarena@TNV:day06$ gcc len.c -o len
tarena@TNV:day06$ ./len
strlen(buf) = 3
sizeof(buf) = 16
4.8 系统I/O与标准I/O
-
当系统调用函数被执行时,需要在用户态和内核态之间来回切换,因此频繁执行系统调用函数会严重影响性能
-
标准库作恶必要的优化,内部维护一个缓冲区,只在满足特定条件时才将缓冲区与系统内核同步,借此降低执行系统调用的频率,减少进程在用户态和核态之间来回切换的次数,提高运行性能
-
stdio.c
//标准库IO
#include <stdio.h>
int main(void){
FILE* fp = fopen("./stdio.txt", "w");
if(fp == NULL){
perror("fopen");
return -1;
}
for(int i = 0; i < 1000000; i++){
fwrite(&i, sizeof(i), 1, fp);
}
fclose(fp);
return 0;
}
- sysio.c
//系统IO
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(void){
int fd = open("./sysio.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(fd == -1){
perror("open");
return -1;
}
for(int i = 0; i < 1000000; i++){
write(fd, &i, sizeof(i));
}
close(fd);
return 0;
}
tarena@TNV:day06$ vi stdio.c
tarena@TNV:day06$ vi sysio.c
tarena@TNV:day06$ gcc stdio.c -o stdio
tarena@TNV:day06$ gcc sysio.c -o sysio
tarena@TNV:day06$ ls
in.txt lseek read redir shared.txt stdio.txt sysio.txt
len lseek.c read.c redir.c stdio sysio write
len.c lseek.txt read.i redir.txt stdio.c sysio.c write.c
tarena@TNV:day06$ time ./stdio
real 0m0.022s
user 0m0.015s
sys 0m0.008s
tarena@TNV:day06$ time ./sysio
real 0m0.954s
user 0m0.100s
sys 0m0.853s
4.9 文件描述符的复制
-
#include <unistd.h>
-
int dup(int oldfd);
– 功能:复制文件描述符的特定条目到最小可用项
– 参数:oldfd:源文件描述符
– 返回值:成功返回目标文件描述符,失败返回-1 -
dup函数将oldfd参数所对应的文件描述符表项复制到文件描述符第一个空闲项中,同时返回该表项对应的文件描述符。dup函数返回的文件描述符一定是调用进程当前未使用的最小文件描述符。
-
dup函数只复制文件描述符表项,不复制文件表项和v节点,因此该函数所返回的文件描述符可以看做是参数文件描述符oldfd的副本,它们标识同一个文件表项
-
-
注意,当关闭文件时,即使是由dup函数产生的文件描述符副本,也应该通过close函数关闭,因为只有当关联于一个文件表项的所有文件描述符都被关闭了,该文件表项才会被销毁,类似的,也只有当关联于一个v节点的所有文件表项都被销毁了,v节点才会被从内存中删除,因此资源合理利用的角度讲,凡是明确不再继续使用的文件描述符,都应该尽可能及时地用close函数关闭
-
dup函数究竟会把oldfd参数所对应的文件描述表项,复制到文件描述符表的什么位置,程序员是无法控制的,这完全由调用该函数时文件描述符表的使用情况决定,因此对该函数的返回值做任何约束性假设都是不严谨的
-
由dup函数返回的文件描述符与作为参数传递给该函数的文件描述符标志的是同一个文件表项,而文件读写位置是保存在文件表项而非文件描述符表项中的,因此通过这些文件描述符中的任何一个,对文件进行读写或随机访问,都会影响通过其它文件描述符操作的文件读写位置。这与多次通过open函数打开同一个文件不同
-
-
#include <unistd.h>
-
int dup2(int oldfd, int newfd);
– 功能:复制文件描述符的特定条目到指定项
– 参数:
– oldfd:源文件描述符
– newfd:目标文件描述符
返回值:成功返回目标文件描述符(newfd),失败返回-1
– dup2函数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,若空闲则直接将前者复制给后者,否则会先将目标文件描述符newfd关闭,使之成为空闲项,再行复制。 -
dup.c
//文件描述符的复制
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(void){
//打开文件 得到文件描述符oldfd
int oldfd = open("./dup.txt",O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(oldfd == -1){
perror("open");
return -1;
}
printf("oldfd = %d\n", oldfd);
//复制文件描述符oldfd --> newfd
int newfd = dup(oldfd);
if(newfd == -1){
perror("dup");
return -1;
}
printf("newfd = %d\n", newfd);
//通过oldfd向文件中写入数据 hello world!
char* buf = "hello world!";
if(write(oldfd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//通过newfd修改文件读写位置
if(lseek(newfd, -6, SEEK_END) == -1){
perror("lseek");
return -1;
}
//通过oldfd再次向文件写入数据 linux!
buf = "linux!";
if(write(oldfd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//关闭文件
close(oldfd);
close(newfd);
return 0;
}
tarena@TNV:day06$ vim dup.c
tarena@TNV:day06$ gcc dup.c -o dup
tarena@TNV:day06$ ./dup
oldfd = 3
newfd = 4
tarena@TNV:day06$ ls
dup in.txt lseek read redir shared.txt stdio.txt sysio.txt
dup.c len lseek.c read.c redir.c stdio sysio write
dup.txt len.c lseek.txt read.i redir.txt stdio.c sysio.c write.c
tarena@TNV:day06$ cat dup.txt
hello linux!
//文件描述符的复制
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(void){
//打开文件 得到文件描述符oldfd
int oldfd = open("./dup.txt",O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(oldfd == -1){
perror("open");
return -1;
}
printf("oldfd = %d\n", oldfd);
//复制文件描述符oldfd --> newfd
//int newfd = dup(oldfd);
int newfd = dup2(oldfd, 1);
if(newfd == -1){
perror("dup");
return -1;
}
printf("newfd = %d\n", newfd);
//通过oldfd向文件中写入数据 hello world!
char* buf = "hello world!";
if(write(oldfd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//通过newfd修改文件读写位置
if(lseek(newfd, -6, SEEK_END) == -1){
perror("lseek");
return -1;
}
//通过oldfd再次向文件写入数据 linux!
buf = "linux!";
if(write(oldfd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//关闭文件
close(oldfd);
close(newfd);
return 0;
}
tarena@TNV:day06$ vim dup.c
tarena@TNV:day06$ gcc dup.c -o dup
tarena@TNV:day06$ ./dup
oldfd = 3
tarena@TNV:day06$ cat dup.txt
newfd = 1
hello linux!
- conf.c
//写冲突
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]){
// ./a.out hello
if(argc < 2){
fprintf(stderr, "用法:./a.out <字符串>\n");
return -1;
}
//打开文件
int fd = open("./conf.txt", O_WRONLY | O_CREAT | O_APPEND, 0664);
if(fd == -1){
perror("open");
return -1;
}
//写入数据 argv[0] --> "./a.out" argv[1] --> "hello"
for(int i = 0; i < strlen(argv[1]); i++){
write(fd, &argv[1][i], sizeof(argv[1][i]));
sleep(1);
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day06$ vi conf.c
tarena@TNV:day06$ gcc conf.c -o conf
tarena@TNV:day06$ ./conf hello
tarena@TNV:day06$ cat conf.txt
worldtarena@TNV:day06$ cat conf.txt
worhellold
tarena@TNV:day06$ vi conf.c
tarena@TNV:day06$ gcc conf.c -o conf
tarena@TNV:day06$ ./conf world
tarena@TNV:day06$ cat conf.txt
worldtarena@TNV:day06$ cat conf.txt
worhellold
4.10 文件锁
- 读写冲突
– 如果两个或两个以上的进程同时向一个文件的某个特定区域写入数据,那么最后写入文件的数据极有可能因为写操作的交错而产生混乱 - 如果一个进程写而其它进程同时在读一个文件的某个特定区域,那么读出的数据极有可能因为读写操作的交错而不完整
- 多个进程同时读一个文件的某个特定区域,不会有任何问题,它们只是各自把文件中的数据拷贝到各自的缓冲区中,并不会改变文件的内容,相互之间也就不会冲突
- 由此可以得出结论,为了避免在读写同一个文件的同一个区域时发生冲突,进程之间应该遵循以下规则,如果一个进程正在写,那么其它进程既不能写也不能读,如果一个进程正在读,那么其它进程不能写但是可以读
- 文件锁
– 为了避免多个进程在读写同一个文件的同一个区域时发生冲突,Unix/Linux系统引入了文件锁机制,并把文件锁分为读锁和写锁两种,它们的区别在于,对一个文件的特定区域可以加多把锁,对一个文件的特定区域只能加一把写锁
– 基于锁的操作模型是:读/写文件中的特定区域之前,先加上锁/写锁,锁成功了再读/写,读/写完成以后再解锁 - 假设进程A期望访问某文件的A区,同时进程B期望访问该文件的B区,而A区和B区存在部分重叠,分情况讨论
– 第一种情况:进程A正在写,进程B也想写 - 第二种情况:进程A正在写,进程B却想读
- 第三种情况:进程A正在读,进程B却想写
- 第四种情况:进程A正在读,进程B却想读
- #include <fcntl.h>
- int fcntl(int fd, F_SETLK/F_SETLKW, struct flock* lock);
– 功能:加解锁
– 参数:
– F_SETLK:非阻塞模式加锁
– F_SETLKW:阻塞模式加锁
– lock:对文件要加的锁
– 返回值:成功返回0,失败返回-1
struct flock{
short l_type;//加锁型:F_RDLCK/F_WRLCK/F_UNLCK
short l_whence;//锁区偏移起点:SEEK_SET/SEEK_CUR/SEEK_END
off_t l_start;//锁区偏移字节数
off_t l_len;//锁区字节数
pid_t l_pid;//加锁进程的PID,-1表示自动设置
};
通过对该结构体类型变量的赋值,再配合fcntl函数,以完成对文件指定区域的加解锁操作
- 几点说明
– 当通过close函数关闭文件描述符时,调用进程在该文件描述符上所加的一切锁将被自动解除;
– 当进程终止时,该进程在所有文件描述符上所加的一切锁将被自动解除;
– 文件锁仅在不同进程之间起作用,同一进程的不同线程不能当做文件解决读写冲突问题;
– 通过fork/vfork函数创建的子进程,不继承父进程所加的任何文件锁;
– 通过exec函数创建的新进程,会继承原进程所加的全部文件锁,除非某文件描述符带有FD_CLOEXEC标志。 - 从前述基于锁的操作模型可以看出,锁机制之所以能够避免读写冲突,关键在于参与读写的多个进程都是按照一套模式----先加锁,再读写,最后解锁-----按部就班也执行,这就形成了一套协议,只要参与者无一例外地遵循这套协议,读写就是安全的。反之,如果哪个进程不遵守这套协议,完全无视锁的存在,想读就读,想写就写,即便有锁,对它也起不到任何约束作用。因此,这样的锁机制被称为劝谏锁或协议锁
- wlock.c 阻塞方式加锁
//文件锁
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]){
// ./a.out hello
if(argc < 2){
fprintf(stderr, "用法:./a.out <字符串>\n");
return -1;
}
//打开文件
int fd = open("./wlock.txt", O_WRONLY | O_CREAT | O_APPEND, 0664);
if(fd == -1){
perror("open");
return -1;
}
//加锁
struct flock lock;//锁信息
lock.l_type = F_WRLCK;//写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;//一直锁到文件尾
lock.l_pid = -1;
//阻塞方式加锁
if(fcntl(fd, F_SETLKW, &lock) == -1){
perror("fcntl");
return -1;
}
//写入数据 argv[0] --> "./a.out" argv[1] --> "hello"
for(int i = 0; i < strlen(argv[1]); i++){
write(fd, &argv[1][i], sizeof(argv[1][i]));
sleep(1);
}
//解锁
struct flock unlock;
unlock.l_type = F_UNLCK;//解锁
unlock.l_whence = SEEK_SET;
unlock.l_start = 0;
unlock.l_len = 0;
unlock.l_pid = -1;
if(fcntl(fd, F_SETLK, &unlock) == -1){
perror("fcntl");
return -1;
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day06$ ./wlock abcdefg
tarena@TNV:day06$ cat wlock.txt
123456abcdefg
tarena@TNV:day06$ ./wlock 123456
tarena@TNV:day06$ cat wlock.txt
123456abcdefg
- wlock.c 非阻塞方式加锁
//文件锁
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char* argv[]){
// ./a.out hello
if(argc < 2){
fprintf(stderr, "用法:./a.out <字符串>\n");
return -1;
}
//打开文件
int fd = open("./wlock.txt", O_WRONLY | O_CREAT | O_APPEND, 0664);
if(fd == -1){
perror("open");
return -1;
}
//加锁
struct flock lock;//锁信息
lock.l_type = F_WRLCK;//写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;//一直锁到文件尾
lock.l_pid = -1;
//阻塞方式加锁
/*
if(fcntl(fd, F_SETLKW, &lock) == -1){
perror("fcntl");
return -1;
}
*/
//非阻塞方式加锁
while(fcntl(fd, F_SETLK, &lock) == -1){
if(errno == EACCES || errno == EAGAIN){
printf("文件被锁定,加不上锁,不等了,干点别的去\n");
sleep(1);
}else{
perror("fcntl");
return -1;
}
}
//写入数据 argv[0] --> "./a.out" argv[1] --> "hello"
for(int i = 0; i < strlen(argv[1]); i++){
write(fd, &argv[1][i], sizeof(argv[1][i]));
sleep(1);
}
//解锁
struct flock unlock;
unlock.l_type = F_UNLCK;//解锁
unlock.l_whence = SEEK_SET;
unlock.l_start = 0;
unlock.l_len = 0;
unlock.l_pid = -1;
if(fcntl(fd, F_SETLK, &unlock) == -1){
perror("fcntl");
return -1;
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day06$ ./wlock hello
文件被锁定,加不上锁,不等了,干点别的去
文件被锁定,加不上锁,不等了,干点别的去
文件被锁定,加不上锁,不等了,干点别的去
文件被锁定,加不上锁,不等了,干点别的去
文件被锁定,加不上锁,不等了,干点别的去
tarena@TNV:day06$ cat wlock.txt
123456abcdefg
worldhello
tarena@TNV:day06$ vi wlock.c
tarena@TNV:day06$ gcc wlock.c -o wlock
tarena@TNV:day06$ ./wlock world
4.9 文件锁的内核结构
- 每次对给定文件的特定区域加锁,都会通过fcntl函数向系统内核传递flock结构体,该结构体中包含了有关锁的一切细节,诸如锁的类型(读锁/写锁),锁区的起始位置和大小,甚至加锁进程的PID(填-1由系统自动设置)
- 系统内核会手机所有进程对该文件所加的各种锁,并把这些flock结构体中的信息,以链表的形式组织称一张锁表,而锁表的起始地址就保存在该文件的v节点中
- 任何一个进程通过fcntl函数对该文件加锁,系统内核都要遍历这张锁表,一旦发现有与欲加之锁构成冲突的锁即阻塞或报错,否则即将欲加之锁插入锁表,而解锁的过程实际上就是调整或删除锁表中的相应节点
4.10 访问测试
-
#include <unistd.h>
-
int access(char const* pathname, int mode);
– 功能:判断当前进程是否可以对某个给定的文件执行某种访问。
– 参数:
– pathname 文件路径
– mode 被测试权限,可以以下取值
– R_OK - 可读否
– W_OK - 可写否
– X_OK - 可执行否
– F_OK - 存在否
– 返回值:成功返回0,失败返回-1 -
access.c
//access函数演示
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]){
// ./a.out hello.c
// hello.c 可读,可写,可执行
// hello.c 不存在
printf("文件:%s", argv[1]);
if(access(argv[1], F_OK) == -1){
//不存在
printf("不存在\n");
}else{
//存在
if(access(argv[1], R_OK) == -1){
printf("不可读,");
}else{
printf("可读,");
}
if(access(argv[1], W_OK) == -1){
printf("不可写,");
}else{
printf("可写,");
}
if(access(argv[1], X_OK) == -1){
printf("不可执行\n");
}else{
printf("可执行\n");
}
}
return 0;
}
tarena@TNV:day07$ vi access.c
tarena@TNV:day07$ gcc access.c -o access
tarena@TNV:day07$ ./access hello.c
文件:hello.c不存在
tarena@TNV:day07$ ./access access.c
文件:access.c可读,可写,不可执行
tarena@TNV:day07$ ls -l
总用量 16
-rwxrwxr-x 1 tarena tarena 8712 8月 1 16:22 access
-rw-rw-r-- 1 tarena tarena 622 8月 1 16:21 access.c
4.11 修改文件大小
-
#include <unistd.h>
-
int truncate(char const* path, off_t length);
-
int ftruncate(int fd, off_t length);
– 功能:修改指定文件的大小
– 参数:
– path 文件路径
– length 文件大小
– fd 文件描述符
– 返回值:成功返回0,失败返回-1 -
该函数既可以把文件截短,也可以把文件加长,所有的改变均发生在文件的尾部,新增加的部分用数字0填充。
-
trunc.c
//修改文件的大小
#include <stdio.h>
#include <string.h>
#include <unistd.h> //close read write lseek
#include <fcntl.h> //open
int main(void){
//打开文件 trunc.txt
int fd = open("./trunc.txt", O_CREAT | O_TRUNC | O_WRONLY, 0664);
if(fd == -1){
perror("open");
return -1;
}
//写入数据 abcde
char* buf = "abcde";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//修改文件大小为3字节
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day07$ vi trunc.c
tarena@TNV:day07$ gcc trunc.c -o trunc
tarena@TNV:day07$ ./trunc
tarena@TNV:day07$ ls -l
总用量 36
-rwxrwxr-x 1 tarena tarena 8712 8月 1 16:22 access
-rw-rw-r-- 1 tarena tarena 622 8月 1 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8808 8月 1 16:39 trunc
-rw-rw-r-- 1 tarena tarena 488 8月 1 16:39 trunc.c
-rw-rw-r-- 1 tarena tarena 5 8月 1 16:39 trunc.txt
tarena@TNV:day07$ cat trunc.txt
abcdetarena@TNV:day07$
tarena@TNV:day07$ vi trunc.txt
tarena@TNV:day07$ cat trunc.txt
abcde
tarena@TNV:day07$ ls -l
总用量 36
-rwxrwxr-x 1 tarena tarena 8712 8月 1 16:22 access
-rw-rw-r-- 1 tarena tarena 622 8月 1 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8808 8月 1 16:39 trunc
-rw-rw-r-- 1 tarena tarena 488 8月 1 16:42 trunc.c
-rw-rw-r-- 1 tarena tarena 6 8月 1 16:42 trunc.txt
- trunc.c
//修改文件的大小
#include <stdio.h>
#include <string.h>
#include <unistd.h> //close read write lseek
#include <fcntl.h> //open
int main(void){
//打开文件 trunc.txt
int fd = open("./trunc.txt", O_CREAT | O_TRUNC | O_WRONLY, 0664);
if(fd == -1){
perror("open");
return -1;
}
//写入数据 abcde
char* buf = "abcde";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//修改文件大小为3字节
if(truncate("./trunc.txt", 3) == -1){
perror("truncate");
return -1;
}
//修改文件大小为8字节
if(ftruncate(fd, 8) == -1){
perror("ftruncate");
return -1;
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day07$ vi trunc.c
tarena@TNV:day07$ gcc trunc.c -o trunc
tarena@TNV:day07$ ./trunc
tarena@TNV:day07$ ls -l
总用量 36
-rwxrwxr-x 1 tarena tarena 8712 8月 1 16:22 access
-rw-rw-r-- 1 tarena tarena 622 8月 1 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8864 8月 1 16:50 trunc
-rw-rw-r-- 1 tarena tarena 566 8月 1 16:50 trunc.c
-rw-rw-r-- 1 tarena tarena 3 8月 1 16:50 trunc.txt
tarena@TNV:day07$ cat trunc.txt
abctarena@TNV:day07$
tarena@TNV:day07$ vi trunc.c
tarena@TNV:day07$ gcc trunc.c -o trunc
tarena@TNV:day07$ ./trunc
tarena@TNV:day07$ cat trunc.txt
abctarena@TNV:day07$ ls -l
总用量 36
-rwxrwxr-x 1 tarena tarena 8712 8月 1 16:22 access
-rw-rw-r-- 1 tarena tarena 622 8月 1 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8896 8月 1 16:53 trunc
-rw-rw-r-- 1 tarena tarena 635 8月 1 16:53 trunc.c
-rw-rw-r-- 1 tarena tarena 8 8月 1 16:53 trunc.txt
4.12 文件元数据的获取
- #include <sys/stat.h>
- int stat(char const* path, struct stat* buf);
- int fstat(int fd, struct stat* buf);
- int lstat(char const* path, struct stat* buf);
– 功能:从i节点提取文件的元数据,即文件的属性信息
– 参数:
– path 文件路径
– buf 文件元数据结构
– fd 文件描述符
– 返回值:成功返回0,失败返回-1 - lstat()函数与另外两个函数的区别在于它不跟踪符号链接。
– 例:
– abc.txt —> xyz.txt abc.txt文件是xyz.txt文件的符号链接
– stat(“abc.txt”, …); //得到xyz.txt文件的元数据
– lstat(“abc.txt”, …); //得到abc.txt文件的元数据 - stat函数族通过stat结构体,向调用者输出文件的元数据
4.13 文件元数据结构
struct stat{
dev_t st_dev; //设备ID
ino_t st_ino; //i节点号
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; //I/O块字节数
blkcnt_t st_blocks; //存储块数
time_t st_atime; //最后访问时间
time_t st_mtime; //最后修改时间
time_t st_ctime; //最后状态改变时间
}
- stat结构的st_mode成员表示文件的类型和权限,该成员在stat结构中被声明为mode_t类型,其原始类型在32位系统中被定义为unsigned int,即32位无符号整数,但到目前为止,只有其中的低16位有意义
- 用16位二进制(B15 … B0)表示的文件类型和权限,从高到低可被分为五组
– B15 - B12:文件类型
– B11 - B9:设置用户ID,设置组ID,粘滞
– B8 - B6:拥有者用户的读、写和执行权限
– B5 - B3:拥有者组的读、写和执行权限
– B2 - B0:其它用户的读、写和执行权限 - 文件类型:B15 - B12
- 设置用户ID、设置组ID和粘滞:B11 - B9
- 拥有者用户的读、写和执行权限:B8 - B6
- 拥有者用户的读、写和执行权限:B3 - B5
- 其它用户的读、写和执行权限:B2 - B0
- 辅助分析文件类型的实用宏
stat.c
//文件的元数据
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h> // stat()
#include <time.h>
//用来转换类型和权限
char* mtos(mode_t m){
static char s[11]; // -rw-rw-r--
if(S_ISDIR(m)){
strcpy(s, "d");
}else if(S_ISSOCK(m)){
strcpy(s, "s");
}else if(S_ISCHR(m)){
strcpy(s, "c");
}else if(S_ISBLK(m)){
strcpy(s, "b");
}else if(S_ISFIFO(m)){
strcpy(s, "p");
}else if(S_ISLNK(m)){
strcpy(s, "l");
}else{
strcpy(s,"-");
}
strcat(s, m & S_IRUSR ? "r" : "-");
strcat(s, m & S_IWUSR ? "w" : "-");
strcat(s, m & S_IXUSR ? "x" : "-");
strcat(s, m & S_IRGRP ? "r" : "-");
strcat(s, m & S_IWGRP ? "w" : "-");
strcat(s, m & S_IXGRP ? "x" : "-");
strcat(s, m & S_IROTH ? "r" : "-");
strcat(s, m & S_IWOTH ? "w" : "-");
strcat(s, m & S_IXOTH ? "x" : "-");
if(m & S_ISUID){
s[3] = (s[3] == 'x') ? 's' : 'S';
}
if(m & S_ISGID){
s[6] = (s[6] == 'x') ? 's' : 'S';
}
if(m & S_ISVTX){
s[9] = (s[9] == 'x') ? 't' : 'T';
}
return s;
}
//转换时间
char* ttos(time_t t){
static char time[20];
struct tm* l = localtime(&t);
sprintf(time, "%04d-%02d-%02d %02d:%02d:%02d", l->tm_year + 1900
,l->tm_mon + 1, l->tm_mday, l->tm_hour, l->tm_min, l->tm_sec);
return time;
}
int main(int argc, char* argv[]){
// ./a.out hello.c
if(argc < 2){
fprintf(stderr,"用法:./a.out <文件名>\n");
return -1;
}
struct stat buf;//用来输出文件的元数据
if(stat(argv[1], &buf) == -1){
perror("stat");
return -1;
}
printf(" 设备ID:%lu\n", buf.st_dev);
printf(" i节点号:%ld\n", buf.st_ino);
printf(" 类型和权限:%s\n", mtos(buf.st_mode));
printf(" 硬连接束:%lu\n", buf.st_nlink);
printf(" 用户ID:%u\n", buf.st_uid);
printf(" 组ID:%u\n", buf.st_gid);
printf(" 特殊设备ID:%lu\n", buf.st_rdev);
printf(" 总字节数:%ld\n", buf.st_size);
printf(" IO块字节数:%ld\n", buf.st_blksize);
printf(" 存储块数:%ld\n", buf.st_blocks);
printf(" 最后访问时间:%s\n", ttos(buf.st_atime));
printf(" 最后修改时间:%s\n", ttos(buf.st_mtime));
printf(" 最后改变时间:%s\n", ttos(buf.st_ctime));
return 0;
}
tarena@TNV:day07$ vi stat.c
tarena@TNV:day07$ gcc stat.c -o stat
tarena@TNV:day07$ ./stat
用法:./a.out <文件名>
tarena@TNV:day07$ ./stat access.c
设备ID:2049
i节点号:1855183
类型和权限:-rw-rw-r--
硬连接束:1
用户ID:1000
组ID:1000
特殊设备ID:0
总字节数:622
IO块字节数:4096
存储块数:8
最后访问时间:2023-08-01 16:22:04
最后修改时间:2023-08-01 16:21:53
最后改变时间:2023-08-01 16:21:53
tarena@TNV:day07$ ls -l access.c
-rw-rw-r-- 1 tarena tarena 622 8月 1 16:21 access.c
4.14 内存映射文件
- #include <sys/mman.h>
- void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
- 功能:建立虚拟内存到物理内存或磁盘文件的映射。
- 参数:
- start:映射区虚拟内存的起始地址,NULL系统自动选定后返回。
- length:映射区字节数,自动按页圆整。
- prot:映射区操作权限,可取以下值:
4.15 解除内存映射
- #include <sys/mman.h>
- int munmap(void* start, size_t length);
- munmap允许对映射区的一部分解映射,但必须按页处理
- fmap.c
//内存映射文件
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
int main(void){
//打开文件
int fd = open("./fmap.txt", O_RDWR | O_CREAT | O_TRUNC, 0664);
if(fd == -1){
perror("open");
return -1;
}
//内存映射文件
char* start = (char*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(start == MAP_FAILED){
perror("mmap");
return -1;
}
//写入文件
strcpy(start, "abcdefg");//write
//读取文件
printf("%s\n", start);//read
//解除映射关系
if(munmap(start, 4096) == -1){
perror("munmap");
return -1;
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day08$ vi fmap.c
tarena@TNV:day08$ gcc fmap.c -o fmap
tarena@TNV:day08$ ./fmap
总线错误
tarena@TNV:day08$ ls -l
总用量 16
-rwxrwxr-x 1 tarena tarena 8856 8月 2 12:02 fmap
-rw-rw-r-- 1 tarena tarena 660 8月 2 12:03 fmap.c
-rw-rw-r-- 1 tarena tarena 0 8月 2 12:02 fmap.txt
//内存映射文件
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
int main(void){
//打开文件
int fd = open("./fmap.txt", O_RDWR | O_CREAT | O_TRUNC, 0664);
if(fd == -1){
perror("open");
return -1;
}
//修改文件的大小
if(ftruncate(fd,4096) == -1){
perror("ftruncate");
return -1;
}
//内存映射文件
char* start = (char*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(start == MAP_FAILED){
perror("mmap");
return -1;
}
//写入文件
strcpy(start, "abcdefg");//write
//读取文件
printf("%s\n", start);//read
//解除映射关系
if(munmap(start, 4096) == -1){
perror("munmap");
return -1;
}
//关闭文件
close(fd);
return 0;
}
tarena@TNV:day08$ ls -l
总用量 16
-rwxrwxr-x 1 tarena tarena 8856 8月 2 12:02 fmap
-rw-rw-r-- 1 tarena tarena 754 8月 2 12:11 fmap.c
-rw-rw-r-- 1 tarena tarena 0 8月 2 12:02 fmap.txt
tarena@TNV:day08$ vi fmap.c
tarena@TNV:day08$ gcc fmap.c -o fmap
tarena@TNV:day08$ ./fmap
abcdefg
tarena@TNV:day08$ ls -l
总用量 20
-rwxrwxr-x 1 tarena tarena 8912 8月 2 12:12 fmap
-rw-rw-r-- 1 tarena tarena 757 8月 2 12:11 fmap.c
-rw-rw-r-- 1 tarena tarena 4096 8月 2 12:12 fmap.txt
5. 进程管理
5.1 进程的概念
- 进程:是程序执行时的一个实例
– 当程序被存储在磁盘上,包含机器指令和数据的文件
– 当这些指令和数据被装载到内存并被CPU所执行,即形成了进程
– 一个程序可以被同时运行为多个进程
– 在Linux源码中通常将进程称为任务(task)
– 从内核观点看,进程的目的就是担当分配系统资源(CPU时间,内存等)的实体
5.2 相关命令
- pstree 以树状结构显示当前所有进程关系
- ps 以简略方式显示当前用户拥有控制终端的进程信息,也可以配合以下选项
– a - 显示所有用户拥有控制终端的进程信息
– x - 也包括没有控制终端的进程
– u - 以详尽方式显示
– w - 以更大列宽显示 - 进程信息表
– user :进程的用户ID
– PID :进程ID
– %CPU :CPU使用率
– %MEM :内存使用率
– VSZ :占用虚拟内存的大小(KB)
– RSS :占用物理内存的大小(KB)
– TTY :终端次设备号 - 进程信息列表
tarena@TNV:day08$ pstree
systemd─┬─ModemManager─┬─{gdbus}
│ └─{gmain}
├─NetworkManager─┬─dnsmasq
│ ├─{gdbus}
│ └─{gmain}
├─VGAuthService
├─accounts-daemon─┬─{gdbus}
│ └─{gmain}
├─acpid
├─agetty
├─avahi-daemon───avahi-daemon
├─bluetoothd
├─colord─┬─{gdbus}
│ └─{gmain}
├─cron
├─cups-browsed─┬─{gdbus}
│ └─{gmain}
├─cupsd───dbus
├─dbus-daemon
├─fwupd─┬─{GUsbEventThread}
│ ├─{fwupd}
│ ├─{gdbus}
│ └─{gmain}
├─gnome-keyring-d─┬─{gdbus}
│ ├─{gmain}
│ └─{timer}
├─irqbalance
├─lightdm─┬─Xorg───{InputThread}
│ ├─lightdm─┬─upstart─┬─at-spi-bus-laun─┬─dbus-daemon
│ │ │ │ ├─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─at-spi2-registr─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─bamfdaemon─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─compiz─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─dbus-daemon
│ │ │ ├─dconf-service─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─evolution-addre─┬─evolution-addre─┬─{dconf worker}
│ │ │ │ │ ├─{evolution-addre}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─{dconf worker}
│ │ │ │ ├─{evolution-addre}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─evolution-calen─┬─evolution-calen─┬─{dconf worker}
│ │ │ │ │ ├─{evolution-calen}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─evolution-calen─┬─{dconf worker}
│ │ │ │ │ ├─2*[{evolution-calen}]
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ ├─{gmain}
│ │ │ │ │ └─{pool}
│ │ │ │ ├─{dconf worker}
│ │ │ │ ├─{evolution-calen}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─evolution-sourc─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gnome-session-b─┬─deja-dup-monito─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─gnome-software─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─nautilus─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─nm-applet─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─polkit-gnome-au─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─unity-fallback-─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─update-notifier─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─zeitgeist-datah─┬─{gdbus}
│ │ │ │ │ ├─{gmain}
│ │ │ │ │ └─4*[{pool}]
│ │ │ │ ├─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gnome-terminal-─┬─bash───pstree
│ │ │ │ ├─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gpg-agent
│ │ │ ├─gvfs-afc-volume─┬─{gdbus}
│ │ │ │ ├─{gmain}
│ │ │ │ └─{gvfs-afc-volume}
│ │ │ ├─gvfs-goa-volume─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gvfs-gphoto2-vo─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gvfs-mtp-volume─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gvfs-udisks2-vo─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gvfsd─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─gvfsd-fuse─┬─{gdbus}
│ │ │ │ ├─{gmain}
│ │ │ │ ├─{gvfs-fuse-sub}
│ │ │ │ └─2*[{gvfsd-fuse}]
│ │ │ ├─gvfsd-trash─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─hud-service─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─ibus-daemon─┬─ibus-dconf─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─ibus-engine-pin─┬─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─ibus-engine-sim─┬─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─ibus-ui-gtk3─┬─{dconf worker}
│ │ │ │ │ ├─{gdbus}
│ │ │ │ │ └─{gmain}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─ibus-x11─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-appli─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-bluet─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-datet─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ ├─{gmain}
│ │ │ │ ├─{indicator-datet}
│ │ │ │ └─{pool}
│ │ │ ├─indicator-keybo─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-messa─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-power─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-print─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-sessi─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─indicator-sound─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─pulseaudio─┬─{alsa-sink-ES137}
│ │ │ │ ├─{alsa-source-ES1}
│ │ │ │ └─{snapd-glib}
│ │ │ ├─sh───zeitgeist-daemo─┬─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─unity-panel-ser─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─unity-settings-─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─2*[upstart-dbus-br]
│ │ │ ├─upstart-file-br
│ │ │ ├─upstart-udev-br
│ │ │ ├─vmtoolsd─┬─{dconf worker}
│ │ │ │ ├─{gdbus}
│ │ │ │ └─{gmain}
│ │ │ ├─window-stack-br
│ │ │ └─zeitgeist-fts─┬─{gdbus}
│ │ │ └─{gmain}
│ │ ├─{gdbus}
│ │ └─{gmain}
│ ├─{gdbus}
│ └─{gmain}
├─mysqld───27*[{mysqld}]
├─polkitd─┬─{gdbus}
│ └─{gmain}
├─redis-server─┬─{bio_aof_fsync}
│ ├─{bio_close_file}
│ ├─{bio_lazy_free}
│ └─{jemalloc_bg_thd}
├─rsyslogd─┬─{in:imklog}
│ ├─{in:imuxsock}
│ └─{rs:main Q:Reg}
├─rtkit-daemon───2*[{rtkit-daemon}]
├─sshd
├─systemd───(sd-pam)
├─systemd-journal
├─systemd-logind
├─systemd-timesyn───{sd-resolve}
├─systemd-udevd
├─udisksd─┬─{cleanup}
│ ├─{gdbus}
│ ├─{gmain}
│ └─{probing-thread}
├─unattended-upgr───{gmain}
├─upowerd─┬─{gdbus}
│ └─{gmain}
├─vmtoolsd───{gmain}
├─vmware-vmblock-───2*[{vmware-vmblock-}]
├─vsftpd
├─whoopsie─┬─{gdbus}
│ └─{gmain}
└─xinetd
tarena@TNV:day08$ top
top - 15:07:58 up 8:23, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 245 total, 1 running, 168 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.1 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4015692 total, 1915552 free, 807052 used, 1293088 buff/cache
KiB Swap: 4191228 total, 4191228 free, 0 used. 2856512 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1100 root 20 0 450108 77916 44548 S 0.3 1.9 0:23.27 Xorg
2602 tarena 20 0 1301604 120316 81168 S 0.3 3.0 0:33.93 compiz
3896 tarena 20 0 43664 3812 3080 R 0.3 0.1 0:00.01 top
1 root 20 0 185292 5748 3824 S 0.0 0.1 0:11.67 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kthreadd
4 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/0:0H
6 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 mm_percpu_wq
7 root 20 0 0 0 0 S 0.0 0.0 0:00.05 ksoftirqd/0
8 root 20 0 0 0 0 I 0.0 0.0 0:09.88 rcu_sched
9 root 20 0 0 0 0 I 0.0 0.0 0:00.00 rcu_bh
10 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
11 root rt 0 0 0 0 S 0.0 0.0 0:00.06 watchdog/0
12 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/0
13 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/1
14 root rt 0 0 0 0 S 0.0 0.0 0:00.06 watchdog/1
15 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/1
16 root 20 0 0 0 0 S 0.0 0.0 0:00.02 ksoftirqd/1
18 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/1:0H
19 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/2
20 root rt 0 0 0 0 S 0.0 0.0 0:00.05 watchdog/2
21 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/2
22 root 20 0 0 0 0 S 0.0 0.0 0:00.05 ksoftirqd/2
24 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 kworker/2:0H
25 root 20 0 0 0 0 S 0.0 0.0 0:00.00 cpuhp/3
26 root rt 0 0 0 0 S 0.0 0.0 0:00.06 watchdog/3
27 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/3
28 root 20 0 0 0 0 S 0.0 0.0 0:00.11 ksoftirqd/3
tarena@TNV:day08$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 185292 5748 ? Ss 06:44 0:11 /sbin/init auto noprompt
root 2 0.0 0.0 0 0 ? S 06:44 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< 06:44 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? I< 06:44 0:00 [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S 06:44 0:00 [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? I 06:44 0:09 [rcu_sched]
root 9 0.0 0.0 0 0 ? I 06:44 0:00 [rcu_bh]
root 10 0.0 0.0 0 0 ? S 06:44 0:00 [migration/0]
root 11 0.0 0.0 0 0 ? S 06:44 0:00 [watchdog/0]
root 12 0.0 0.0 0 0 ? S 06:44 0:00 [cpuhp/0]
root 13 0.0 0.0 0 0 ? S 06:44 0:00 [cpuhp/1]
root 14 0.0 0.0 0 0 ? S 06:44 0:00 [watchdog/1]
root 15 0.0 0.0 0 0 ? S 06:44 0:00 [migration/1]
root 16 0.0 0.0 0 0 ? S 06:44 0:00 [ksoftirqd/1]
root 18 0.0 0.0 0 0 ? I< 06:44 0:00 [kworker/1:0H]
5.3 父子孤尸
-
父子进程
-
Unix系统中的进程存在父子关系。一个父进程可以创建一到多个子进程,但每个子进程有且仅有一个父进程。整个系统中只有一个根进程,即PID为0的调度进程
-
-
孤儿进程
-
父进程创建子进程以后,子进程在操作系统的调度下与其父进程同时运行如果父进程先于子进程终止,子进程即成为孤儿进程,同时被某个专门的进程收养,即成为该进程的子进程,因此该进程又被称为孤儿院进程
-
僵尸进程
-
父进程创建子进程以后,子进程在操作系统的调度下与其父进程同时运行。如果子进程先于父进程终止,但由于某种原因,父进程并没有回收该子进程的终止状态,这时子进程即处于僵尸状态,被称为僵尸进程
-
僵尸进程虽然已不再活动,即不会继续消耗处理机资源,但其所携带的进程终止状态会消耗内存资源。因此,作为程序的设计者,无论对子进程的终止状态是否感兴趣,都应该尽可能及时地回收子进程的僵尸。
5.4 进程标识 PID和PPID
- 每个进程都有一个非负整数形式的唯一编号,即PID(Process ldentification,进程标识)
- PID在任何时刻都是唯一的,但是可以重用,当进程终止并被回收以后,其PID就可以为其它进程所用
- 进程的PID由系统内核根据延迟重用算法生成,以确保新进程的PID不同于最近终止进程的PID
- 系统中有些PID是专用的,比如
- 相关函数
- id.c
//进程的各种ID
#include <stdio.h>
#include <unistd.h>
int main(void){
printf(" 进程的PID:%d\n", getpid());
printf(" 父进程的PID:%d\n", getppid());
printf(" 实际用户ID:%d\n", getuid());
printf(" 有效用户ID:%d\n", geteuid());
printf(" 实际组ID:%d\n", getgid());
printf(" 有效组ID:%d\n", getegid());
return 0;
}
tarena@TNV:day08$ vi id.c
tarena@TNV:day08$ gcc id.c -o id
tarena@TNV:day08$ ./id
进程的PID:3674
父进程的PID:2913
实际用户ID:1000
有效用户ID:1000
实际组ID:1000
有效组ID:1000
5.5 创建子进程
-
-
-
-
-
-
fork.c
//创建子进程
#include <stdio.h>
#include <unistd.h>
int main(void){
//创建子进程
printf("%d进程:我是父进程,我要创建子进程了\n", getpid());
pid_t a = fork();
if(a == -1){
perror("fork");
return -1;
}
printf("%d进程:铁锅炖大鹅\n", getpid());
printf("--------------\n");
return 0;
}
tarena@TNV:day08$ vi fork.c
tarena@TNV:day08$ gcc fork.c -o fork
tarena@TNV:day08$ ./fork
4112进程:我是父进程,我要创建子进程了
4112进程:铁锅炖大鹅
--------------
4113进程:铁锅炖大鹅
--------------
//创建子进程
#include <stdio.h>
#include <unistd.h>
int main(void){
//创建子进程
printf("%d进程:我是父进程,我要创建子进程了\n", getpid());
pid_t a = fork();
if(a == -1){
perror("fork");
return -1;
}
if(a == 0){
printf("%d进程:糊辣汤\n", getpid());
}else{
printf("%d进程:肉包子\n", getpid());
}
printf("%d进程:铁锅炖大鹅\n", getpid());
printf("%d进程:--------------\n", getpid());
return 0;
}
tarena@TNV:day08$ ./fork
4066进程:我是父进程,我要创建子进程了
4066进程:肉包子
4066进程:铁锅炖大鹅
4066进程:--------------
4067进程:糊辣汤
4067进程:铁锅炖大鹅
4067进程:--------------
tarena@TNV:day08$ vi fork.c
tarena@TNV:day08$ gcc fork.c -o fork
tarena@TNV:day08$ ./fork
4081进程:我是父进程,我要创建子进程了
4081进程:肉包子
4082进程:糊辣汤
tarena@TNV:day08$ cat fork.c
//创建子进程
#include <stdio.h>
#include <unistd.h>
int main(void){
//创建子进程
printf("%d进程:我是父进程,我要创建子进程了\n", getpid());
pid_t a = fork();
if(a == -1){
perror("fork");
return -1;
}
if(a == 0){
printf("%d进程:糊辣汤\n", getpid());
return 0;
}else{
printf("%d进程:肉包子\n", getpid());
return 0;
}
printf("%d进程:铁锅炖大鹅\n", getpid());
printf("%d进程:--------------\n", getpid());
return 0;
}
tarena@TNV:day08$ vi fork.c
tarena@TNV:day08$
tarena@TNV:day08$ gcc fork.c -o fork
tarena@TNV:day08$ ./fork
4127进程:我是父进程,我要创建子进程了
4127进程:这是父进程的代码
4128进程:这是子进程的代码
4127进程:我又创建了一个子进程
4129进程:我是二儿子
tarena@TNV:day08$ cat fork.c
//创建子进程
#include <stdio.h>
#include <unistd.h>
int main(void){
//创建子进程
printf("%d进程:我是父进程,我要创建子进程了\n", getpid());
pid_t a = fork();
if(a == -1){
perror("fork");
return -1;
}
if(a == 0){
printf("%d进程:这是子进程的代码\n", getpid());
return 0;
}
printf("%d进程:这是父进程的代码\n", getpid());
pid_t b = fork();
if(b == 0){
printf("%d进程:我是二儿子\n", getpid());
return 0;
}
printf("%d进程:我又创建了一个子进程\n", getpid());
return 0;
}
tarena@TNV:day08$ vi cfork.c
tarena@TNV:day08$ gcc cfork.c -o cfork
tarena@TNV:day08$ ./cfork
4199进程:我是父进程
子进程4201
子进程4202
子进程4200
子进程4203
子进程4204
子进程4205
子进程4206
tarena@TNV:day08$ cat cfork.c
//循环:创建多少个进程
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我是父进程\n", getpid());
for(int i = 0; i < 3; i++){
pid_t a = fork();
if(a == 0){
printf("子进程%d\n", getpid());
}
}
return 0;
}
tarena@TNV:day08$ vi copy.c
tarena@TNV:day08$ gcc copy.c -o copy
tarena@TNV:day08$ ./copy
4339进程:0x601068:1 0x7ffd4b21a218:2 0x1fbb010:3
4340进程:0x601068:2 0x7ffd4b21a218:3 0x1fbb010:4
4339进程:0x601068:1 0x7ffd4b21a218:2 0x1fbb010:3
tarena@TNV:day08$ cat copy.c
//子进程是父进程的副本
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int global = 1;//数据区
int main(void){
int local = 2;//栈区:非静态局部变量
int* heap = malloc(sizeof(int));//堆区
*heap = 3;
printf("%d进程:%p:%d %p:%d %p:%d\n", getpid(), &global, global, &local, local, heap, *heap);
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:%p:%d %p:%d %p:%d\n", getpid(), &global, ++global, &local, ++local, heap, ++*heap);
return 0;
}
//父进程代码
sleep(2);//等待子进程执行结束
printf("%d进程:%p:%d %p:%d %p:%d\n", getpid(), &global, global, &local, local, heap, *heap);
return 0;
}
-
由fork产生的子进程是其父进程的不完全副本,子进程在内存中的映像除了代码区与父进程共享同一块物理内存,其它各区映射到独立的物理内存,但其内容从父进程拷贝。
-
-
fork函数返回后,系统内核会将父进程维护的文件描述符表也复制到子进程的进程表项中,但并不复制文件表象。
-
孤儿进程演示
-
僵尸进程演示
-
ftab.c
tarena@TNV:day08$ vi ftab.c
tarena@TNV:day08$ gcc ftab.c -o ftab
tarena@TNV:day08$ ./ftab
tarena@TNV:day08$ cat ftab.txt
hello linux!tarena@TNV:day08$
tarena@TNV:day08$ cat ftab.c
//子进程会复制父进程的文件描述符
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(void){
//打开文件
int fd = open("./ftab.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(fd == -1){
perror("open");
return -1;
}
//父进程向文件中写入数据 hello world!
char* buf = "hello world!";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//父进程创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码 负责更改文件读写位置
if(pid == 0){
if(lseek(fd, -6, SEEK_END) == -1){
perror("lseek");
return -1;
}
close(fd);//子进程关闭文件
return 0;
}
//父进程代码 负责再次写入linux!
sleep(2);//等待子进程改完读写位置
buf = "linux!";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
close(fd);
return 0;
}
- orphan.c 孤儿进程演示
tarena@TNV:day08$ vi orphan.c
tarena@TNV:day08$ gcc orphan.c -o orphan
tarena@TNV:day08$ ./orphan
4519进程:我是子进程,我的父进程是4518
4518进程:我是父进程,我的子进程是4519
tarena@TNV:day08$ 4519进程:我是子进程,我的父进程是2252
^C
tarena@TNV:day08$ cat orphan.c
//孤儿进程演示
#include <stdio.h>
#include <unistd.h>
int main(void){
//父进程创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:我是子进程,我的父进程是%d\n", getpid(), getppid());
sleep(5);//子进程睡醒,变成孤儿进程,被孤儿院进程收养
printf("%d进程:我是子进程,我的父进程是%d\n", getpid(), getppid());
return 0;
}
//父进程代码
sleep(1);
printf("%d进程:我是父进程,我的子进程是%d\n", getpid(), pid);
return 0;
}
- zombie.c 僵尸进程演示
//僵尸进程演示
#include <stdio.h>
#include <unistd.h>
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:我是子进程,马上变僵尸!\n", getpid());
sleep(7);
return 0;
}
//父进程代码
printf("%d进程:我是父进程,我很忙\n", getpid());
sleep(15);
return 0;
}
tarena@TNV:day08$ vi zombie.c
tarena@TNV:day08$ gcc zombie.c -o zombie
tarena@TNV:day08$ ./zombie
4664进程:我是父进程,我很忙
4665进程:我是子进程,马上变僵尸!
tarena@TNV:~$ ps aux
tarena 4610 0.0 0.0 4352 680 pts/17 S+ 18:19 0:00 ./zom
tarena 4611 0.0 0.0 4352 76 pts/17 S+ 18:19 0:00 ./zom
tarena 4612 0.0 0.0 39104 3220 pts/2 R+ 18:19 0:00 ps au
tarena@TNV:~$ ps aux
tarena 4610 0.0 0.0 4352 680 pts/17 S+ 18:19 0:00 ./zom
tarena 4611 0.0 0.0 0 0 pts/17 Z+ 18:19 0:00 [zomb
tarena 4617 0.0 0.0 39104 3248 pts/2 R+ 18:20 0:00 ps au
tarena@TNV:~$ ps aux
tarena 4595 0.0 0.1 24328 5100 pts/2 Ss 18:18 0:00 bash
tarena 4618 0.0 0.0 39104 3280 pts/2 R+ 18:20 0:00 ps au
5.6 进程的终止
正常终止
-
-
-
-
-
-
-
-
-
exit.c
tarena@TNV:day08$ vi exit.c
tarena@TNV:day08$ gcc exit.c -o exit
tarena@TNV:day08$ ./exit
fun函数被调用
fun函数返回10
tarena@TNV:day08$ cat exit.c
//exit函数演示
#include <stdio.h>
#include <stdlib.h> //exit
int fun(void){
printf("fun函数被调用\n");
return 10;
}
int main(void){
int ret = fun();
printf("fun函数返回%d\n", ret);
return 0;
}
tarena@TNV:day08$ vi exit.c
tarena@TNV:day08$ gcc exit.c -o exit
tarena@TNV:day08$ ./exit
fun函数被调用
tarena@TNV:day08$ cat exit.c
//exit函数演示
#include <stdio.h>
#include <stdlib.h> //exit
int fun(void){
printf("fun函数被调用\n");
exit(0);
return 10;
}
int main(void){
int ret = fun();
printf("fun函数返回%d\n", ret);
return 0;
}
tarena@TNV:day09$ vi exit.c
tarena@TNV:day09$ gcc exit.c -o exit
tarena@TNV:day09$ ./exit
fun函数被调用
2850:我是退出处理函数
tarena@TNV:day09$ cat exit.c
//exit函数演示
#include <stdio.h>
#include <stdlib.h> //exit
#include <unistd.h>
//退出处理函数
void doit(void){
printf("%d:我是退出处理函数\n", getpid());
}
int fun(void){
printf("fun函数被调用\n");
exit(0);
return 10;
}
int main(void){
//注册退出处理函数
atexit(doit);
int ret = fun();
printf("fun函数返回%d\n", ret);
return 0;
}
tarena@TNV:day09$ vi exit.c
tarena@TNV:day09$ gcc exit.c -o exit
tarena@TNV:day09$ ./exit
a = 0
fun函数被调用
status = 520
arg = hello
2968:我是退出处理函数
tarena@TNV:day09$ cat exit.c
//exit函数演示
#include <stdio.h>
#include <stdlib.h> //exit
#include <unistd.h>
//退出处理函数
void doit(void){
printf("%d:我是退出处理函数\n", getpid());
}
//退出处理函数
void doit2(int status, void* arg){
printf("status = %d\n", status);
printf("arg = %s\n", (char*)arg);
}
int fun(void){
printf("fun函数被调用\n");
exit(520);
return 10;
}
int main(void){
//注册退出处理函数
atexit(doit);
int a = on_exit(doit2,"hello");
if(a == -1){
perror("on_exit");
return -1;
}
printf("a = %d\n", a);
int ret = fun();
printf("fun函数返回%d\n", ret);
return 0;
}
tarena@TNV:day09$ vi exit.c
tarena@TNV:day09$ gcc exit.c -o exit
tarena@TNV:day09$ ./exit
a = 0
fun函数被调用
tarena@TNV:day09$ cat exit.c
//exit函数演示
#include <stdio.h>
#include <stdlib.h> //exit
#include <unistd.h>
//退出处理函数
void doit(void){
printf("%d:我是退出处理函数\n", getpid());
}
//退出处理函数
void doit2(int status, void* arg){
printf("status = %d\n", status);
printf("arg = %s\n", (char*)arg);
}
int fun(void){
printf("fun函数被调用\n");
//exit(520);
_exit(1314);
return 10;
}
int main(void){
//注册退出处理函数
atexit(doit);
int a = on_exit(doit2,"hello");
if(a == -1){
perror("on_exit");
return -1;
}
printf("a = %d\n", a);
int ret = fun();
printf("fun函数返回%d\n", ret);
return 0;
}
异常终止
5.7 为什么要回收子进程
- 清除僵尸进程,避免消耗系统资源。
- 父进程需要等待子进程的终止,以继续后续工作。
- 父进程需要知道子进程终止的原因
– 如果是正常终止,那么进程的退出码是多少?
– 如果是异常终止,那么进程是被那个信号所终止的?
5.8 wait
tarena@TNV:day10$ vi wait.c
tarena@TNV:day10$ gcc wait.c -o wait
tarena@TNV:day10$ ./wait
2857进程:我是子进程
2856进程:我是父进程,2857进程的僵尸
tarena@TNV:day10$ cat wait.c
//回收子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h> //wait()
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(5);
return 0;
}
//父进程代码
int s;//用来输出子进程的终止状态
pid_t childpid = wait(&s);
if(childpid == -1){
perror("wait");
return -1;
}
printf("%d进程:我是父进程,%d进程的僵尸\n", getpid(), childpid);
return 0;
}
tarena@TNV:day10$ vi wait.c
tarena@TNV:day10$ gcc wait.c -o wait
tarena@TNV:day10$ ./wait
2906进程:我是子进程
2905进程:我是父进程,2906进程的僵尸
正常终止:0
tarena@TNV:day10$ cat wait.c
//回收子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h> //wait()
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(5);
return 0;
}
//父进程代码
int s;//用来输出子进程的终止状态
pid_t childpid = wait(&s);
if(childpid == -1){
perror("wait");
return -1;
}
printf("%d进程:我是父进程,%d进程的僵尸\n", getpid(), childpid);
if(WIFEXITED(s)){
//正常终止
printf("正常终止:%d\n", WEXITSTATUS(s));
}else{
//异常终止
printf("异常终止:%d\n", WTERMSIG(s));
}
return 0;
}
tarena@TNV:day10$ vi wait.c
tarena@TNV:day10$ gcc wait.c -o wait
tarena@TNV:day10$ ./wait
2964进程:我是子进程
2963进程:我是父进程,2964进程的僵尸
正常终止:44
tarena@TNV:day10$ cat wait.c
//回收子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h> //wait()
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(5);
//return 300;
//exit(300);
//_exit(300);
_Exit(300);
}
//父进程代码
int s;//用来输出子进程的终止状态
pid_t childpid = wait(&s);
if(childpid == -1){
perror("wait");
return -1;
}
printf("%d进程:我是父进程,%d进程的僵尸\n", getpid(), childpid);
if(WIFEXITED(s)){
//正常终止
printf("正常终止:%d\n", WEXITSTATUS(s));
}else{
//异常终止
printf("异常终止:%d\n", WTERMSIG(s));
}
return 0;
}
tarena@TNV:day10$ vi wait.c
tarena@TNV:day10$ gcc wait.c -o wait
tarena@TNV:day10$ ./wait
2975进程:我是子进程
2974进程:我是父进程,2975进程的僵尸
异常终止:6
tarena@TNV:day10$ cat wait.c
//回收子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h> //wait()
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(5);
//return 300;
//exit(300);
//_exit(300);
//_Exit(300);
abort();
}
//父进程代码
int s;//用来输出子进程的终止状态
pid_t childpid = wait(&s);
if(childpid == -1){
perror("wait");
return -1;
}
printf("%d进程:我是父进程,%d进程的僵尸\n", getpid(), childpid);
if(WIFEXITED(s)){
//正常终止
printf("正常终止:%d\n", WEXITSTATUS(s));
}else{
//异常终止
printf("异常终止:%d\n", WTERMSIG(s));
}
return 0;
}
tarena@TNV:day10$ vi wait.c
tarena@TNV:day10$ gcc wait.c -o wait
tarena@TNV:day10$ ./wait
2998进程:我是子进程
2997进程:我是父进程,2998进程的僵尸
异常终止:11
tarena@TNV:day10$ cat wait.c
//回收子进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h> //wait()
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(5);
//return 300;
//exit(300);
//_exit(300);
//_Exit(300);
//abort();
int* p = NULL;
*p = 1;
}
//父进程代码
int s;//用来输出子进程的终止状态
pid_t childpid = wait(&s);
if(childpid == -1){
perror("wait");
return -1;
}
printf("%d进程:我是父进程,%d进程的僵尸\n", getpid(), childpid);
if(WIFEXITED(s)){
//正常终止
printf("正常终止:%d\n", WEXITSTATUS(s));
}else{
//异常终止
printf("异常终止:%d\n", WTERMSIG(s));
}
return 0;
}
tarena@TNV:day10$ vi waitall.c
tarena@TNV:day10$ gcc waitall.c -o waitall
tarena@TNV:day10$ ./waitall
3078进程:我是子进程
3080进程:我是子进程
3079进程:我是子进程
3081进程:我是子进程
3082进程:我是子进程
3077进程:回收了3078进程
正常终止:0
3077进程:回收了3079进程
正常终止:1
3077进程:回收了3080进程
正常终止:2
3077进程:回收了3081进程
正常终止:3
3077进程:回收了3082进程
正常终止:4
没有子进程回收
tarena@TNV:day10$ cat waitall.c
//回收多个子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
int main(void){
//创建多个子进程
for(int i = 0; i < 5; i++){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(1 + i);
return i;//子进程结束,创建5个子进程
}
}
//回收多个子进程
while(1){
int s;//用来输出子进程的终止状态
pid_t pid = wait(&s);
if(pid == -1){
if(errno == ECHILD){
printf("没有子进程回收\n");
break;
}else{
perror("wait");
return -1;
}
}
printf("%d进程:回收了%d进程\n", getpid(), pid);
if(WIFSIGNALED(s)){
printf("异常终止:%d\n", WTERMSIG(s));
}else{
printf("正常终止:%d\n", WEXITSTATUS(s));
}
}
return 0;
}
tarena@TNV:day10$ vi waitpid.c
tarena@TNV:day10$ gcc waitpid.c -o waitpid
tarena@TNV:day10$ ./waitpid
我要回收3187进程的僵尸
3183进程:我是老1
3185进程:我是老3
3184进程:我是老2
3187进程:我是老5
3186进程:我是老4
我回收了3187进程的僵尸
我要回收3186进程的僵尸
我回收了3186进程的僵尸
我要回收3185进程的僵尸
我回收了3185进程的僵尸
我要回收3184进程的僵尸
我回收了3184进程的僵尸
我要回收3183进程的僵尸
我回收了3183进程的僵尸
tarena@TNV:day10$ cat waitpid.c
//回收特定子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void){
//创建多个子进程
pid_t pids[5];//存储子进程的ID
for(int i = 0; i < 5; i++){
pids[i] = fork();
if(pids[i] == -1){
perror("fork");
return -1;
}
if(pids[i] == 0){
printf("%d进程:我是老%d\n", getpid(), i+1);
sleep(i + 1);
return 0;
}
}
//父进程回收子进程
for(int i = 4; i >= 0; i--){
printf("我要回收%d进程的僵尸\n", pids[i]);
pid_t pid = waitpid(pids[i], NULL, 0);
if(pid == -1){
perror("waitpid");
return -1;
}
printf("我回收了%d进程的僵尸\n", pid);
}
return 0;
}
5.9 waitpid
tarena@TNV:day10$ vi nohang.c
tarena@TNV:day10$ gcc nohang.c -o nohang
tarena@TNV:day10$ ./nohang
3256进程:子进程在运行,干饭去
3257进程:我是子进程,就不结束
3256进程:子进程在运行,干饭去
3256进程:子进程在运行,干饭去
3256进程:子进程在运行,干饭去
3256进程:子进程在运行,干饭去
3256进程:子进程在运行,干饭去
3256进程:子进程在运行,干饭去
3256进程:子进程在运行,干饭去
3256进程:回收了3257进程
3256进程:没有子进程了
tarena@TNV:day10$ cat nohang.c
//非阻塞回收
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码,暂时不结束
if(pid == 0){
printf("%d进程:我是子进程,就不结束\n", getpid());
sleep(8);
return 0;
}
//父进程回收子进程
while(1){
pid_t pid = waitpid(-1, NULL, WNOHANG);
if(pid == -1){
if(errno == ECHILD){
printf("%d进程:没有子进程了\n", getpid());
break;
}else{
perror("waitpid");
return -1;
}
}else if(pid == 0){
printf("%d进程:子进程在运行,干饭去\n", getpid());
sleep(1);
}else{
printf("%d进程:回收了%d进程\n", getpid(), pid);
}
}
return 0;
}
5.10 创建新进程
tarena@TNV:day10$ vi new.c
tarena@TNV:day10$ gcc new.c -o new
tarena@TNV:day10$ ./new hello.c 123
PID:3457
命令行参数的内容
./new
hello.c
123
--------------------------------------
tarena@TNV:day10$ ./new "hello.c" 123
PID:3458
命令行参数的内容
./new
hello.c
123
--------------------------------------
tarena@TNV:day10$ ./new \"hello.c\" 123
PID:3459
命令行参数的内容
./new
"hello.c"
123
--------------------------------------
tarena@TNV:day10$ ./new "he"l"lo.c" 123
PID:3463
命令行参数的内容
./new
hello.c
123
--------------------------------------
tarena@TNV:day10$ cat new.c
//变身的目标
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[], char* envp[]){
printf("PID:%d\n", getpid());
printf("命令行参数的内容\n");
for(char** pp = argv; *pp; pp++){
printf("%s\n", *pp);
}
/*
printf("\n\n环境变量\n\n");
for(char** pp = envp; *pp; pp++){
printf("%s\n", *pp);
}
*/
printf("--------------------------------------\n");
return 0;
}
tarena@TNV:day10$ vi exec.c
tarena@TNV:day10$ gcc exec.c -o exec
tarena@TNV:day10$ ./exec
3518进程:我要变身了
PID:3518
命令行参数的内容
new
hello
123
--------------------------------------
tarena@TNV:day10$ ./exec
3521进程:我要变身了
PID:3521
命令行参数的内容
new
hello
123
--------------------------------------
tarena@TNV:day10$ cat exec.c
//新进程的创建
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我要变身了\n", getpid());
if(execl("./new", "new", "hello", "123", NULL) == -1){
perror("execl");
return -1;
}
printf("%d进程:变身完成了!\n", getpid());
return 0;
}
tarena@TNV:day10$ vi exec.c
tarena@TNV:day10$ gcc exec.c -o exec
tarena@TNV:day10$ ./exec
3569进程:我要变身了
总用量 96
1967568 -rwxrwxr-x 1 tarena tarena 8760 8月 4 13:36 exec
1967572 -rw-rw-r-- 1 tarena tarena 383 8月 4 13:36 exec.c
1967567 -rwxrwxr-x 1 tarena tarena 8704 8月 4 12:58 new
1967570 -rw-rw-r-- 1 tarena tarena 406 8月 4 12:58 new.c
1967564 -rwxrwxr-x 1 tarena tarena 8928 8月 4 11:46 nohang
1967569 -rw-rw-r-- 1 tarena tarena 788 8月 4 11:46 nohang.c
1967559 -rwxrwxr-x 1 tarena tarena 8920 8月 4 10:34 wait
1967561 -rwxrwxr-x 1 tarena tarena 9032 8月 4 11:00 waitall
1967565 -rw-rw-r-- 1 tarena tarena 856 8月 4 11:00 waitall.c
1967563 -rw-rw-r-- 1 tarena tarena 820 8月 4 10:34 wait.c
1967562 -rwxrwxr-x 1 tarena tarena 8928 8月 4 11:26 waitpid
1967566 -rw-rw-r-- 1 tarena tarena 662 8月 4 11:26 waitpid.c
tarena@TNV:day10$ cat exec.c
//新进程的创建
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我要变身了\n", getpid());
/*
if(execl("./new", "new", "hello", "123", NULL) == -1){
perror("execl");
return -1;
}
*/
if(execl("/bin/ls", "ls", "-l", "-i", "--color=auto", NULL) == -1){
perror("execl");
return -1;
}
printf("%d进程:变身完成了!\n", getpid());
return 0;
}
tarena@TNV:day10$ vi exec.c
tarena@TNV:day10$ gcc exec.c -o exec
tarena@TNV:day10$ ./exec
3618进程:我要变身了
总用量 96
-rwxrwxr-x 1 tarena tarena 8760 8月 4 13:51 exec
-rw-rw-r-- 1 tarena tarena 502 8月 4 13:51 exec.c
-rwxrwxr-x 1 tarena tarena 8704 8月 4 12:58 new
-rw-rw-r-- 1 tarena tarena 406 8月 4 12:58 new.c
-rwxrwxr-x 1 tarena tarena 8928 8月 4 11:46 nohang
-rw-rw-r-- 1 tarena tarena 788 8月 4 11:46 nohang.c
-rwxrwxr-x 1 tarena tarena 8920 8月 4 10:34 wait
-rwxrwxr-x 1 tarena tarena 9032 8月 4 11:00 waitall
-rw-rw-r-- 1 tarena tarena 856 8月 4 11:00 waitall.c
-rw-rw-r-- 1 tarena tarena 820 8月 4 10:34 wait.c
-rwxrwxr-x 1 tarena tarena 8928 8月 4 11:26 waitpid
-rw-rw-r-- 1 tarena tarena 662 8月 4 11:26 waitpid.c
tarena@TNV:day10$ cat exec.c
//新进程的创建
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我要变身了\n", getpid());
/*
if(execl("./new", "new", "hello", "123", NULL) == -1){
perror("execl");
return -1;
}
*/
/*
if(execl("/bin/ls", "ls", "-l", "-i", "--color=auto", NULL) == -1){
perror("execl");
return -1;
}
*/
if(execlp("ls", "ls", "-l", "--color=auto", NULL) == -1){
perror("execlp");
return -1;
}
printf("%d进程:变身完成了!\n", getpid());
return 0;
}
tarena@TNV:day10$ vi new.c
tarena@TNV:day10$ gcc new.c -o new
tarena@TNV:day10$ cat new.c
//变身的目标
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[], char* envp[]){
printf("PID:%d\n", getpid());
printf("命令行参数的内容\n");
for(char** pp = argv; *pp; pp++){
printf("%s\n", *pp);
}
printf("\n\n环境变量\n\n");
for(char** pp = envp; *pp; pp++){
printf("%s\n", *pp);
}
printf("--------------------------------------\n");
return 0;
}
tarena@TNV:day10$ vi exec.c
tarena@TNV:day10$ gcc exec.c -o exec
tarena@TNV:day10$ ./exec
3682进程:我要变身了
PID:3682
命令行参数的内容
new
hello
123
环境变量
NAME=laozhang
AGE=18
FOOD=liurouduan
--------------------------------------
tarena@TNV:day10$ cat exec.c
//新进程的创建
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我要变身了\n", getpid());
/*
if(execl("./new", "new", "hello", "123", NULL) == -1){
perror("execl");
return -1;
}
*/
/*
if(execl("/bin/ls", "ls", "-l", "-i", "--color=auto", NULL) == -1){
perror("execl");
return -1;
}
*/
/*
if(execlp("ls", "ls", "-l", "--color=auto", NULL) == -1){
perror("execlp");
return -1;
}
*/
char* envp[] = {"NAME=laozhang", "AGE=18", "FOOD=liurouduan", NULL};
if(execle("./new", "new", "hello", "123", NULL, envp) == -1){
perror("execle");
return -1;
}
printf("%d进程:变身完成了!\n", getpid());
return 0;
}
tarena@TNV:day10$ vi exec.c
tarena@TNV:day10$ gcc exec.c -o exec
tarena@TNV:day10$ ./exec
3728进程:我要变身了
PID:3728
命令行参数的内容
new
hello
123
环境变量
NAME=laozhang
AGE=18
FOOD=liurouduan
--------------------------------------
tarena@TNV:day10$ cat exec.c
//新进程的创建
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("%d进程:我要变身了\n", getpid());
/*
if(execl("./new", "new", "hello", "123", NULL) == -1){
perror("execl");
return -1;
}
*/
/*
if(execl("/bin/ls", "ls", "-l", "-i", "--color=auto", NULL) == -1){
perror("execl");
return -1;
}
*/
/*
if(execlp("ls", "ls", "-l", "--color=auto", NULL) == -1){
perror("execlp");
return -1;
}
*/
//char* envp[] = {"NAME=laozhang", "AGE=18", "FOOD=liurouduan", NULL};
/*if(execle("./new", "new", "hello", "123", NULL, envp) == -1){
perror("execle");
return -1;
}*/
char* envp[] = {"NAME=laozhang", "AGE=18", "FOOD=liurouduan", NULL};
char* argv[] = {"new", "hello", "123", NULL};
if(execve("./new", argv, envp) == -1){
perror("execve");
return -1;
}
printf("%d进程:变身完成了!\n", getpid());
return 0;
}
5.11 system
tarena@TNV:day10$ vi system.c
tarena@TNV:day10$ gcc system.c -o system
tarena@TNV:day10$ ./system
PID:4387
命令行参数的内容
./new
hello
123
环境变量
--------------------------------------
正常结束:0
总用量 128
-rwxrwxr-x 1 tarena tarena 8824 8月 4 16:01 exec
-rw-rw-r-- 1 tarena tarena 882 8月 4 14:22 exec.c
-rwxrwxr-x 1 tarena tarena 8872 8月 4 16:01 forkexec
-rw-rw-r-- 1 tarena tarena 999 8月 4 16:00 forkexec.c
-rwxrwxr-x 1 tarena tarena 8704 8月 4 16:01 new
-rw-rw-r-- 1 tarena tarena 406 8月 4 15:54 new.c
-rwxrwxr-x 1 tarena tarena 8928 8月 4 11:46 nohang
-rw-rw-r-- 1 tarena tarena 788 8月 4 11:46 nohang.c
-rwxrwxr-x 1 tarena tarena 8656 8月 4 16:13 system
-rw-rw-r-- 1 tarena tarena 439 8月 4 16:13 system.c
-rwxrwxr-x 1 tarena tarena 8920 8月 4 10:34 wait
-rwxrwxr-x 1 tarena tarena 9032 8月 4 11:00 waitall
-rw-rw-r-- 1 tarena tarena 856 8月 4 11:00 waitall.c
-rw-rw-r-- 1 tarena tarena 820 8月 4 10:34 wait.c
-rwxrwxr-x 1 tarena tarena 8928 8月 4 11:26 waitpid
-rw-rw-r-- 1 tarena tarena 662 8月 4 11:26 waitpid.c
正常结束:0
tarena@TNV:day10$ cat system.c
//system函数演示
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void){
int s = system("./new hello 123");
if(WIFEXITED(s)){
printf("正常结束:%d\n", WEXITSTATUS(s));
}else{
printf("异常终止:%d\n", WTERMSIG(s));
}
s = system("ls -l --color=auto");
if(WIFEXITED(s)){
printf("正常结束:%d\n", WEXITSTATUS(s));
}else{
printf("异常终止:%d\n", WTERMSIG(s));
}
return 0;
}
6. 信号(软中断)
6.1 信号基础
- 什么是信号
- 信号的名称与编号
- 常用编号
//所有信号
tarena@TNV:day11$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
tarena@TNV:day11$ ls
a.out test.c
tarena@TNV:day11$ gcc test.c
tarena@TNV:day11$ a.out
段错误 (核心已转储)
tarena@TNV:day11$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 13933
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 13933
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
tarena@TNV:day11$ ulimit -c 1024
tarena@TNV:day11$ ulimit -a
core file size (blocks, -c) 1024
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 13933
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 13933
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
tarena@TNV:day11$ a.out
段错误 (核心已转储)
tarena@TNV:day11$
tarena@TNV:day11$ ls
a.out core test.c
tarena@TNV:day11$ gdb corefile core
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
corefile: 没有那个文件或目录.
[New LWP 4900]
Core was generated by `a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004004e6 in ?? ()
(gdb) q
tarena@TNV:day11$ cat test.c
//转储文件
#include <stdio.h>
int main(void){
int* p = NULL;
*p = 10;
return 0;
}
6.2 信号处理
tarena@TNV:day11$ vi signal.c
tarena@TNV:day11$ gcc signal.c -o signal
tarena@TNV:day11$ ./signal
ret = (nil)
^C^C^_^\退出
tarena@TNV:day11$ cat signal.c
//信号处理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
int main(void){
//忽略2号信号
sighandler_t ret = signal(SIGINT, SIG_IGN);
if(ret == SIG_ERR){
perror("signal");
return -1;
}
printf("ret = %p\n", ret);
for(;;);
return 0;
}
tarena@TNV:day11$ vi signal.c
tarena@TNV:day11$ gcc signal.c -o signal
tarena@TNV:day11$ ./signal
ret = (nil)
ret = 0x1
^C2978进程:捕获到2号信号
^C2978进程:捕获到2号信号
^C2978进程:捕获到2号信号
^\退出
tarena@TNV:day11$ cat signal.c
//信号处理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n", getpid(), signum);
}
int main(void){
//忽略2号信号
sighandler_t ret = signal(SIGINT, SIG_IGN);
if(ret == SIG_ERR){
perror("signal");
return -1;
}
printf("ret = %p\n", ret);//上一次的处理返回 NULL
//捕获2号信号
ret = signal(SIGINT, sigfun);
if(ret == SIG_ERR){
perror("signal");
return -1;
}
printf("ret = %p\n", ret); //上一次忽略处理,返回SIG_IGN -> 0x1
for(;;);
return 0;
}
tarena@TNV:day11$ vi signal.c
tarena@TNV:day11$ gcc signal.c -o signal
tarena@TNV:day11$ ./signal
ret = (nil)
ret = 0x1
ret = 0x400606
sigfun = 0x400606
^C
tarena@TNV:day11$ cat signal.c
//信号处理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n", getpid(), signum);
}
int main(void){
//忽略2号信号
sighandler_t ret = signal(SIGINT, SIG_IGN);
if(ret == SIG_ERR){
perror("signal");
return -1;
}
printf("ret = %p\n", ret);//上一次的处理返回 NULL
//捕获2号信号
ret = signal(SIGINT, sigfun);
if(ret == SIG_ERR){
perror("signal");
return -1;
}
printf("ret = %p\n", ret); //上一次忽略处理,返回SIG_IGN -> 0x1
//恢复默认处理
ret = signal(SIGINT,SIG_DFL);
if(ret == SIG_ERR){
perror("signal");
return -1;
}
printf("ret = %p\n", ret); //上一次处理返回
printf("sigfun = %p\n", sigfun);
for(;;);
return 0;
}
6.3 太平间信号
tarena@TNV:day11$ vi sigchild.c
tarena@TNV:day11$ gcc sigchild.c -o sigchild
tarena@TNV:day11$ ./sigchild
3215进程:我是子进程
3216进程:我是子进程
3217进程:我是子进程
3218进程:我是子进程
3219进程:我是子进程
3214进程:捕获到17信号
3214进程:回收3215进程的僵尸
3214进程:捕获到17信号
3214进程:回收3216进程的僵尸
3214进程:捕获到17信号
3214进程:回收3217进程的僵尸
3214进程:捕获到17信号
3214进程:回收3218进程的僵尸
3214进程:捕获到17信号
3214进程:回收3219进程的僵尸
^C
tarena@TNV:day11$ cat sigchild.c
//太平间信号
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
//信号处理函数,负责对子进程进行收尸
void sigchild(int signum){
printf("%d进程:捕获到%d信号\n", getpid(), signum);
pid_t pid = wait(NULL);
if(pid == -1){
perror("wait");
}
printf("%d进程:回收%d进程的僵尸\n", getpid(), pid);
}
int main(void){
//对17号信号进行捕获处理
if(signal(SIGCHLD, sigchild) == SIG_ERR){
perror("signal");
return -1;
}
//创建多个子进程
for(int i =0; i < 5; i++){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(i + 1);
return 0;
}
}
//父进程代码
for(;;);
return 0;
}
在信号处理函数执行期间,如果有相同的多个信号再次到来,只保留一个,其余统统丢弃
tarena@TNV:day11$ vi sigchild.c
tarena@TNV:day11$ gcc sigchild.c -o sigchild
tarena@TNV:day11$ ./sigchild
3275进程:我是子进程
3278进程:我是子进程
3276进程:我是子进程
3277进程:我是子进程
3279进程:我是子进程
3274进程:捕获到17信号
3274进程:回收3275进程的僵尸
3274进程:捕获到17信号
3274进程:回收3276进程的僵尸
^C
tarena@TNV:day11$ cat sigchild.c
//太平间信号
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
//信号处理函数,负责对子进程进行收尸
void sigchild(int signum){
sleep(3);//假装信号处理周期比较长
printf("%d进程:捕获到%d信号\n", getpid(), signum);
pid_t pid = wait(NULL);
if(pid == -1){
perror("wait");
}
printf("%d进程:回收%d进程的僵尸\n", getpid(), pid);
}
int main(void){
//对17号信号进行捕获处理
if(signal(SIGCHLD, sigchild) == SIG_ERR){
perror("signal");
return -1;
}
//创建多个子进程
for(int i =0; i < 5; i++){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
//sleep(i + 1);
sleep(1);
return 0;
}
}
//父进程代码
for(;;);
return 0;
}
tarena@TNV:day12$ vi sigchild.c
tarena@TNV:day12$ gcc sigchild.c -o sigchild
tarena@TNV:day12$ ./sigchild
2811进程:我是子进程0
2812进程:我是子进程0
2814进程:我是子进程0
2815进程:我是子进程0
2813进程:我是子进程0
2816进程:我是老六:0,就不结束
2810进程:捕获到17号信号
2810进程:回收了2811进程的僵尸
2810进程:回收了2812进程的僵尸
2810进程:回收了2813进程的僵尸
2810进程:回收了2814进程的僵尸
2810进程:回收了2815进程的僵尸
0进程:子进程正在运行,没发回收
2810进程:捕获到17号信号
0进程:子进程正在运行,没发回收
2810进程:捕获到17号信号
2810进程:回收了2816进程的僵尸
2810进程:没有子进程了
^C
tarena@TNV:day12$ cat sigchild.c
//太平间信号
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
//信号处理函数,负责堆子进程进行收尸
void sigchild(int signum){
sleep(3);//假装信号处理周期比较长
printf("%d进程:捕获到%d号信号\n", getpid(), signum);
/*
for(;;){//在一次信号处理函数中,尽可能多的收尸
pid_t pid = wait(NULL);
if(pid == -1){
if(errno == ECHILD){
printf("%d进程:没有子进程\n", getpid());
break;
}else{
perror("wait");
return;
}
}else{
printf("%d进程:回收%d进程的僵尸\n", getpid(), pid);
}
}
*/
for(;;){
pid_t pid = waitpid(-1, NULL, WNOHANG);
if(pid == -1){
if(errno == ECHILD){
printf("%d进程:没有子进程了\n", getpid());
break;
}else{
perror("waitpid");
return;
}
}else if(pid == 0){
printf("%d进程:子进程正在运行,没发回收\n", pid);
break;
}else{
printf("%d进程:回收了%d进程的僵尸\n", getpid(), pid);
}
}
}
int main(void){
//对17号信号进行捕获
if(signal(SIGCHLD, sigchild) == SIG_ERR){
perror("signal");
return -1;
}
//创建多个进程
for(int i = 0; i < 5; i++){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
printf("%d进程:我是子进程%d\n", getpid(), pid);
sleep(1);
return 0;
}
}
pid_t oldsix = fork();
if(oldsix == -1){
perror("fork");
return -1;
}
if(oldsix == 0){
printf("%d进程:我是老六:%d,就不结束\n", getpid(), oldsix);
sleep(15);
return 0;
}
//父进程
for(;;);
return 0;
}
6.4 信号处理的继承与恢复
//子进程是否会集成父进程的信号处理方式
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d进程的信号\n", getpid(), signum);
}
int main(void){
//忽略2号信号返回NULL
if(signal(SIGINT, SIG_IGN) == SIG_ERR){
perror("signal");
return -1;
}
//捕获3号信号返回NULL
if(signal(SIGQUIT, sigfun) == SIG_ERR){
perror("signal");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程代码暂时不结束
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
sleep(20);
return 0;
}
//父进程代码
if(wait(NULL) == -1){
perror("wait");
return -1;
}
printf("%d进程:回收了子进程的僵尸\n", getpid());
return 0;
}
tarena@TNV:~$ kill -2 2958
tarena@TNV:~$ kill -3 2958
tarena@TNV:day12$ vi fork.c
tarena@TNV:day12$ gcc fork.c -o fork
tarena@TNV:day12$ ./fork
2958进程:我是子进程
2958进程:捕获到3进程的信号
2957进程:回收了子进程的僵尸
tarena@TNV:day12$ cat new.c
//变身的对象
#include <stdio.h>
#include <unistd.h>
int main(void){
printf("PID:%d\n", getpid());
for(;;);
return 0;
}
tarena@TNV:day12$ cat exec.c
//新进成是否会继承旧进程的信号处理方式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n", getpid(), signum);
}
int main(void){
//捕获忽略2号处理方式
if(signal(SIGINT, SIG_IGN) == SIG_ERR){
perror("signal");
return -1;
}
//捕获3号信号
if(signal(SIGQUIT, sigfun) == SIG_ERR){
perror("signal");
return -1;
}
//变身成new
if(execl("./new", "new", NULL) == -1){
perror("execl");
return -1;
}
return 0;
}
tarena@TNV:day12$ vi exec.c
tarena@TNV:day12$ gcc exec.c -o exec
tarena@TNV:day12$ gcc new.c -o new
tarena@TNV:day12$ ./exec
PID:3060
^C^C^C^C^C^C^C^C^C^C^C^C^\退出
tarena@TNV:day12$ cat exec.c
//新进成是否会继承旧进程的信号处理方式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n", getpid(), signum);
}
int main(void){
//捕获忽略2号处理方式
if(signal(SIGINT, SIG_IGN) == SIG_ERR){
perror("signal");
return -1;
}
//捕获3号信号
if(signal(SIGQUIT, sigfun) == SIG_ERR){
perror("signal");
return -1;
}
int s = system("./new");
if(WIFEXITED(s)){
printf("正常结束:%d\n", WEXITSTATUS(s));
}else{
printf("异常结束:%d\n", WTERMSIG(s));
}
//变身成new
/*if(execl("./new", "new", NULL) == -1){
perror("execl");
return -1;
}*/
return 0;
}
tarena@TNV:day12$ vi exec.c
tarena@TNV:day12$ gcc exec.c -o exec
tarena@TNV:day12$ ./exec
PID:3100
^C^C^C^C^\异常结束:3
6.5 发送信号
tarena@TNV:day12$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
tarena@TNV:day12$ cat kill.c
//kill函数演示
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d信号\n", getpid(), signum);
}
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程对2号进行捕获
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
if(signal(SIGINT,sigfun) == SIG_ERR){
perror("signal");
return -1;
}
sleep(10);
return 0;
}
//父进程向子进程发送2号信号
printf("%d进程:我要向子进程发送2号信号\n", getpid());
getchar();
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
return 0;
}
tarena@TNV:day12$ vi kill.c
tarena@TNV:day12$ gcc kill.c -o kill
tarena@TNV:day12$ ./kill
3342进程:我要向子进程发送2号信号
3343进程:我是子进程
3343进程:捕获到2信号
tarena@TNV:day12$ cat kill.c
//kill函数演示
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d信号\n", getpid(), signum);
}
//用来判断进程是否存在的函数
int isexist(pid_t pid){
if(kill(pid, 0) == -1){
if(errno == ESRCH){//kill失败是因为进程PID不存在
return 1;
}else{
perror("kill");
exit(-1);
}
}
return 0;//kill函数成功,表示进程PID存在
}
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程对2号进行捕获
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
/*if(signal(SIGINT,sigfun) == SIG_ERR){
perror("signal");
return -1;
}*/
sleep(10);
return 0;
}
//父进程向子进程发送2号信号
printf("%d进程:我要向子进程发送2号信号\n", getpid());
getchar();
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
//查看子进程是否存在
getchar();
printf("子进程:%s\n", isexist(pid) ? "不存在" : "存在");
return 0;
}
tarena@TNV:day12$ vi kill.c
tarena@TNV:day12$ gcc kill.c -o kill
tarena@TNV:day12$ ./kill
3380进程:我要向子进程发送2号信号
3381进程:我是子进程
子进程:存在
tarena@TNV:day12$ cat kill.c
//kill函数演示
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d信号\n", getpid(), signum);
}
//用来判断进程是否存在的函数
int isexist(pid_t pid){
if(kill(pid, 0) == -1){
if(errno == ESRCH){//kill失败是因为进程PID不存在
return 1;
}else{
perror("kill");
exit(-1);
}
}
return 0;//kill函数成功,表示进程PID存在
}
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程对2号进行捕获
if(pid == 0){
printf("%d进程:我是子进程\n", getpid());
/*if(signal(SIGINT,sigfun) == SIG_ERR){
perror("signal");
return -1;
}*/
sleep(10);
return 0;
}
//父进程向子进程发送2号信号
printf("%d进程:我要向子进程发送2号信号\n", getpid());
getchar();
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
//查看子进程是否存在
getchar();
printf("子进程:%s\n", isexist(pid) ? "不存在" : "存在");
//回收进程
if(wait(NULL) == -1){
perror("wait");
return -1;
}
//查看子进程是否存在
getchar();
printf("子进程:%s\n", isexist(pid) ? "不存在" : "存在");
return 0;
}
tarena@TNV:day12$ vi kill.c
tarena@TNV:day12$ gcc kill.c -o kill
tarena@TNV:day12$ ./kill
3409进程:我要向子进程发送2号信号
3410进程:我是子进程
子进程:存在
子进程:不存在
6.6 暂停、睡眠与闹钟
pause.c
tarena@TNV:day12$ cat pause.c
//暂停 pause函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:%d号信号处理开始\n", getpid(), signum);
sleep(3);
printf("%d进程:%d号信号处理结束\n", getpid(), signum);
}
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程睡觉
if(pid == 0){
//对2号信号进行捕获
if(signal(SIGINT, sigfun) == SIG_ERR){
perror("signal");
return -1;
}
printf("%d进程:我是子进程,我要一睡不醒\n", getpid());
int res = pause();
printf("%d进程:pause函数返回%d\n", getpid(), res);
return 0;
}
//父进程发信号,打断子进程睡觉
printf("%d进程:我是父进程,要给子进程发送2号信号\n", getpid());
getchar();
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
//回收子进程
if(wait(NULL) == -1){
perror("wait");
return -1;
}
return 0;
}
tarena@TNV:day12$ vi pause.c
tarena@TNV:day12$ gcc pause.c -o pause
tarena@TNV:day12$ ./pause
3519进程:我是父进程,要给子进程发送2号信号
3520进程:我是子进程,我要一睡不醒
3520进程:2号信号处理开始
3520进程:2号信号处理结束
3520进程:pause函数返回-1
sleep.c
tarena@TNV:day12$ cat sleep.c
//睡眠 sleep函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:%d号信号处理开始\n", getpid(), signum);
sleep(3);
printf("%d进程:%d号信号处理结束\n", getpid(), signum);
}
int main(void){
//创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//子进程睡觉
if(pid == 0){
//对2号信号进行捕获
if(signal(SIGINT, sigfun) == SIG_ERR){
perror("signal");
return -1;
}
printf("%d进程:我是子进程,我要睡一会\n", getpid());
int res = sleep(10);
printf("%d进程:pause函数返回%d\n", getpid(), res);
return 0;
}
//父进程发信号,打断子进程睡觉
printf("%d进程:我是父进程,要给子进程发送2号信号\n", getpid());
getchar();
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
//回收子进程
if(wait(NULL) == -1){
perror("wait");
return -1;
}
return 0;
}
tarena@TNV:day12$ vi sleep.c
tarena@TNV:day12$ gcc sleep.c -o sleep
tarena@TNV:day12$ ./sleep
3552进程:我是父进程,要给子进程发送2号信号
3553进程:我是子进程,我要睡一会
3553进程:2号信号处理开始
3553进程:2号信号处理结束
3553进程:pause函数返回6
tarena@TNV:day12$ ./sleep
3554进程:我是父进程,要给子进程发送2号信号
3555进程:我是子进程,我要睡一会
3555进程:2号信号处理开始
3555进程:2号信号处理结束
3555进程:pause函数返回9
alarm.c
tarena@TNV:day12$ cat alarm.c
//闹钟
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n", getpid(), signum);
}
int main(void){
//捕获到14号信号
if(signal(SIGALRM, sigfun) == SIG_ERR){
perror("signal");
return -1;
}
//设置闹钟
printf("alarm(10) = %d\n", alarm(10));
getchar();
printf("alarm(5) = %d\n", alarm(5));
for(;;);
return 0;
}
tarena@TNV:day12$ vi alarm.c
tarena@TNV:day12$ gcc alarm.c -o alarm
tarena@TNV:day12$ ./alarm
alarm(10) = 0
3670进程:捕获到14号信号
alarm(5) = 6
3670进程:捕获到14号信号
^C
6.7 信号集
信号集
sigset.c
tarena@TNV:day12$ cat sigset.c
//信号集操作
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
//输出一个字节8个比特位的值
void printb(char byte){
for(int i = 0; i < 8; i++){
printf("%d", byte & 1 << 7 - i ? 1 : 0 );
}
printf(" ");//空格
}
//输出一个缓冲区里所有字节的比特位值
//buf接收缓冲区的首地址,size接收缓冲区的大小
void printm(void* buf, size_t size){
for(int i = 0; i < size; i++){
//从最后字节开始,倒着输出
//buf优先强转char* ,再访问缓冲区
printb( ((char*)buf)[size-1-i] );
if((i + 1) % 8 == 0){
printf("\n");
}
}
}
int main(void){
//信号集类型的变量
sigset_t s;
//初始化
printf("填满信号集\n");
sigfillset(&s);
printm(&s, sizeof(s));
printf("清空信号集\n");
sigemptyset(&s);
printm(&s, sizeof(s));
//添加信号
printf("添加2号信号\n");
sigaddset(&s, SIGINT);
printm(&s, sizeof(s));
printf("添加3号信号\n");
sigaddset(&s, SIGQUIT);
printm(&s, sizeof(s));
printf("删除2号信号\n");
sigdelset(&s, SIGINT);
printm(&s, sizeof(s));
printf("信号集中%s2号信号\n", sigismember(&s, SIGINT) ? "有" : "无");
printf("信号集中%s3号信号\n", sigismember(&s, SIGQUIT) ? "有" : "无");
return 0;
}
tarena@TNV:day12$ vi sigset.c
tarena@TNV:day12$ gcc sigset.c -o sigset
tarena@TNV:day12$ ./sigset
填满信号集
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
11111111 11111111 11111111 11111110 01111111 11111111 11111111 11111111
清空信号集
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
添加2号信号
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010
添加3号信号
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000110
删除2号信号
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000100
信号集中无2号信号
信号集中有3号信号
信号集操作相关函数
6.8 信号屏蔽
sigmask.c
tarena@TNV:day13$ cat sigmask.c
//信号屏蔽
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
//信号处理函数
void sigfun(int signum){
printf("%d进程:捕获到%d号信号\n", getpid(), signum);
}
//假装更新数据库
void updatedb(void){
for(int i = 0; i < 5; i++){
printf("正在更新第%d条数据...\n", i+ 1);
sleep(1);
}
}
int main(void){
int signum = 50 /*SIGINT*/;
//对2号信号进行捕获处理
if(signal(signum, sigfun) == SIG_ERR){
perror("signal");
return -1;
}
//父进程屏蔽2号信号
printf("%d进程:屏蔽%d号信号\n", getpid(), signum);
sigset_t sigset;//信号集变量
sigemptyset(&sigset);//清空信号集
sigaddset(&sigset, signum);//添加2号信号到信号集
sigset_t oldset;//用来输出以前的信号掩码
if(sigprocmask(SIG_SETMASK, &sigset, &oldset) == -1){
perror("sigprocmask");
return -1;
}
//父进程创建子进程
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
//父进程负责向父进程发送2号信号
if(pid == 0){
for(int i = 0; i < 5; i++){
printf("%d进程:我要向父进程发送%d号信号\n", getpid(), signum);
kill(getppid(), signum);
}
return 0;
}
//父进程假装更新数据库
updatedb();
//父进程解除对2号信号的屏蔽
printf("%d进程:解除对%d号信号的屏蔽\n", getpid(), signum);
if(sigprocmask(SIG_SETMASK, &oldset, NULL) == -1){
perror("sigprocmask");
return -1;
}
for(;;);
return 0;
}
tarena@TNV:day13$ vi sigmask.c
tarena@TNV:day13$ gcc sigmask.c -o sigmask
tarena@TNV:day13$ ./sigmask
5380进程:屏蔽2号信号
正在更新第1条数据...
5381进程:我要向父进程发送2号信号
5381进程:我要向父进程发送2号信号
5381进程:我要向父进程发送2号信号
5381进程:我要向父进程发送2号信号
5381进程:我要向父进程发送2号信号
正在更新第2条数据...
正在更新第3条数据...
正在更新第4条数据...
正在更新第5条数据...
5380进程:解除对2号信号的屏蔽
5380进程:捕获到2号信号
^\退出
tarena@TNV:day13$ vi sigmask.c
tarena@TNV:day13$ gcc sigmask.c -o sigmask
tarena@TNV:day13$ ./sigmask
5390进程:屏蔽50号信号
正在更新第1条数据...
5391进程:我要向父进程发送50号信号
5391进程:我要向父进程发送50号信号
5391进程:我要向父进程发送50号信号
5391进程:我要向父进程发送50号信号
5391进程:我要向父进程发送50号信号
正在更新第2条数据...
正在更新第3条数据...
正在更新第4条数据...
正在更新第5条数据...
5390进程:解除对50号信号的屏蔽
5390进程:捕获到50号信号
5390进程:捕获到50号信号
5390进程:捕获到50号信号
5390进程:捕获到50号信号
5390进程:捕获到50号信号
^\退出