C++基础入门【5】- Unix操作系统

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 730 16:53 a.txt
tarena@TNV:day05$ ls -l -i
总用量 4
1855138 -rw-rw-r-- 1 tarena tarena 16 730 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 730 16:53 a.txt
1855138 -rw-rw-r-- 2 tarena tarena 16 730 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 730 16:53 a.txt
1855138 -rw-rw-r-- 2 tarena tarena 16 730 16:53 b.txt
1855128 lrwxrwxrwx 1 tarena tarena  5 730 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 730 16:53 a.txt
-rw-rw-r-- 2 tarena tarena   16 730 16:53 b.txt
-rwxrwxr-x 1 tarena tarena 8760 730 19:08 open
-rw-rw-r-- 1 tarena tarena  361 730 18:36 open.c
-rwxrwxr-x 1 tarena tarena    0 730 19:09 open.txt
lrwxrwxrwx 1 tarena tarena    5 730 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 81 16:22 access
-rw-rw-r-- 1 tarena tarena  622 81 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 81 16:22 access
-rw-rw-r-- 1 tarena tarena  622 81 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8808 81 16:39 trunc
-rw-rw-r-- 1 tarena tarena  488 81 16:39 trunc.c
-rw-rw-r-- 1 tarena tarena    5 81 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 81 16:22 access
-rw-rw-r-- 1 tarena tarena  622 81 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8808 81 16:39 trunc
-rw-rw-r-- 1 tarena tarena  488 81 16:42 trunc.c
-rw-rw-r-- 1 tarena tarena    6 81 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 81 16:22 access
-rw-rw-r-- 1 tarena tarena  622 81 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8864 81 16:50 trunc
-rw-rw-r-- 1 tarena tarena  566 81 16:50 trunc.c
-rw-rw-r-- 1 tarena tarena    3 81 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 81 16:22 access
-rw-rw-r-- 1 tarena tarena  622 81 16:21 access.c
-rwxrwxr-x 1 tarena tarena 8896 81 16:53 trunc
-rw-rw-r-- 1 tarena tarena  635 81 16:53 trunc.c
-rw-rw-r-- 1 tarena tarena    8 81 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 81 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 82 12:02 fmap
-rw-rw-r-- 1 tarena tarena  660 82 12:03 fmap.c
-rw-rw-r-- 1 tarena tarena    0 82 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 82 12:02 fmap
-rw-rw-r-- 1 tarena tarena  754 82 12:11 fmap.c
-rw-rw-r-- 1 tarena tarena    0 82 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 82 12:12 fmap
-rw-rw-r-- 1 tarena tarena  757 82 12:11 fmap.c
-rw-rw-r-- 1 tarena tarena 4096 82 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 84 13:36 exec
1967572 -rw-rw-r-- 1 tarena tarena  383 84 13:36 exec.c
1967567 -rwxrwxr-x 1 tarena tarena 8704 84 12:58 new
1967570 -rw-rw-r-- 1 tarena tarena  406 84 12:58 new.c
1967564 -rwxrwxr-x 1 tarena tarena 8928 84 11:46 nohang
1967569 -rw-rw-r-- 1 tarena tarena  788 84 11:46 nohang.c
1967559 -rwxrwxr-x 1 tarena tarena 8920 84 10:34 wait
1967561 -rwxrwxr-x 1 tarena tarena 9032 84 11:00 waitall
1967565 -rw-rw-r-- 1 tarena tarena  856 84 11:00 waitall.c
1967563 -rw-rw-r-- 1 tarena tarena  820 84 10:34 wait.c
1967562 -rwxrwxr-x 1 tarena tarena 8928 84 11:26 waitpid
1967566 -rw-rw-r-- 1 tarena tarena  662 84 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 84 13:51 exec
-rw-rw-r-- 1 tarena tarena  502 84 13:51 exec.c
-rwxrwxr-x 1 tarena tarena 8704 84 12:58 new
-rw-rw-r-- 1 tarena tarena  406 84 12:58 new.c
-rwxrwxr-x 1 tarena tarena 8928 84 11:46 nohang
-rw-rw-r-- 1 tarena tarena  788 84 11:46 nohang.c
-rwxrwxr-x 1 tarena tarena 8920 84 10:34 wait
-rwxrwxr-x 1 tarena tarena 9032 84 11:00 waitall
-rw-rw-r-- 1 tarena tarena  856 84 11:00 waitall.c
-rw-rw-r-- 1 tarena tarena  820 84 10:34 wait.c
-rwxrwxr-x 1 tarena tarena 8928 84 11:26 waitpid
-rw-rw-r-- 1 tarena tarena  662 84 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 84 16:01 exec
-rw-rw-r-- 1 tarena tarena  882 84 14:22 exec.c
-rwxrwxr-x 1 tarena tarena 8872 84 16:01 forkexec
-rw-rw-r-- 1 tarena tarena  999 84 16:00 forkexec.c
-rwxrwxr-x 1 tarena tarena 8704 84 16:01 new
-rw-rw-r-- 1 tarena tarena  406 84 15:54 new.c
-rwxrwxr-x 1 tarena tarena 8928 84 11:46 nohang
-rw-rw-r-- 1 tarena tarena  788 84 11:46 nohang.c
-rwxrwxr-x 1 tarena tarena 8656 84 16:13 system
-rw-rw-r-- 1 tarena tarena  439 84 16:13 system.c
-rwxrwxr-x 1 tarena tarena 8920 84 10:34 wait
-rwxrwxr-x 1 tarena tarena 9032 84 11:00 waitall
-rw-rw-r-- 1 tarena tarena  856 84 11:00 waitall.c
-rw-rw-r-- 1 tarena tarena  820 84 10:34 wait.c
-rwxrwxr-x 1 tarena tarena 8928 84 11:26 waitpid
-rw-rw-r-- 1 tarena tarena  662 84 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号信号
^\退出

endl

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良辰美景好时光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值