目录
7、waitpid()使用方法及使用waitpid()解决僵尸进程
1)普通管道pipe---操作的是内核缓冲区(内存中的一块存储空间)
4)信号 对未决信号和阻塞信号的进一步理解以及屏蔽信号、解除屏蔽的方法
5)使用信号回收子线程 设计到SIGCHLD信号的屏蔽和信号的解除
6)sigaction结构体中的sa_mask信号集的解释 另在最后附上了常见信号的解释
(1)多线程概念以及优缺点 m2111
(2)创建一个线程 m2112
(6)实现线程分离:pthread_detach(),执行这个函数之后,这个线程就不要回收,系统自动回收
(7)创建多个线程+线程传参 线程传入参数为pthread_create()的最后一个形参
(8)线程属性设置函数 设置pthread_creat()第三个形参
(10)可以参考别人写的有关Linux多线程函数 m21110
(13)使用线程实现简单的生产者和消费者--另外附上xshell和xftp的使用方法
三、Linux下int main(int argc, char* argv[])的含义及解释
2、测试
6、SIGALRM信号和alarm()函数和signal()函数
(7)Linux常见的命令---pwd、mkdir、cp、mv、lsof、netstat
一、入门
1、C++编译环境安装以及第一个C++程序
0、在虚拟机下安装ubuntu系统,我的电脑太low了,ubuntu18版本装不了,然后就换了16版本,安装完成后安装vmware-tools,这个百度即可
1、安装vim
sudo apt-get install -y vim
2、配置vim
cd /etc/vim //切换到vim安装目录下
vim vimrc //用vim打开vimrc 刚开始进入是出于命令模式,可以按下a切换到输入模式,右键->Paste
//粘贴完毕后按下Esc由输入模式进入命令模式->输入:wq->回车保存并退出
vimrc文件设置参考博客顺序:
粘贴的内容为:
1 " 显示行号 2 set number 3 " 显示标尺 4 set ruler 5 " 历史纪录 6 set history=1000 7 " 输入的命令显示出来,看的清楚些 8 set showcmd 9 " 状态行显示的内容 10 set statusline=%F%m%r%h%w [FORMAT=%{&ff}] [TYPE=%Y] [POS=%l,%v][%p%%] %{strftime("%d/%m/%y - %H:%M")} 11 " 启动显示状态行1,总是显示状态行2 12 set laststatus=2 13 " 语法高亮显示 14 syntax on 15 set fileencodings=utf-8,gb2312,gbk,cp936,latin-1 16 set fileencoding=utf-8 17 set termencoding=utf-8 18 set fileformat=unix 19 set encoding=utf-8 20 " 配色方案 21 colorscheme desert 22 " 指定配色方案是256色 23 set t_Co=256 24 25 set wildmenu 26 27 " 去掉有关vi一致性模式,避免以前版本的一些bug和局限,解决backspace不能使用的问题 28 set nocompatible 29 set backspace=indent,eol,start 30 set backspace=2 31 32 " 启用自动对齐功能,把上一行的对齐格式应用到下一行 33 set autoindent 34 35 " 依据上面的格式,智能的选择对齐方式,对于类似C语言编写很有用处 36 set smartindent 37 38 " vim禁用自动备份 39 set nobackup 40 set nowritebackup 41 set noswapfile 42 43 " 用空格代替tab 44 set expandtab 45 46 " 设置显示制表符的空格字符个数,改进tab缩进值,默认为8,现改为4 47 set tabstop=4 48 49 " 统一缩进为4,方便在开启了et后使用退格(backspace)键,每次退格将删除X个空格 50 set softtabstop=4 51 52 " 设定自动缩进为4个字符,程序中自动缩进所使用的空白长度 53 set shiftwidth=4 54 55 " 设置帮助文件为中文(需要安装vimcdoc文档) 56 set helplang=cn 57 58 " 显示匹配的括号 59 set showmatch 60 61 " 文件缩进及tab个数 62 au FileType html,python,vim,javascript setl shiftwidth=4 63 au FileType html,python,vim,javascript setl tabstop=4 64 au FileType java,php setl shiftwidth=4 65 au FileType java,php setl tabstop=4 66 " 高亮搜索的字符串 67 set hlsearch 68 69 " 检测文件的类型 70 filetype on 71 filetype plugin on 72 filetype indent on 73 74 " C风格缩进 75 set cindent 76 set completeopt=longest,menu 77 78 " 功能设置 79 80 " 去掉输入错误提示声音 81 set noeb 82 " 自动保存 83 set autowrite 84 " 突出显示当前行 85 set cursorline 86 " 突出显示当前列 87 set cursorcolumn 88 "设置光标样式为竖线vertical bar 89 " Change cursor shape between insert and normal mode in iTerm2.app 90 "if $TERM_PROGRAM =~ "iTerm" 91 let &t_SI = "<Esc>]50;CursorShape=1x7" " Vertical bar in insert mode 92 let &t_EI = "<Esc>]50;CursorShape=0x7" " Block in normal mode 93 "endif 94 " 共享剪贴板 95 set clipboard+=unnamed 96 " 文件被改动时自动载入 97 set autoread 98 " 顶部底部保持3行距离 99 set scrolloff=3
vimrc文件配置以上参考博客链接
以上的vim配置方法会在当前行和列显示白线条,去掉的方法为:
cd /etc/vim //转到vim安装目录下
vim vimrc //打开vimrc,删掉set cursorline 和set cursorcolum
另外vimrc详细配置参考博客
最后使用cim输入cpp文件的效果如下:
2、安装g++和gcc编译器
在安装之前可以使用以下命令先检查一下存在g++和gcc与否
g++ version
gcc version
如果没有使用如下命令进行安装
cd ..
cd ..
cd .. //退出vim安装路径
sudo apt-get install -y g++
sudo apt-get install -y gcc
至此编译环境安装成功
3、创建一个cpp文件
cd /home //切换到home目录下
mkdir test //在home目录下创建一个test文件夹
cd test //进入刚刚创建的test文件夹下
vim test.cpp //创建cpp文件,有可能会有问题,直接回车即可
//然后按下a由命令模式进入输入模式,输入完毕后,按下Esc由输入模式进入命令模式->:wq->回车,保存并退出
g++ -o test test.cpp //编译,注意必须要有test.cpp所在的路径
./test //输出hello world
1 yiya@YiYA:~$ sudo apt-get install -y vim 2 [sudo] password for yiya: 3 Reading package lists... Done 4 Building dependency tree 5 Reading state information... Done 6 The following additional packages will be installed: 7 vim-common vim-runtime vim-tiny 8 Suggested packages: 9 ctags vim-doc vim-scripts vim-gnome-py2 | vim-gtk-py2 | vim-gtk3-py2 10 | vim-athena-py2 | vim-nox-py2 indent 11 The following NEW packages will be installed: 12 vim vim-runtime 13 The following packages will be upgraded: 14 vim-common vim-tiny 15 2 upgraded, 2 newly installed, 0 to remove and 335 not upgraded. 16 Need to get 6,755 kB of archives. 17 After this operation, 30.0 MB of additional disk space will be used. 18 Get:1 http://cn.archive.ubuntu.com/ubuntu xenial-updates/main amd64 vim-tiny amd64 2:7.4.1689-3ubuntu1.4 [446 kB] 19 Get:2 http://cn.archive.ubuntu.com/ubuntu xenial-updates/main amd64 vim-common amd64 2:7.4.1689-3ubuntu1.4 [103 kB] 20 Get:3 http://cn.archive.ubuntu.com/ubuntu xenial-updates/main amd64 vim-runtime all 2:7.4.1689-3ubuntu1.4 [5,169 kB] 21 Get:4 http://cn.archive.ubuntu.com/ubuntu xenial-updates/main amd64 vim amd64 2:7.4.1689-3ubuntu1.4 [1,036 kB] 22 Fetched 6,755 kB in 16s (411 kB/s) 23 (Reading database ... 177098 files and directories currently installed.) 24 Preparing to unpack .../vim-tiny_2%3a7.4.1689-3ubuntu1.4_amd64.deb ... 25 Unpacking vim-tiny (2:7.4.1689-3ubuntu1.4) over (2:7.4.1689-3ubuntu1.2) ... 26 Preparing to unpack .../vim-common_2%3a7.4.1689-3ubuntu1.4_amd64.deb ... 27 Unpacking vim-common (2:7.4.1689-3ubuntu1.4) over (2:7.4.1689-3ubuntu1.2) ... 28 Selecting previously unselected package vim-runtime. 29 Preparing to unpack .../vim-runtime_2%3a7.4.1689-3ubuntu1.4_all.deb ... 30 Adding 'diversion of /usr/share/vim/vim74/doc/help.txt to /usr/share/vim/vim74/doc/help.txt.vim-tiny by vim-runtime' 31 Adding 'diversion of /usr/share/vim/vim74/doc/tags to /usr/share/vim/vim74/doc/tags.vim-tiny by vim-runtime' 32 Unpacking vim-runtime (2:7.4.1689-3ubuntu1.4) ... 33 Selecting previously unselected package vim. 34 Preparing to unpack .../vim_2%3a7.4.1689-3ubuntu1.4_amd64.deb ... 35 Unpacking vim (2:7.4.1689-3ubuntu1.4) ... 36 Processing triggers for man-db (2.7.5-1) ... 37 Processing triggers for gnome-menus (3.13.3-6ubuntu3.1) ... 38 Processing triggers for desktop-file-utils (0.22-1ubuntu5.2) ... 39 Processing triggers for bamfdaemon (0.5.3~bzr0+16.04.20180209-0ubuntu1) ... 40 Rebuilding /usr/share/applications/bamf-2.index... 41 Processing triggers for mime-support (3.59ubuntu1) ... 42 Processing triggers for hicolor-icon-theme (0.15-0ubuntu1.1) ... 43 Setting up vim-common (2:7.4.1689-3ubuntu1.4) ... 44 Setting up vim-tiny (2:7.4.1689-3ubuntu1.4) ... 45 Setting up vim-runtime (2:7.4.1689-3ubuntu1.4) ... 46 Setting up vim (2:7.4.1689-3ubuntu1.4) ... 47 update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/vim (vim) in auto mode 48 update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/vimdiff (vimdiff) in auto mode 49 update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/rvim (rvim) in auto mode 50 update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/rview (rview) in auto mode 51 update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/vi (vi) in auto mode 52 update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/view (view) in auto mode 53 update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/ex (ex) in auto mode 54 yiya@YiYA:~$ ls 55 Desktop Downloads Music Public Videos 56 Documents examples.desktop Pictures Templates 57 yiya@YiYA:~$ sudo su 58 root@YiYA:/home/yiya# 123 59 123: command not found 60 root@YiYA:/home/yiya# ls 61 Desktop Downloads Music Public Videos 62 Documents examples.desktop Pictures Templates 63 root@YiYA:/home/yiya# cd /etc 64 root@YiYA:/etc# ls 65 acpi hosts profile.d 66 adduser.conf hosts.allow protocols 67 alternatives hosts.deny pulse 68 anacrontab hp python 69 apg.conf ifplugd python2.7 70 apm iftab python3 71 apparmor ImageMagick-6 python3.5 72 apparmor.d init rc0.d 73 apport init.d rc1.d 74 appstream.conf initramfs-tools rc2.d 75 apt inputrc rc3.d 76 aptdaemon insserv rc4.d 77 at-spi2 insserv.conf rc5.d 78 avahi insserv.conf.d rc6.d 79 bash.bashrc iproute2 rc.local 80 bash_completion issue rcS.d 81 bash_completion.d issue.net resolvconf 82 bindresvport.blacklist kbd resolv.conf 83 binfmt.d kernel rmt 84 bluetooth kernel-img.conf rpc 85 brlapi.key kerneloops.conf rsyslog.conf 86 brltty ldap rsyslog.d 87 brltty.conf ld.so.cache sane.d 88 ca-certificates ld.so.conf securetty 89 ca-certificates.conf ld.so.conf.d security 90 calendar legal selinux 91 chatscripts libao.conf sensors3.conf 92 compizconfig libaudit.conf sensors.d 93 console-setup libnl-3 services 94 cracklib libpaper.d sgml 95 cron.d libreoffice shadow 96 cron.daily lightdm shadow- 97 cron.hourly lintianrc shells 98 cron.monthly locale.alias signond.conf 99 crontab locale.gen signon-ui 100 cron.weekly localtime skel 101 cups logcheck speech-dispatcher 102 cupshelpers login.defs ssh 103 dbus-1 logrotate.conf ssl 104 dconf logrotate.d subgid 105 debconf.conf lsb-release subgid- 106 debian_version ltrace.conf subuid 107 default machine-id subuid- 108 deluser.conf magic sudoers 109 depmod.d magic.mime sudoers.d 110 dhcp mailcap sysctl.conf 111 dictionaries-common mailcap.order sysctl.d 112 dnsmasq.d manpath.config systemd 113 doc-base mime.types terminfo 114 dpkg mke2fs.conf thermald 115 drirc modprobe.d thunderbird 116 emacs modules timezone 117 environment modules-load.d tmpfiles.d 118 firefox mtab tpvmlp.conf 119 fonts mtools.conf ucf.conf 120 fstab nanorc udev 121 fuse.conf network udisks2 122 fwupd.conf NetworkManager ufw 123 gai.conf networks updatedb.conf 124 gconf newt update-manager 125 gdb nsswitch.conf update-motd.d 126 ghostscript opt update-notifier 127 gnome os-release UPower 128 gnome-app-install pam.conf upstart-xsessions 129 groff pam.d usb_modeswitch.conf 130 group papersize usb_modeswitch.d 131 group- passwd vim 132 grub.d passwd- vmware-caf 133 gshadow pcmcia vmware-tools 134 gshadow- perl vtrgb 135 gss pki wgetrc 136 gtk-2.0 pm wpa_supplicant 137 gtk-3.0 pnm2ppa.conf X11 138 guest-session polkit-1 xdg 139 hdparm.conf popularity-contest.conf xml 140 host.conf ppp zsh_command_not_found 141 hostname profile 142 root@YiYA:/etc# cd /vim 143 bash: cd: /vim: No such file or directory 144 root@YiYA:/etc# cd vim 145 root@YiYA:/etc/vim# ls 146 vimrc vimrc.tiny 147 root@YiYA:/etc/vim# vim vimrc 148 root@YiYA:/etc/vim# g++ version 149 g++: error: version: No such file or directory 150 g++: fatal error: no input files 151 compilation terminated. 152 root@YiYA:/etc/vim# gcc version 153 gcc: error: version: No such file or directory 154 gcc: fatal error: no input files 155 compilation terminated. 156 root@YiYA:/etc/vim# cd.. 157 cd..: command not found 158 root@YiYA:/etc/vim# cd .. 159 root@YiYA:/etc# cd .. 160 root@YiYA:/# cd .. 161 root@YiYA:/# sudo apt-get install -y g++ 162 Reading package lists... Done 163 Building dependency tree 164 Reading state information... Done 165 g++ is already the newest version (4:5.3.1-1ubuntu1). 166 0 upgraded, 0 newly installed, 0 to remove and 335 not upgraded. 167 root@YiYA:/# sudo apt-get install -y gcc 168 Reading package lists... Done 169 Building dependency tree 170 Reading state information... Done 171 gcc is already the newest version (4:5.3.1-1ubuntu1). 172 0 upgraded, 0 newly installed, 0 to remove and 335 not upgraded. 173 root@YiYA:/# ls 174 bin dev initrd.img lib64 mnt root snap tmp vmlinuz 175 boot etc initrd.img.old lost+found opt run srv usr 176 cdrom home lib media proc sbin sys var 177 root@YiYA:/# cd /home 178 root@YiYA:/home# ls 179 yiya 180 root@YiYA:/home# cd .. 181 root@YiYA:/# cd /Desptop 182 bash: cd: /Desptop: No such file or directory 183 root@YiYA:/# cd /home 184 root@YiYA:/home# cd /Desktop 185 bash: cd: /Desktop: No such file or directory 186 root@YiYA:/home# mkdir test 187 root@YiYA:/home# ls 188 test yiya 189 root@YiYA:/home# cd /test 190 bash: cd: /test: No such file or directory 191 root@YiYA:/home# cd test 192 root@YiYA:/home/test# vim test.cpp 193 Error detected while processing /usr/share/vim/vimrc: 194 line 56: 195 E518: Unknown option: histoty=1000 196 line 57: 197 E518: Unknown option: showmd 198 Press ENTER or type command to continue 199 root@YiYA:/home/test# g++ -o test.cpp 200 g++: fatal error: no input files 201 compilation terminated. 202 root@YiYA:/home/test# ./test 203 bash: ./test: No such file or directory 204 root@YiYA:/home/test# ls 205 test.cpp 206 root@YiYA:/home/test# vim test.cpp 207 Error detected while processing /usr/share/vim/vimrc: 208 line 56: 209 E518: Unknown option: histoty=1000 210 line 57: 211 E518: Unknown option: showmd 212 Press ENTER or type command to continue 213 root@YiYA:/home/test# g++ -o test.cpp 214 g++: fatal error: no input files 215 compilation terminated. 216 root@YiYA:/home/test# g++ -o test test.cpp 217 root@YiYA:/home/test# ./test 218 hello world! 219 root@YiYA:/home/test#
2、编译多个cpp文件
步骤如下:
cd /home/test //切换到home目录下的test目录下 sudo su //切换到管理员模式下去删除以前的编译输出文件test,否则删除不了 rm test vim funtion.h //创建h文件 vim function.cpp vim test.cpp g++ -o test function.h function.cpp test.cpp //将编译结果命名为test,其中h文件和cpp文件无先后顺序,也可以替换为g++ function.h function.cpp test.cpp -o test 顺序是灵活的 ./test //运行编译结果test
运行结果:
如果要多次运行g++ -o test test.cpp function.h function.cpp,则在运行之前要删除掉以前的编译结果test
3、使用make+makefile编译多个cpp文件
sudo apt-get install -y make //安装make
在/home/test目录下新建function.h function.cpp test.cpp
vim makefile //创建makefile,注意makefile无后缀名,如果原来的工程文件夹是在管理员模式下创建的,而makefile是在普通用户下创建的,那么vim最后保存的时候是保存不上的,必须转到管理员下才可以保存上。
makefile中的内容如下:
运行makefile
4、makefile系统的学习
(1)第一层
Linux编译过程:
1 预处理:使用把.h和.c文件展开成一个文件,并把宏定义替换为宏指代的东西,形成.iwenjian 2 编译 .i文件生成.s文件 3 汇编 .s文件生成.o文件 4 链接 .o文件生成.exe文件
转换成命令行(注"#"为makefile下的注释):
1 #假如路径下只有一个hello.c文件 2 #hello.i是目标生成文件,hello.i的生成依赖于hello.c 3 hello.i:hello.c 4 gcc -E hello.c -o hello.i #生成hello.i的命令 5 hello.S:hello.i 6 gcc -S hello.i -o hello.S #生成hello.s的命令 7 hello.o:hello.S 8 gcc -c hello.S -o hello.o #生成hello.o的命令 9 hello:hello.o 10 gcc hello.o -o hello #生成可执行文件hello的命令
正确的计算机编译过程一个是上面这样的,但是在makefile中要反着写,以形成依赖关系,如下:
1 #makefile书写方法正确顺序如下: 2 hello:hello.o 3 gcc hello.o -o hello #生成hello的命令 4 hello.o:hello.S 5 gcc -c hello.S -o hello.o #生成hello.o的命令 6 hello.s:hello.i 7 gcc -S hello.i -o hello.S #生成hello.s的命令 8 #hello.i是目标生成文件,hello.i的生成依赖于hello.c 9 hello.i:hello.c 10 gcc -E hello.c -o hello.i #生成hello.i的命令 11 #伪目标: .PHONY 不需要输出的指令 12 .PHONY 13 #clear是自定义的 14 clear: 15 rm hello.o hello.s hello.i hello #删除rm后的这些文件 16 #要执行伪目标的话,要是终端中输入make clear 貌似也可以写成make clean,对应的在makefile中也要更改
1 #假设不同路径下中有circle.h、circle.c、cube.h、cube.c、main.h、main.c 2 #省去了预处理、编译和汇编步骤 3 test:circle.o cube.o main.o 4 gcc circle.o cube.o main.o -o test #由circle.o cube.o main.o生成test可执行文件,但是这些.o文件都没有,那么在下面生成即可 5 6 circle.o:circle.c 7 gcc -c circle.c -o circle.o #由circle.c生成circle.o 8 cube.o:cube.c 9 gcc -c cube.c -o cube.o #cube.c生成cube.o,cube.h包含在了cube.c中 10 main.o:main.c 11 gcc -c main.c -o main.o #main.c生成main.o 12 #.PHONY为伪目标关键字,clearall为自定义命令字 13 .PHONY 14 clearall: 15 rm -rf circle.o cube.o main.o test 16 clear: 17 rm -rf circle.o cube.o main.o
(2)第二层 变量替换
1 #第二层;变量符号 =(替换) +=(追加) :=(恒等于) 2 #TAR=test #给目标文件起别名为TAR TAR+=test1 #追加TAR,此时TAR就是test和test1 3 #OBJ=circle.o cube.o main.o 4 #CC:=gcc 5 test:circle.o cube.o main.o 6 gcc circle.o cube.o main.o -o test #由circle.o cube.o main.o生成test可执行文件,但是这些.o文件都没有,那么在下面生成即可 7 上面的test就可以用使用$(TAR)替换、circle.o cube.o main.o使用$(OBJ)替换,即: 8 $(TAR):$(OBJ) 9 $(CC) $(OBJ) -o $(TAR)
那么(1)中的举例就可以更改为:
1 #假设不同路径下中有circle.h、circle.c、cube.h、cube.c、main.h、main.c 2 #省去了预处理、编译和汇编步骤 3 TAR=test #给目标文件起别名为TAR TAR+=test1 #追加TAR,此时TAR就是test和test1 4 OBJ=circle.o cube.o main.o #给依赖文件circle.o、cube.o、main.0起别名为OBJ、circle.o、cube.o、main.o可以使用$(OBJ)替换 5 CC:=gcc #给gcc起别名为CC,则gcc可以使用$(CC)替换 6 $(TAR):$(OBJ) 7 $(CC) $(OBJ) -o test #由circle.o cube.o main.o生成test可执行文件,但是这些.o文件都没有,那么在下面生成即可 8 9 circle.o:circle.c 10 $(CC) -c circle.c -o circle.o #由circle.c生成circle.o 11 cube.o:cube.c 12 $(CC) -c cube.c -o cube.o #cube.c生成cube.o,cube.h包含在了cube.c中 13 main.o:main.c 14 $(CC) -c main.c -o main.o #main.c生成main.o 15 #.PHONY为伪目标关键字,clearall为自定义命令字 16 .PHONY 17 clearall: 18 rm -rf $(OBJ) $(TAR) 19 clear: 20 rm -rf $(OBJ)
(3)第三层 隐含规则
%.c表示任意的.c文件、%.o表示任意的.o文件、*.c和*.o表示所有的.c和.o文件
将(2)中的一部分命令行摘抄下来:
1 circle.o:circle.c 2 gcc -c circle.c -o circle.o #由circle.c生成circle.o 3 cube.o:cube.c 4 gcc -c cube.c -o cube.o #cube.c生成cube.o,cube.h包含在了cube.c中 5 main.o:main.c 6 gcc -c main.c -o main.o #main.c生成main.o
我们看到下面的命令行的目标文件都是.o文件,依赖文件都是.c文件,且都是由.c文件生成.o文件,故可以使用如下的语句进行替换:
1 %.o:%.c #.o文件为要生成的目标文件,要生成任意的.o文件需要使用任意的.c文件 2 $(CC) -c %.c -o %.o #任意的.c文件生成任意的.o文件
那么(2)中的举例就可以更改为:
1 假设不同路径下中有circle.h、circle.c、cube.h、cube.c、main.h、main.c 2 #省去了预处理、编译和汇编步骤 3 TAR=test #给目标文件起别名为TAR TAR+=test1 #追加TAR,此时TAR就是test和test1 4 OBJ=circle.o cube.o main.o 5 CC:=gcc 6 $(TAR):$(OBJ) 7 $(CC) $(OBJ) -o $(TAR) #由circle.o cube.o main.o生成test可执行文件,但是这些.o文件都没有,那么在下面生成即可 8 9 %.o:%.c #.o文件为要生成的目标文件,要生成任意的.o文件需要使用任意的.c文件 10 $(CC) -c %.c -o %.o #任意的.c文件生成任意的.o文件 11 12 #.PHONY为伪目标关键字,clearall为自定义命令字 13 .PHONY 14 clearall: 15 rm -rf $(OBJ) $(TAR) 16 clear: 17 rm -rf $(OBJ)
(4)第四层 通配符
$^表示所有的依赖文件、$@表示所有的目标文件、$<所有依赖文件的第一个文件
在(3)中的第7行的$(TAR) 即所有的目标文件,因此该行的$(TAR)可以使用$@替换
在(3)中的第7行的$(OBJ) 即所有的依赖文件,因此该行的$(TAR)可以使用$^替换
在(3)中的第10行的%.o 即所有的目标文件,因此该行的%.o可以使用$@替换
在(3)中的第10行的%.c 即所有的依赖文件,因此该行的%.c可以使用$^替换
那么(3)中的举例就可以更改为:
1 #假设不同路径下中有circle.h、circle.c、cube.h、cube.c、main.h、main.c 2 #省去了预处理、编译和汇编步骤 3 TAR=test #给目标文件起别名为TAR TAR+=test1 #追加TAR,此时TAR就是test和test1 4 OBJ=circle.o cube.o main.o 5 CC:=gcc 6 7 $(TAR):$(OBJ) 8 $(CC) $^ -o $@ #$@为所有的依赖文件,$^为所有的目标文件 9 10 %.o:%.c 11 $(CC) -c $^ -o $@ #$@为所有的依赖文件,$^为所有的目标文件 12 13 #.PHONY为伪目标关键字,clearall为自定义命令字 14 .PHONY 15 clearall: 16 rm -rf $(OBJ) $(TAR) 17 clear: 18 rm -rf $(OBJ)
(5)函数
没讲
二、Linux多进程相关
(0)并行、并发以及多进程多线程要解决的问题
(1)并行
并行是多核CPU下的概念,在多核CPU下,CPU1执行进程1、CPU2执行进程2
(2)并发
并发是经过CPU经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象。并发是一种现象,之所以能有这种现象的存在,和CPU多少无关,而是和进程调度以及上下文切换有关的。
(3)多进程和多线程要解决的问题
在程序中写下多进程或者是多线程代码主要是为了解决并发的问题,并行与否由操作系统的调度器决定。只不过调度算法会尽量让不同进程/线程使用不同的CPU核心,并行与否程序员无法控制,只能让操作系统决定。
(4)串行
串行表示所有任务都一一按先后顺序进行。串行意味着必须先执行完任务1,只有任务一完成了,才能执行任务二,然后再按照顺序执行后面的任务。
和稍后所解释的并行相对比,串行是一次只能取得一个任务,并执行这个任务。
1、线程基本知识
1 /* 2 程序: 编译好的二进制文件 3 进程: 运行起来的程序,站在程序猿的角度即运行一些列指令的过程,站在操作系统的角度即分配系统资源的基本单位 4 程序没有运行起来只占用磁盘空间,程序运行起来占用CPU和内存空间,内存占用系统资源 5 一个程序对应多个进程,如可以有多个qq运行 6 一个进程对应一个程序 7 多进程:在电脑上同时运行着qq、浏览器、网易云 8 假如只有一个cpu,那么在a时间运行qq、a+10时间开始运行浏览器,a+30时间运行网易云、a+40开始运行qq..... 9 */
一个进程有四个状态:就绪、运行、挂起、终止
挂起:主动失去CPU,如我想在qq接收一个文件,但是文件还没有来,此时主动放弃cpu
MMU(内存管理单元)的基本知识
1 /* 2 MMU:内存管理单元,虚拟内存地址,和物理内存地址进行映射虚拟内存和物理内存的映射 3 即我们定义int a=10; 会在虚拟内存上分配空间,也会在物理内存上找一个地方存储a=10 4 MMU还可以修改内存访问级别 5 */
进程控制块相关
1 进程控制块PCB:tast_struct是一个结构体,其中保存了进程信息 2 sudo grep -rn "struct task_strucr{"/usr/ 查找进程控制块 3 tast_struct结构体中的内容: 4 1)进程id,系统每个进程有唯一的id 5 2)进程运行的状态,有就绪、运行、挂起、终止等状态 6 3)描述虚拟地址空间的信息 7 4)进程切换时需要保存和恢复的一些CPU寄存器 8 5)虚拟地址空间信息 9 6)当前工作目录 10 7)umask掩码,掩码也是每个进程独有的 11 8)晚间描述符表 12 9)和信号相关信息 13 10)用户id和组id 14 11)进程可以使用的资源上限
环境变量
env命令可以查看所有的环境变量
getenv
2、fork()函数
1 //需要包含头文件#include<unistd.h> 2 pid_t fork(void) //创建新线程,返回值:失败时候返回-1,成功两次返回,父进程返回子进程id,子进程返回0 3 //调用该函数后就回产生一个父进程和一个子进程 4 pid_t getpid(void) //获得当前进程id 5 pid_t getppid(void) //获得当前进程的父进程id 头文件为#include
多线程执行过程
man getppid 可以查看需要包含的头文件
Linux下加或者不加 是有区别的,如下
这是由于第一个printf()没有加
,只有加了
才会输出到屏幕上,所以第一个printf()内容
会保存在输入缓冲区,在子进程中也会有Begin......字符串
1 #include <stdio.h> 2 #include <unistd.h> 3 int main() 4 { 5 printf("Begin...... "); 6 pid_t pid=fork(); //pid_t为变量类型名字,执行完该句后开始有两个进程 7 printf("End...... "); 8 return 0; 9 } 10 11 执行结果: 12 Begin...... 13 End...... 14 End......
1 #include <stdio.h> 2 #include <unistd.h> 3 int main() 4 { 5 printf("Begin......"); 6 pid_t pid=fork(); //pid_t为变量类型名字,执行完该句后开始有两个进程 7 printf("End...... "); 8 return 0; 9 } 10 执行结果: 11 Begin......End...... 12 Begin......End......
使用fork()返回值判断当前运行的是子线程还是父线程:
1 #include <stdi.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 int main() 5 { 6 printf("Begin......"); 7 pid_t pid=fork(); //pid_t为变量类型名字 8 if(pid<0) 9 { 10 perror("fork error"); 11 exit(1); 12 } 13 if(pid==0) 14 { 15 //如果是子进程 16 printf("I am a child,pid=%d,ppid=%d ",getpid(),getppid()); 17 } 18 else if(pid>0) 19 { 20 //如果是父进程,当前进程是父进程,所以getpid()获取的是当前父进程的id 21 printf(”childpid=%d,self=%d,ppid=%d ",pid,getpid(),getppid()); 22 } 23 printf("End......"); 24 return 0; 25 }
但是上面的程序有可能出现父进程先执行完,但是子进程还没有执行完,所以该子进程即为孤儿进程
解决方法如下(父进程睡一会):
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 int main() 5 { 6 printf("Begin......"); 7 pid_t pid=fork(); //pid_t为变量类型名字 8 if(pid<0) 9 { 10 perror("fork error"); 11 exit(1); 12 } 13 if(pid==0) 14 { 15 //如果是子进程 16 printf("I am a child,pid=%d,ppid=%d ",getpid(),getppid()); 17 } 18 else if(pid>0) 19 { 20 //如果是父进程,当前进程是父进程,所以getpid()获取的是当前父进程的id 21 printf(”childpid=%d,self=%d,ppid=%d ",pid,getpid(),getppid()); 22 sleep(1); 23 } 24 printf("End......"); 25 return 0; 26 }
使用fork()让一个父进程创建出5个子进程
1 //让一个父进程创建出5个子进程 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 int main() 6 { 7 int n=5; 8 int i=0; 9 pid_t pid; 10 for(int i=0;i<5;++i) 11 { 12 pid=fork(); 13 if(pid==0) 14 { 15 //如果是子进程 16 printf("I am a child,pid=%d,ppid=%d ",getpid(),getppid()); 17 } 18 else if(pid>0) 19 { 20 printf("I am father,pid=%d,ppid=%d ",getpid(),getppid()); 21 } 22 while(1) 23 sleep(1); 24 return 0; 25 } 26 }
但是上面执行创建完子线程后,子线程并没有跳出for循环,所以子线程也会创建子线程
这样就会创建出32个子线程
解决方法:让儿子没有生育能力即可,在执行完子线程后跳出for循环即可,但是父进程不可以跳出for循环
1 //让一个父进程创建出5个子进程 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 int main() 6 { 7 int n=5; 8 int i=0; 9 pid_t pid; 10 for(int i=0;i<5;++i) 11 { 12 pid=fork(); 13 if(pid==0) 14 { 15 //如果是子进程 16 printf("I am a child,pid=%d,ppid=%d ",getpid(),getppid()); 17 break; //子线程跳出for循环,防止子线程再次执行for循环,子线程再次创建子线程 18 } 19 else if(pid>0) 20 { 21 printf("I am father,pid=%d,ppid=%d ",getpid(),getppid()); 22 } 23 while(1) 24 sleep(1); 25 return 0; 26 } 27 }
1 ps aux 查看当前进程详细信息 2 ps ajx 可以查看到当前进程的父进程信息 3 kill -l 查看信号相关的信息 4 kill SIGKILL pid 杀死进程号位pid的进程
3、进程间数据共享
刚fork之后:
父子相同处:全局变量、.data(数据段)、.text(代码段)、栈、堆、环境变量、用户id宿主目录、进程工作目录、信号处理方式
父子不同处:进程id、fork()返回值、父进程id、进程运行时间、定时器、未决定信号
似乎,子进程赋值了父进程0~3G用户控件内容,以及父进程的PCB,但pid不同,但是父子进程间遵循读时共享写时复制的原则
读时共享(只读的数据子进程不复制)、写时复制(可写的数据子进程要复制一份)
进程间数据共享举例:
1 #include <stdi.h> 2 #include <unistd.h> 3 int var=100; 4 5 int main() 6 { 7 pid_t pid=fork(); 8 if(pid==0) 9 { 10 //son 11 //由于当前进程是子进程,所以getpid()返回时子进程的id,getppid()返回的是当前子进程父进程的id 12 printf("var=%d,child,pid=%d,ppid=%d ",var,getpid(),getppid()); 13 var=10001; 14 printf("var=%d,child,pid=%d,ppid=%d ",var,getpid(),getppid()); 15 sleep(3); //保证父进程中var可以成功被修改 16 } 17 else if(pid>0) 18 { 19 //parent 20 //由于当前进程是父进程,所以getpid()返回时父进程的id,getppid()返回的是当前父进程父进程的id 21 sleep(1); //保证子进程中的var可以成功被修改 22 printf("var=%d,parent,pid=%d,ppid=%d ",var,getpid(),getppid()); 23 var=2000; 24 printf("var=%d,parent,pid=%d,ppid=%d ",var,getpid(),getppid()); 25 } 26 27 return 0; 28 } 29 执行结果: 30 var=100,child,pid=4256,ppid=4255 31 var=1001,child,pid=4256,ppid=4255 32 var=100,parent,pid=4255,ppid=1545 33 var=2000,parent,pid=4255,ppid=1545 34 有可能在下面再打印一行这个 35 yiya@ubuntu:~linux/code/process$ var=1001,child,pid=4256,ppid=1 36 这表明父进程已死,子进程继续显示
4、exec()族函数
1 //execl()执行其他的程序函数,执行其他函数后一直执行下去,除非出错;执行程序必须加路径 2 int execl(const char *path,const char *arg,.../*(char*)NULL*/) 3 //execlp()执行程序的时候,使用环境变量,执行程序可以不用加路径 4 int execlp(const char *file,const char *arg,.../*(char*)NULL*/) //...的意思是变参 5 file要执行额程序 6 arg参数列表,一般第一个参数为要执行命令名 7 (char*)NULL表示参数列表后需要一个NULL作为结尾,起到哨兵的作用,告诉程序以后就没有参数了 8 返回值:只有失败才返回 9 导致execl()或execlp()函数返回的错误示例: 10 浮点型错误: int a=0; int b=10/a; 11 段错误:操作非法地址
1 //execl()函数示例 2 #include <unistd.h> 3 #include <stdio> 4 5 int main() 6 { 7 execl("/bin/ls","ls","-l",NULL); //第一个"ls"是ls命令所在的路径,第二个"ls"为参数列表的第一个参数,一般参数列表的第一个参数为要执行的命令名 8 perror("exec,err"); //如果上一句(ls -l)执行失败,才会执行这一句,实际上不会执行,因为ls -l不会出错 9 return 0; 10 }
1 //execlp()函数示例 2 #include <unistd.h> 3 #include <stdio> 4 5 int main() 6 { 7 execlp("ls","ls","-l",NULL); //第一个"ls"是形参file的实参,第二个"ls"为参数列表的第一个参数,一般参数列表的第一个参数为要执行的命令名 8 perror("exec,err"); //如果上一句(ls -l)执行失败,才会执行这一句,实际上不会执行,因为ls -l不会出错 9 return 0; 10 } 11 以上程序会执行ls -l这个命令行后结束
execlp()函数执行原理:
5、孤儿进程与僵尸进程
孤儿进程:父进程死了,子进程会被init进程领养
僵尸进程:子进程死了,父进程没有回收子进程的资源(PCB)
1 //孤儿进程示例:父进程死了,子进程会被init进程领养 2 #include <unistd.h> 3 #include <stdio> 4 5 int main() 6 { 7 pid_t pid=fork(); 8 if(pid==0) 9 { 10 //son 11 while(1) 12 { 13 printf("child,pid=%d,ppid=%d ",getpid(),getppid()); 14 sleep(1); 15 } 16 } 17 else if(pid>0) 18 { 19 //parent 20 printf("parent,pid=%d,ppid=%d ",getpid(),getppid()); 21 sleep(5); 22 printf("parent will die"); 23 } 24 return 0; 25 } 26 执行结果: 27 child,pid=4965,ppid=4964 28 parent,pid=4964,ppid=1545 29 child,pid=4965,ppid=4964 30 child,pid=4965,ppid=4964 31 child,pid=4965,ppid=4964 32 child,pid=4965,ppid=4964 33 parent will die 34 child,pid=4965,ppid=1 //此时子进程被init领养 35 child,pid=4965,ppid=1 36 child,pid=4965,ppid=1 37 child,pid=4965,ppid=1 38 ... 39 此时 40 ctrl+c结束不了,因为shell(终端)只和父进程有关,此时父进程没有了,只能是暴力终止 41 在另外一个终端下输入:kill -9 4965 子进程的id为4965,-9为kill的参数
1 //僵尸(zombie)进程示例:子进程死了,父进程没有回收子进程的资源(PCB) 2 #include <unistd.h> 3 #include <stdio> 4 5 int main() 6 { 7 pid_t pid=fork(); 8 if(pid==0) 9 { 10 //son 11 printf("child,pid=%d,ppid=%d ",getpid(),getppid()); 12 sleep(2); 13 printf("son will die"); 14 } 15 else if(pid>0) 16 { 17 //parent 18 while(1) 19 { 20 printf("parent,pid=%d,ppid=%d ",getpid(),getppid()); 21 sleep(1); 22 } 23 } 24 return 0; 25 } 26 在另外一个终端下输入:ps aux|grep a.out //a.out为上面程序编译的结果 27 Z+表示进程已死 28 僵尸进程回收子进程资源方法:杀死父进程,子进程被init领养,init负责回收子进程资源 29 kill -9 5193 //5193为父进程id,杀死父进程
6、wait()
1 wait():回收已经结束了的子进程资源,并查看死亡原因,如果子进程没有死,那么就阻塞等待 2 头文件(在man wait中可以查看到): 3 pid_t wait(int* status) 4 status传出参数,告诉你子进程为啥结束,由系统填充 5 子进程死亡原因(在man wait()可以查看到): 6 正常死亡status==WIFEXITED 7 如果WIFEXITED为真,使用WEXITSTATUS得到推出状态 8 非正常死亡WIFEFIGNALED 9 如果WIFEFIGNALED为真,使用WTERMSIG得到信号 10 返回值:成功则返回终止的子进程id,失败返回-1
使用wait()解决僵尸进程:即让主进程等待,阻塞在wait()这里,知道子线程结束
1 #include <stdio> 2 #include <unistd.h> 3 #include <sys/wait.h> 4 in main() 5 { 6 pid_t=fork(); 7 if(pid==0) 8 { 9 //son 10 printf("I am child,I will die "); 11 } 12 else if(pid>0) 13 { 14 //parent 15 printf("I am parent,I will wait for chid die "); 16 pid_t wpid=wait(NULL); //回收已经结束了的子进程资源,如果子进程没有结束,那么就阻塞在这里,等待子线程结束 17 printf("wait ok,wpid=%d,pid=%d",wpid,pid); 18 while(1) 19 sleep(1); //不让父进程死 20 } 21 return 0; 22 } 23 ps aux|grep a.out //a.out为上面程序生成的.o文件 24 如果出现Z+则说明有僵尸进程
1 //查看进程死亡原因 2 #include <stdio> 3 #include <unistd.h> 4 #include <sys/wait.h> 5 in main() 6 { 7 pid_t=fork(); 8 if(pid==0) 9 { 10 //son 11 printf("I am child,I will die "); 12 } 13 else if(pid>0) 14 { 15 //parent 16 printf("I am parent,I will wait for chid die "); 17 int status; 18 pid_t wpid=wait(status); //回收已经结束了的子进程资源,如果子进程没有结束,那么就阻塞在这里,等待子线程结束 19 printf("wait ok,wpid=%d,pid=%d",wpid,pid); 20 while(1) 21 sleep(1); //不让父进程死 22 } 23 return 0; 24 }
7、waitpid()使用方法及使用waitpid()解决僵尸进程
1 /* 2 waitpid()函数原型介绍 3 pid_t waitpid(int* status); 4 pid_t waitpid(pid_t pid,int* status,int options); 5 options有三种选择(这里只介绍两个参数): 6 options=0表示与wait()相同,如果子线程没有结束,就会阻塞住主线程 7 option=WNOHANG表示如果子进程没有结束,waitpid()不会阻塞住主线程,主线程可以做一些其他的事情 8 pid表示回收哪个线程 9 pid<-1回收组id 10 pid=-1表示回收任何子线程 11 pid=0表示回收和调用进程组id相同组内的子线程 12 pid>0表示回收哪个线程id为pid的线程 13 返回值 14 如果设置了WNOHANG,且子线程没有退出,返回0 15 如果设置了WNOHANG,切子线程已退出,返回已退出子线程的pid 16 如果失败(没有子进程或者是子进程全部结束)返回-1 17 */
1 #include <stdio> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/wait.h> 6 in main() 7 { 8 pid_t pid=fork(); 9 if(pid==0) 10 { 11 //son 12 printf("child,pid=%d ",getpid()); 13 sleep(2); 14 } 15 else if(pid>0) 16 { 17 //parent 18 printf("parent,pid=%d ",getpid()); 19 int ret; 20 //-1表示回收任何子线程,如果子线程没有结束,则waitpid返回0 21 while((ret=waitpid(-1,NULL,WNOHANG))==0) 22 { 23 sleep(2); 24 } 25 ret=waitpid(-1,NULL,WNOHANG); //再执行一遍waitpid(),此时上面的waitpid()已经将子线程回收,这里waitpid()一定返回-1 26 if(ret<0) 27 perror("wait err"); //打印wait err: No child processes 28 printf("ret=%d ",ret); 29 while(1) 30 sleep(1); 31 } 32 return 0; 33 }
1 //使用waitpid回收多个子线程 2 #include <stdio> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <sys/types.h> 6 #include <sys/wait.h> 7 in main() 8 { 9 int n=5; 10 int i=0; 11 pid_t pid; 12 //产生多个子线程 13 for(i=0;i<n;++i) 14 { 15 pid=fork(); 16 if(pid==0) 17 { 18 break; //如果是子线程则退出for循环,防止子线程再次产生子线程 19 } 20 } 21 22 if(i==5) 23 { 24 //parent 25 printf("parent "); //Linux最好每一个printf()都加上换行符 26 while(1) 27 { 28 pid_t wpid=waitpid(-1,NULL,WNOHANG); //如果没有子线程结束,则阻塞在这里 29 if(wpid==-1) //如果waitpid()返回值为-1,说明所有的子线程都回收完毕 30 { 31 //如果pid=-1说明所有的线程都被回收了,则结束while循环 32 break; 33 } 34 else if(wpid>0) //如果waitpid()回收已结束的子线程成功,返回值为已结束的线程的id,且该id大于0 35 { 36 //如果还有线程没有被回收 37 printf("waitpid,wpid=%d ",wpid); 38 } 39 } 40 while(1) 41 { 42 //printf("main_treading running "); 43 sleep(1); //保证主线程不会退出 44 } 45 } 46 47 if(i<5) 48 { 49 //son 50 sleep(i); 51 printf("child,pid=%d ",getpid()); 52 } 53 54 55 return 0; 56 }
使用多个waitpid()回收多个线程执行结果:
程序执行过程:
需要注意的是:子线程1、子线程2、子线程3、...、子线程5都是单独拿出来的额一个waitTest.c文件,在子线程1的wiatTest.c文件中,i==0,在子线程2的waitTest.c中i==1
同理子线程3、4、5的waitTest.c中i分别恒等于2、3、4,上面图中写错了,应该改为“i=0时,子线程1到这里去执行”、应该改为“i=1时,子线程2到这里去执行”
8、homweork
创建子线程,调用fork()之后,在子进程调用自定义程序(存在浮点型错误),用wait()回收,查看推出状态
9、进程间通信方式
#进程间通信(IPC)
通过内核提供的一块缓冲区,进行数据交换
IPC通信方式(重点掌握前三种):
* pipe 管道---最简单
* fifo 有名管道
* mmap 文件映射IO--速度最快
* 本地socket 最稳定
* 信息 ---携带的信息量最小
* 共享内存
* 消息队列
常见的数据通信方式
单工:在任意时刻,A--->B 如广播,人只能听
半双工:同一个时刻,数据只可以朝一个方向流,如对讲机,如在t1时刻A--->B,在t2时刻B--->A
双工: 在任意时刻A--->B、B--->A
1)管道(匿名管道)----半双工通信方式---操作的是内核缓冲区(内存中的一块存储空间)
需要包含的头文件:
1 #include <unistd.h>
函数原型:
1 int pipe(int pipefd[2]);
参数介绍:
pipefd读写文件描述符,0-代表读(pipefd[0]),1-代表写
返回值:
传输成功返回0,失败返回-1
1 #include <stdio.h> 2 #include <unistd.h> 3 4 int main(){ 5 int fd[2]; //fd[0]表示读到的数据(管道的读端),fd[1]表示写入的数据(管道的写端) 6 pipe(fd); //创建管道 7 8 pid_t pid=fork(); //创建子线程 9 10 if(pid==0){ 11 //son 12 sleep(3); 13 //将hello这个5个字节数据传送到fd[1]所指向的文件中,此时fd指向的文件为管道的写端 14 write(fd[1],"hello",5); //子线程向管道中写入数据,写入的数据为字符串hello,5为要写入的字符的个数 15 } 16 else if(pid>0){ 17 //parent 18 char buf[12]={0}; //buff数组的大小12是随便写的 19 //将fd所指向的文件中的sizeof(buf)个字节传送到buf中 20 int ret=read(fd[0],buf,sizeof(buf)); //管道中没有数据的时候,会阻塞在这里.read()返回值为实读到的字节数 21 if(ret>0){ 22 //将buf中ret个字节传送到STDOUT_FILENO中 23 write(STDOUT_FILENO,buf,ret); //打印buf中的内容 24 } 25 26 } 27 28 return 0; 29 }
1 //父子进程实现ps aux|grep xxx 2 //让子进程实现ps aux命令,父进程实现grep xxx命令 3 4 #include <stdio.h> 5 #include <unistd.h> //for STDOUT_FILENO、STDIN_FILENO 6 7 int main(){ 8 int fd[2]; 9 pipe(fd); //创建管道 10 11 pid_t pid=fork(); 12 if(pid>0){ 13 //son 14 //1.重定向 15 dup2(fd[1],STDOUT_FILENO); //标准输出重定向到管道的写端 16 //2.使用execlp()转到ps命令 17 execlp("ps","ps","aux",NULL); 18 } 19 else if(pi>0){ 20 //parent 21 //1.重定向 22 dup2(fd[0],STDIN_FILENO); //标准输入重定向到管道的读端 23 //2.execlp() 24 execlp("grep","grep","bash",NULL); //该.c文件生成的可执行文件名字必须为bash 25 } 26 27 return 0; 28 } 29 30 /* 31 ps aux|grep bash 查看bash可执行文件中的线程执行情况 32 grep是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。 33 kill -s 9 5332 杀死id为5332的进程 34 */
测试结果:
grep "hello"
//等待输入:
0000
xxxx
hello //此时可以匹配到grep后的字符串,所以在终端下输出hello
同理grep bash会等待终端输入,然后匹配bash
注意:
在Linux系统调用中,标准输入描述字用stdin,标准输出用stdout,标准出错用stderr表示,但在一些调用函数,引用了STDIN_FILENO表示标准输入才,同样,标准出入用STDOUT_FILENO,标准出错用STDERR_FILENO.
他们的区别:
stdin等是FILE *类型,属于标准I/O,在<stdio.h>。
STDIN_FILENO等是文件描述符,是非负整数,一般定义为0, 1, 2,属于没有buffer的I/O,直接调用系统调用,在<unistd.h>。
管道的读写行为:
1、读 (1)写端全部关闭:read会读到0,相当于读到文件尾 (2)写端没有全部关闭: 写端有数据---read读到数据 写端没有数据---read阻塞,fcnt函数可以更改非阻塞 2、写 (1)读端全部关闭:会报错,产生一个信号SIGPIPE,程序会异常终止。相当于水管,进水口一直进,但出水口堵死了 (2)读端未全部关闭: 管道已满---write阻塞(读端一直不读,写端狂写) 管道未满---write正常写入
1 //写端全部关闭 2 #include <stdio.h> 3 #include <unistd.h> 4 5 int main(){ 6 int fd[2]; //fd[0]表示读到的数据,fd[1]表示写入的数据 7 pipe(fd); //创建管道 8 9 pid_t pid=fork(); //创建子线程 10 11 if(pid==0){ 12 //son 13 sleep(3); 14 close(fd[0]); //由于在本例中要让子进程给管道的一端写,所以要管理子进程在管道的读 15 write(fd[1],"hello",5); //子线程向管道中写入数据,写入的数据为字符串hello,5为要写入的字符的个数 16 close(fd[1]); //此时子线程的写端全部关闭,如果父线程的读函数(read())返回值为0,相当于读到文件末尾 17 } 18 else if(pid>0){ 19 //parent 20 close(fd[1]); //由于在本例中要让父进程给管道的一端读,所以要管理父进程在管道的写 21 char buf[12]={0}; //buff数组的大小12是随便写的 22 23 //等待父进程读完,如果读完,则read()返回0 24 while(1){ 25 int ret=read(fd[0],buf,sizeof(buf)); //管道中没有数据的时候,会阻塞在这里 26 if(ret==0){ 27 printf("read over! "); 28 break; 29 } 30 if(ret>0){ 31 write(STDOUT_FILENO,ret); //向控制台(终端)写(打印) 32 } 33 } 34 35 } 36 37 return 0; 38 }
1 //读端全部关闭:会报错,产生一个信号SIGPIPE,程序会异常终止 2 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <sys/types.h> //for wait()、waitpid() 6 #include <sys/wait.h> //for wait()、waitpid() 7 8 int main(){ 9 int fd[2]; //fd[0]表示读到的数据,fd[1]表示写入的数据 10 pipe(fd); //创建管道 11 12 pid_t pid=fork(); //创建子线程 13 14 if(pid==0){ 15 //son 16 sleep(3); //子线程睡了3s之后,父进程已经有足够的时间将它自己那一端的读和写关闭了 17 close(fd[0]); //由于在本例中要让子进程给管道的一端写,所以要管理子进程在管道的读 18 write(fd[1],"hello",5); //子线程向管道中写入数据,写入的数据为字符串hello,5为要写入的字符的个数 19 close(fd[1]); //此时子线程的写端全部关闭,如果父线程的读函数(read())返回值为0,相当于读到文件末尾 20 } 21 else if(pid>0){ 22 //parent 23 close(fd[1]); //此时父进程的写端关闭了 24 close(fd[0]); //此时父进程的读端也关闭了 25 26 int status; 27 wait(&status); //此时父进程是被信号SIGPIPE杀死的,故可以使用wait(int* status)的形参来查看线程死亡原因 28 if(WIFSIGNALED(status)){ 29 printf("parent is killed by %d ",WTERMSIG(status)); //使用WTERMSIG()得到使使线程结束的信号 30 } 31 32 while(1){ 33 sleep(1); 34 } 35 } 36 37 return 0; 38 }
程序执行结果:
parent is killed by 13
然后使用命令kill -l 来查看13代表的信号是啥(此时13代表的是SIGPIPE)
管道大小查看方法
使用命令ulimit -a来查看
可以找到pipe size
pipe size (512 bytes,-p)8 //表示8个512字节
也可以使用函数来查看管道的大小
long fpathconf(int fd,int name);
第一个形参fd填fd[0]或fd[1]即可。
其中fd[0]和fd[1]来自于:
int fd[2];
pipe(fd);
第二个参数name是一个宏定义:_PC_PIPE_BUF表示管道的参数
管道的优劣:
(1)优点:简单、使用方便 (2)缺点:只能有血缘关系的进程间通信,只能半双工通信,如果需要双向通信需要创建多个管道
2)FIFO(命名管道)
实现无血缘关系进程通信
创建一个管道的伪文件,该伪文件相当于一个缓冲区,不同进程之间操作这个缓冲区即可实现进程间通信
内核会针对fifo伪文件开辟一个缓冲区,操作fifo伪文件,还可以操作缓冲区,实现进程间通信
实际上即为文件读写,这个文件类型为fifo文件
创建FIFO命令行:mkfifo myfifo //创建一个名字为myfifo的管道伪文件
ls -lrt //查看
注意:fifo文件中的内容读了以后对应的内容就没有了
man 3 mkfifo //查看mkfifo函数,3表示在第三章
创建FIFO管道函数
int mkfifo(const char* pathname,mode_t mode);
示例1:
首先使用命令行创建fifo文件,然后通过04_fifo_w.c向fifo文件写,再通过04_fifo_r.c读fifo文件中的数据
命令行部分:
mkfifo myfifo //先使用命令行创建fifo文件myfifo
touch 04_fifo_w.c //创建04_fifo_w.c文件
vim 04_fifo_w.c //使用vim打开刚 刚创建的.c文件
1 #include <stdio.h> 2 #include <unistd.h> //for fork()、open() 3 #include <sys/types.h> //for wait() 4 #include <sys/stat.h> //for wait() 5 #include <fcnt1.h> //for open() 6 #include <string.h> //for strlen() 7 8 int main(int argc,char* argv[]){ 9 if(argc != 2){ 10 printf("./a.out fifoname "); 11 return -1; 12 } 13 //当前目录下有一个myfifo文件 14 //打开myfifo文件,如果open()打开文件成功,返回一个文件文件描述符,打开失败则返回-1 15 //如果先开启04_fifo_w.c,那么会阻塞在open()这里,直到04_fifo_r.c启动为止 16 int fd=open(argv[1],O_WRONLY | O_CREAT); //argv[1]为通过终端传进来的欲打开的文件的路径,O_WEONLY表示以只写的方式打开文件,O_CREAT表示没有argv[1]对应的文件,则创建文件 17 18 //写操作 19 char buf[256]; 20 int num=1; 21 while(1){ 22 //一直写 23 memset(buf,0x00,sizeof(buf)); //初始化buf中的内容为0 24 //将xiaoming%04d中的%04d使用num替代后,写入buf中去 25 sprintf(buf,"xiaoming%04d",num++); //将num的值补充道%04d的位置上,如果num的值不足4位,则在前面补充空格,超过4位,则只取前面4位 26 write(fd,buf,strlen(buf)); //将buf中的内容写到打开的文件描述符为fd的文件中 27 sleep(1); 28 } 29 //关闭描述符 30 close(fd); 31 return 0; 32 }
touch 04_fifo_r.c //创建04_fifo_w.c文件
vim 04_fifo_r.c //使用vim打开刚 刚创建的.c文件
1 #include <stdio.h> 2 #include <unistd.h> //for fork()、open() 3 #include <sys/types.h> //for wait() 4 #include <sys/stat.h> //for wait() 5 #include <fcnt1.h> //for open() 6 #include <string.h> //for strlen() 7 8 int main(int argc,char* argv[]){ 9 if(argc != 2){ 10 printf("./a.out fifoname "); 11 return -1; 12 } 13 14 int fd=open(argv[1],O_RDONLY); //以只读的方式打开argv[1]路径下的文件 15 16 char buf[256]; 17 int ret; 18 while(1){ 19 //一直读 20 ret=read(fd,buf,sizeof(buf)); //从fd文件描述符指向的文件中读sizeof(buf)个字符到buf中 21 if(ret>0){ 22 printf("read: %s ",buf); //打印读到的字符串 23 } 24 } 25 26 return 0; 27 }
fifo注意事项:
打开fifo文件时,read端会祖肃等待write端open;同理write端也会阻塞等待read端open
执行过程:
1、拖文件到LInux下
2、运行
第二次运行:
下一步:按下读的回车键
修改程序后第三次运行结果:
3)mmap()
将一个文件中的内容使用mmap()函数映射到内存中的一块区域,进而可以对该内存区域进行读写操作,如果进程1对了文件A使用了mmap()函数进行了内存映射,且进程2也对文件A使用mmap()进行了内存映射,且mmap()的flags参数设置为了MAP_SHARED,则进程1和进程2可以通过映射的内存,交换数据
创建映射区函数(mmap()函数原型):
void* mmap(void* addr,size_t length,int prot,int flags,inf fd,off_t offset);
需要包含的头文件:
#include <sys/mman.h>
参数说明:
addr:传入地址,现在传入NULL即可
length: txt文件中需要被映射的文件长度
prot主要被使用的宏定义有
PROT_READ: 代表可读
PROT_WRITE: 代表可写
PROT_READ | PROT_WRITE 表示映射区即可读也可写
flags主要被使用的宏定义有:
MAP_SHARED:映射区是共享的,如果是共享的,对内存的修改会影响到原文件
MAP_PRIVATE:映射区是私有的,如果是私有的,对内存的修改不会影响到原文件
fd: 文件描述符(mmap是将一个文件映射到内存上,所以要先open()一个文件)
offset: 偏移量,即文件从哪里开始映射
返回值:
成功:返回可用内存首地址
失败:返回MAP_FAILED
释放映射区函数(函数原型):
int munmap(void* addr, size_t length);
参数说明:
addr:传mmap的返回值
length:mmap创建的长度
返回值:成功返回0,失败返回-1
示例1:
不使用进程操作映射区,只是修改了映射区的内容:
先创建一个文件:
touch mem.txt
vim mem.txt //随意向里面添加一行字母
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 #include <sys/mman.h> //for mmap() 7 8 int main(){ 9 int fd=open("mem.txt",O_RDWR); //打开的时候必须以可读写的权限打开 10 11 char* mem=mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); 12 //mmap()使用的时候必须判断返回值 13 if(mem==MAP_FAILED){ 14 perror("mmap error:"); 15 return -1; 16 } 17 18 //现在内存中有数据,就可以使用str系列函数对其进行操作 19 //由于内存中的映射区是MAP_SHARED,故对映射区操作在原文件中也会发生变化 20 strcpy(mem,"hello"); //如果mmap()在内存中创建映射区域成功,则返回该映射区的收地址,所以该句的意思是想内存中收地址为mem的地方复制数据 21 22 23 24 //释放内存 25 munmap(mem,8); 26 close(fd); 27 28 return 0; 29 }
示例2:
使用mmap实现父子进程间通信
原理:父子进程都可以拿到内存映射区的地址,在一个进程中改变该映射区的内容,在另一个进程都可以发生相应的改变
1 //06_map_child.char 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/wait.h> 6 #include <sys/stat.h> 7 #include <sts/mman.h> //for mmap() 8 #include <fcntl.h> 9 10 11 int main(){ 12 //先创建映射区 13 int fd=open("mem.txt",O_RDWR); 14 int length=4; //txt文件中需要被映射的文件长度 15 int* mem=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); //如果内存开辟成功,则返回该内存的首地址,所以可以使用int*来接收该地址 16 if(mem==MAP_FAILED){ 17 perror("mmap error:"); 18 return -1; 19 } 20 21 //fork()子进程 22 pid_t pid=fork(); 23 24 //父进程和子进程之间交替更改映射区数据 25 if(pid==0){ 26 //son 27 *mem=100; //改变映射区(内存)首地址为mem中的内容 28 printf("child *mem=%d ",*mem); 29 sleep(3); 30 printf("child *mem=%d ",*mem); //打印父进程中修改的内容 31 } 32 else if(pid>0){ 33 //parent 34 sleep(1); //父进程睡1s,此时子进程肯定将首地址为mem中的内容修改了 35 printf("parent *mem=%d ",*mem); //打印子进程中修改的内容 36 *mem=1001; 37 printf("parent *mem=%d ",*mem); 38 39 wait(NULL); //等待子进程结束,后父进程再结束 40 } 41 42 //释放 43 munmap(mem,length); //如果mmap()成功开辟内存,则返回开辟的内存的首地址 44 close(fd); 45 46 return 0; 47 }
在以上的代码中,mem.txt并没有起作用,只是打开用了一下;可以使用匿名映射
即在mmap()中添加MAP_ANONYMOUS(或MAP_ANON),此时可以不用打开一个txt文件
void* mem=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
以下命令行可以将06_mmap_child.c中的前七行重定向(复制)到07_mmap_anon.c中
head -7 06_mmap_child.c > 07_mmap_anon.c
1 //匿名映射07_mmap_anon.c 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/wait.h> 6 #include <sys/stat.h> 7 #include <sts/mman.h> //for mmap() 8 #include <fcntl.h> 9 10 int main(){ 11 int length=4; 12 //创建映射区 13 int* mem=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0); 14 if(mem == MAP_FAILED){ 15 perror("mmap err"); 16 return -1; 17 } 18 19 //创建子进程 20 pid_t pid=fork(); 21 22 //父进程和子进程之间交替更改映射区数据 23 if(pid==0){ 24 //son 25 *mem=101; //改变映射区(内存)首地址为mem中的内容 26 printf("child *mem=%d ",*mem); 27 sleep(3); 28 printf("child *mem=%d ",*mem); //打印父进程中修改的内容 29 } 30 else if(pid>0){ 31 //parent 32 sleep(1); //父进程睡1s,此时子进程肯定将首地址为mem中的内容修改了 33 printf("parent *mem=%d ",*mem); //打印子进程中修改的内容 34 *mem=10001; 35 printf("parent *mem=%d ",*mem); 36 37 wait(NULL); //等待子进程结束,后父进程再结束 38 } 39 40 //释放内存 41 munmap(mem,length); //如果mmap()成功开辟内存,则返回开辟的内存的首地址 42 43 return 0; 44 }
/dev/zero 聚宝盆,可以随意映射
/dev/null 无底洞(该文件无限大),一般错误信息重定向到这个文件中
示例3:
mmap实现无血缘关系进程间通信,mmap()函数的flags参数必须设置为MAP_SHARED
原理:08_mmap_w.c是对一个文件A对应的映射区进行写操作,08_mmap_r.c是对文件A对应的映射区进行读操作
且创建文件A对应的映射区的时候使用的参数是MAP_SHARED,故在一个线程中修改了映射区中的内容,在另外一个线程中也可以读到该变化
08_mmap_w.c
1 //08_mmap_w.c 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/wait.h> 6 #include <sys/stat.h> 7 #include <sts/mman.h> //for mmap() 8 #include <fcntl.h> 9 10 typedef struct Student{ 11 int id; 12 char name[20]; 13 }student; 14 15 int main(int argc,char* argv[]){ 16 if(argc != 2){ 17 printf("./a/out filename "); 18 return -1; 19 } 20 21 //1.打开一个文件,该文件不一定是txt,任意形式的文件都可以 22 int fd=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666); //以可读和可写的方式打开,O_TRUNC会使文件长度清为0,并且原来存于该文件的资料也会消失 23 int length=sizeof(Student); 24 ftruncate(fd,length); //将fd指向的文件裁剪为length指定的大小 25 26 //2.mmap 27 Student* stu = mem=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); 28 if(stu == MAP_FAILED){ 29 perror("mmap err"); 30 return -1; 31 } 32 33 //3.修改内存中的数据 34 int mum=1; 35 while(1){ 36 stu->id=num; 37 sprintf(stu->name,"xiaoming-%03d ",num); 38 num++; 39 sleep(1); //相当于每隔1s修改一次映射区的内容 40 } 41 42 //4.释放映射区和关闭文件 43 munmap(stu,length); 44 close(fd); 45 46 rerturn 0; 47 }
08_mmap_r.c
1 //08_mmap_r.c 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/wait.h> 6 #include <sys/stat.h> 7 #include <sts/mman.h> //for mmap() 8 #include <fcntl.h> 9 10 typedef struct Student{ 11 int id; 12 char name[20]; 13 }student; 14 15 int main(int argc,char* argv[]){ 16 //1.打开文件成功 17 int fd=open(argv[1],O_RDWR); 18 19 //2.使用mmap()创建映射区 20 int length=sizeof(Student); 21 Student* stu=mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); 22 if(stu == MAP_FAILED){ 23 perror("mmap err"); 24 return -1; 25 } 26 27 //3.读另外一个线程中的数据 28 while(1){ 29 printf("id=%d,name=%s ",stu->id,stu->name); 30 sleep(1); 31 } 32 33 //4.释放映射区和关闭文件 34 munmap(stu,length); 35 close(fd); 36 37 return 0; 38 }
08_mmap_w.c中的错误,需要使用下面的替代:
第7行需要更改为:
#include <sys/mman.h>
10-13行需要更改为:
1 typedef struct _student{ 2 int id; 3 char name[20]; 4 }Student; 5 /* 6 7 8 typedef struct tagMyStruct 9 { 10 int iNum; 11 long lLength; 12 } MyStruct; 13 在C中,这个申明后申请结构变量的方法有两种: 14 (1)struct tagMyStruct 变量名 15 (2)MyStruct 变量名 16 在c++中可以有 17 (1)struct tagMyStruct 变量名 18 (2)MyStruct 变量名 19 (3)tagMyStruct 变量名 20 21 */
第34行需要更改为:int num=1;
第46行需要更改为:return 0;
08_mmap_r.c中的错误,需要使用下面的替代:
第7行的头文件
执行结果:
1、将windows下的08_mmap_w.c和08_mmap_r.c文件复制到Ubuntu中去
2、编译
3、结果
4、停止运行
需要在读端和写端分别按下Ctrl+c,才可以停止运行程序
5、总结:
4)信号
1 不同进程间通信方式---信号(由内核产生,也叫软件产生的中断,有可能会有延迟) 2 特点:简单,不能带大量信息,满足特定条件发生 3 4 一个进程中只有一个时钟,也只有一个定时器 5 6 信号的原理:进程B发送信号给进程A,那么进程A必须立即处理该信号,处理信号的方式有忽略、处理等 7 信号的产生: 8 按键产生:ctrl+c ctrl+z ctrl+ 9 调用函数:kill raise abort 10 定时器:alrm setitimer 11 命令产生:kill 12 硬件异常:段错误、浮点错误、总线错误、SIGPIE 13 信号的状态: 14 产生 15 递达 16 未决:信号被阻塞 17 18 信号的默认处理方式: 19 忽略 20 执行默认动作 21 捕获 22 9,19(暂停信号 )号信号不能捕捉,不能忽略,甚至不能阻塞 23 信号的4要素 24 编号、事件、名称、默认处理动作 25 man 7 signal 查看默认处理动作:忽略、终止+产生core文件、暂停、继续
1 kill -l 查看常见信号(前31个位普通常见信号) 2 kill -SIGKILL pid 3 其中SIGKILL是9的宏定义
1 int kill(pid_t pid, int sig) 2 如果pid>0,代表当前进程id 3 如果pid=0,代表当前调用进程组内所有进程 4 如果pid=-1,代表有权限的所有进程 5 如果pid<0,代表-pid对应的组内所有进程 6 sig对应的信号 7 举例: 8 kill(2094,SIGKILL); //将进程为2094的进程杀掉
1 //举例:在父进程中将子进程3删除掉 2 #include <stdio.h> 3 #include <unistd.h> //for fork() 4 #include <sys/types.h> //for kill() 5 #include <sinal.h> //for kill() 6 7 int main(){ 8 int i=0; 9 pid_t pid,pid3; 10 //整个for循环都是在父进程中执行的,要在for循环中获取3号进程的进程id 11 for(i=0;i<5;++i){ 12 pid=fork(); 13 if(pid==0){ 14 break; //如果是子进程,则跳出for循环,父进程继续执行for循环 15 } 16 if(i==2){ 17 pid3=pid; //这一句是在父进程中执行,因为上一句是子进程跳出 18 } 19 } 20 21 if(i<5){ 22 //son 23 printf("I am child,pid=%d",getpid()); 24 sleep(3); 25 } 26 else if(i==5){ 27 //parent 28 printf("I am parent,pid=%d,I will kill pid3=%d in 5s",getpid(),pid3); 29 sleep(5); 30 kill(pid3,SIGKILL); 31 while(1){ 32 sleep(1); 33 } 34 } 35 36 return 0; 37 }
raise()函数
需要包含的头文件:
#include <sinal.h>
函数原型:
int raise(int sig); 自己给自己发送信号
1 //举例: 2 #include <stdio.h> 3 #include <sys/types.h> 4 #include <sinal.h> 5 #include <unistd.h> 6 7 int main(){ 8 printf("I will die in 3s "); 9 sleep(3); 10 raise(SIGKILL); //实际上调用kill(getpid(),SIGKILL); 11 return 0; 12 }
abort()函数
需要包含的头文件:
#include <sinal.h>
函数原型:
int raise(int sig); 自己给自己发送信号
1 //举例: 2 #include <stdio.h> 3 #include <sys/types.h> 4 #include <sinal.h> 5 #include <unistd.h> 6 #include <stdlib.h> 7 8 int main(){ 9 printf("I will die in 3s "); 10 sleep(3); 11 abort(); //不管什么信号都会杀掉 12 return 0; 13 }
setitimer函数:周期性的发送信号
1 #include <sys/time.h> 2 3 int setitimer(int which,const struct itimerval *new_value,struct itimerval* old_value); 4 5 which对应的宏定义: 6 ITIMER_REAL:自然定时法,对应SIGNALRM信号 7 ITIMER_VIRTUAL: 计算进程执行时间 对应SIGVTALRM信号 8 ITIMER_PROF: 进程执行时间+调度时间 对应ITIMER_VIRTUAL信号 9 new_value:要设置的闹钟时间 10 old_value:原闹钟时间 11 struct itimerval{ 12 struct timerval it_interval; //周期性的时间设置 13 struct timerval it_value; //下次的闹钟时间 14 }; 15 struct timerval{ 16 time_t tv_sec; //秒 17 time_t tv_usec; //微秒 18 } 19 20 返回值:成功返回0,失败返回-1
1 //非周期性的发送信号,只发送一次 2 //05_setitimer.c 3 #include <stdio.h> 4 #include <sys/time.h> 5 #include <unistd.h> //for sleep() 6 7 int main(){ 8 //下面这两句设置3s后发送SIGNALRM信号 9 struct itimerval myit={{0,0},{3,0}}; //不是周期性的发送信号定义方法:it_interval设置为{0,0};it_value设置为{3,0} 10 setitimer(ITIMER_REAL,&myit,NULL); //ITIMER_REAL对应要发送的SIGNALRM信号,SIGALRM信号无对应的动作,则终止当前进程 11 12 while(1){ 13 printf("who can kill me "); //3s后程序终止 14 sleep(1); 15 } 16 return 0; 17 }
1 //使用setitimer()周期性的发送信号 2 //05_setitimer2.c 3 #include <stdio.h> 4 #include <sys/time.h> 5 #include <unistd.h> //for sleep() 6 #include <signal.h> 7 8 void catch_sig(int num){ 9 printf("catch &d signal ",num); 10 } 11 12 int main(){ 13 signal(SIGALRM,catch_sig); //将SIGALRM信号和catch_sig()关联 14 15 //下面这两句设置3s后发送SIGNALRM信号 16 struct itimerval myit={{3,0},{5,0}}; //周期性的发送信号定义方法:it_interval设置为{3,0};it_value设置为{5,0};第一次5s后发送SIGALRM信号,之后每隔3s发送一次SIGALRM信号 17 setitimer(ITIMER_REAL,&myit,NULL); //ITIMER_REAL对应要发送的SIGNALRM信号,SIGALRM信号无对应的动作,则终止当前进程 18 19 while(1){ 20 printf("who can kill me "); //3s后程序终止 21 sleep(1); 22 } 23 return 0; 24 }
1 //使用setitimer()实现alarm()函数 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <sys/time.h> 5 6 unsigned int myalarm(unsigned int seconds){ 7 struct itimerval myit = {{0,0},{0,0}}; 8 myit.it_value.tv_sec=seconds; //alarm()相当于下一次闹钟什么时候来,要使用it_value;而不是使用周期性发送信号的it_interval 9 10 setitimer(ITIMER_REAL,&myit,NULL); //setitimer()的第三个参数old_value先不管 11 12 return 0; 13 } 14 15 int main(){ 16 17 return 0; 18 }
使用重定向提高程序运行效率一个例子:
1 //1s钟数数,由于printf()执行特别的花费时间,所以可以将程序执行结果输入到一个文件中 2 //05_count.c 3 #include <unistd.h> 4 #include <stdio.h> 5 6 int main(){ 7 int i=0; 8 alarm(1); 9 while(1){ 10 printf("%d ",i++); 11 } 12 return 0; 13 }
1 gcc 05_count.c -o count 2 ./count > xxx.log //将./count执行的结果输入到xxx.log文件中,这样比打印在屏幕上数的数多的多
信号集处理函数
1 #include <signal.h> 2 int sigempty(sigset_t *set); //将信号集set清空 3 int sigfillset(sigset_t *set); //填充信号集set 4 int sigaddset(sigset_t *set,int signum); //将信号signum添加到set中 5 int sigdelset(sigset_t *set,int signum); //将信号signum从set中删除 6 int sigismember(sigset_t *set,int signum); //判断信号signum是否为set中的成员,在则返回1,否则返回0 7 8 9 //设置阻塞或者解除阻塞信号集 10 #include <signal.h> 11 int sigprocmask(int how,const sigset_t* set, sigset_t* oldset); 12 how可选的参数: 13 SIG_BLOCK 阻塞 14 SIG_UNBLOCK 解除阻塞 15 SIG_SETMASK 将信号集set设置为新的阻塞信号集 16 set:传入的信号集 17 oldset:传出,便于以后恢复状态 18 返回值:成功返回0,失败返回-1 19 20 获取未决信号集 21 #include <signal.h> 22 int sigpending(sigset_t* set); 23 set:传出参数,当前未决定信号集 24 返回值:成功返回0,失败返回-1
1 //打印1-31信号在当前进程是否是未决信号 2 //07_sigpending.c 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <signal.h> 6 7 int main(){ 8 //设置阻塞信号, 9 sigset_t sigproc; 10 sigemptyset(&sigproc); //将信号集sigproc清空 11 sigaddset(&sigproc,SIGINT); //将2号信号(SIGINT)添加到信号集sigproc中 12 sigaddset(&sigproc,SIGQUIT); //将3号信号(SIGQUIT)添加到信号集sigproc中 13 14 //设置阻塞信号集(2号、3号信号被阻塞,其余信号没有被阻塞) 15 sigprocmask(SIG_BLOCK,&sigproc,NULL); 16 17 sigset_t pend; 18 sigpending(&pend); //将当前进程的未决信号集合放到pend中 19 int i; 20 for(int i=1;i<32;++i){ 21 if(sigismember(&pend,i)==1){ 22 printf("1"); 23 } 24 else 25 printf("0"); 26 } 27 printf(" "); 28 return 0; 29 }
信号捕捉(即创建信号和函数的对应关系):防止进程意外死亡
1 捕捉方法1,使用signal()函数,原型: 2 sighandler_t signal(int signum,sighandler_t handler); //创建信号signum和函数handler()的关联 3 typedef void(*sighandler_t)(int) //表明sighandler_t是一个函数指针,该函数的返回值为void,形参为int 4 5 捕捉方法2:使用sigaction()函数,原型: 6 int sigaction(int signum,const struct sigaction* act, struct sigaction* oldact); 7 struct sigaction{ 8 void(* sa_handler)(int); //一个返回值为void,形参为int的指针函数 9 void(* sa_sigaction)(int,siginfo_t*,void*); //一个返回值为void,形参为 10 sigset_t sa_mask; //执行捕捉函数期间,临时屏蔽的信号集 11 int sa_flags; //写0会使用sa_handler()函数指针模板对应的函数,写SA_SIGINFO会使用sa_sigaction()函数指针模板对应的函数 12 void (*sa_restorer)(void); //无效 13 } 14 参数说明: 15 signum: 要捕捉的信号 16 act: 要传入的动作 17 oldact: 原动作,帮忙恢复现场 18 返回值:成功返回0,失败返回-1
1 //使用sigaction()捕捉信号 2 #include <signal.h> 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <sys/time.h> 6 7 void catch_sig(int num){ 8 printf("catch %d signal ",num); 9 } 10 11 int main(){ 12 //注册一下捕捉函数 13 struct sigaction act; 14 act.sa_handler=catch_sig; //将自己定义的动作赋值给结构体中的函数指针 15 sigemptyset(&act.sa_mask); //将sa_mask信号集清空,初始化一个未包含任何成员的信号集 16 sigaction(SIGALRM,&act,NULL); 17 18 //设置计时时间和计时时间到后要发送的信号 19 struct itimerval myit{{3,0},{5,0}}; //第一次计时5s发送一次信号,接下来每隔3s发送一次信号 20 setitimer(ITIMER_REAL,&myit,NULL); //ITIMER_REAL对应SIGALRM信号 21 while(1){ 22 printf("who can kill me "); 23 sleep(1); 24 } 25 26 return 0; 27 }
10、守护进程
守护进程(Daemon进程):
一般进程将终端关闭之后,该进程就结束了,但是守护进程在终端关闭之后该进程也不会结束
一般周期性的处理某些事物,这些事物一般采用那个以d结尾的名字
进程组:如一个主进程创建了5个子进程,那么他们就是一个进程组,第一个进程默认是进程组的组长
会话:多个进程组组成一个会话,创建会话的时候,进程组的组长不可以创建会话,必须是组员创建
会话创建的步骤:
1、创建子进程,父进程去死,子进程自当会长
守护进程的创建步骤:
1、使用fork()创建子进程,父进程退出
2、子进程当会长setsid()
3、切换工作目录,一般切换到Home下
4、设置掩码,umask()
5、关闭文件描述符 在最初调试的时候最好先不要关,因为在执行核心逻辑的时候什么也看不到了
6、执行核心逻辑
7、退出
1 //创建一个守护进程:每分钟在$Home/log/创建一个文件,程序名.时间戳 2 3 //01_daemon.c 4 #include <stdio.h> 5 #include <unistd.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 #include <string.h> 10 #include <stdlib.h> 11 12 #define _FILE_NAME_FORMAT_ "%s/log/mydaemon.%ld" //定义文件格式化 13 14 void touchfile(int num){ 15 char* HOMEDir=getenv("HOME"); //获取HOME的环境变量 16 char strFilename[256]={0}; 17 //sprintf(a,b)将b中的内容传送到a中,但是此时b是一个宏定义,相当于把宏定义搬过来,而宏定义又需要向里面传参数,所以后面需要继续跟变量 18 sprintf(strFilename,_FILE_NAME_FORMAT_,HOMEDir,time(NULL)); //time()形参为NULL的时候表示获取当前时间 19 20 int fd=open(strFilename,O_RDWR|O_CREAT,0666); //0666表示110 110 110 对用户、组、其他都对该文件有读写权利 21 if(fd<0){ 22 perror("open err"); 23 exit(1); //相当于守护进程退出 24 } 25 close(fd); 26 } 27 28 int main(){ 29 //创建子进程,父进程退出 30 pid_t pid=fork(); 31 if(pid>0){ 32 exit(1); //主进程退出 33 } 34 //子进程当会长 35 setsid(); 36 //切换工作目录 37 chdir(getenv("HOME")); //切换到家目录,getenv("HOME")获取HOME的环境变量 38 //设置掩码 39 umask(0); //设置掩码为0 40 //关闭文件描述符 41 //close(1); close(2); close(3); 42 //执行核心逻辑:每分钟在$Home/log/创建一个文件 43 struct itimerval myit={{60,0},{60,0}}; //第一个表示60秒为一次计时,第二个60表示第一次计时60s 44 setitimer(ITIMER_REAL,&myit,NULL); 45 struct sigaction act; 46 act.sa_flags=0; 47 sigemptyset(&act.sa_mask); 48 act.sa_handler=touchfile; 49 sigaction(SIGALRM,&act,NULL); 50 while(1){ 51 52 } 53 //退出 54 }
*****对未决信号和阻塞信号的进一步理解以及屏蔽信号、解除屏蔽的方法
信号常见的感念:
实际执行信号的处理动作(3种)称为信号递达;
信号从产生到递达之间的状态,叫做信号未决;
进程可以选择阻塞某个信号;
被阻塞的信号产生时,将保持在未决状态,直至进程取消对该信号的阻塞,才执行递达的动作; 于是我们可以选择先屏蔽掉某个信号,在屏蔽该信号的期间,该信号可能已经被发出,在接触该信号的屏蔽之后可以重 新捕获到该信号。
注意:阻塞和忽略是不同的。只要信号阻塞就不会被递达;而忽略是信号在递达之后的一种处理方式。
举例:
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针(handler)表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直至信号递达才清除该标志。操作系统向进程发送信号就是将pending位图中的该信号对应状态位由0变为1。
如上图,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作;SIGINT信号产生过,但是正在被阻塞,所以暂时递达不了。虽然它的处理动作是忽略,但是未解除阻塞时不能忽略该信号,因为经常仍有机会改变处理动作后再解除阻塞;SIGQUIT信号未产生过,但是它是阻塞的,所以一旦该信号产生它就被阻塞无法递达,它的处理动作也是用户自定义函数。
在Linux下,如果进程解除某信号的阻塞之前,该信号产生了很多次,它的处理方法是:若是常规信号,在递达之前多次产生只计一次;若是实时信号,在递达之前产生多次则可以放在一个队列里。本文只讨论常规信 号,下面提到的信号都是常规信号。
信号集
从上图我们可以知道,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次。同样的,阻塞标志也是这样表示的。所以阻塞和未决标志我们可以采用相同的数据类型sigset_t来存储,sigget_t称为信号集。
这个类型可以表示每个信号的“有效”、“无效”状态。在未决信号集中,“有效”、“无效”表示该信号是否处于未决状态;在阻塞信号集中,“有效”、“无效”表示该信号是否被阻塞。阻塞信号集也叫做当前进程的信号屏蔽字。
信号集操作函数
sigget_t类型对每一种信号用一个bit表示“有效”或者“无效”状态,至于这个类型内部是怎样储存这些bit则依赖系统实现,从使用者的角度是不用关心的。使用者只用调用以下函数来对sigget_t变量进行操作,而不用对它的内部数据进行任何解释。
其中,前四个函数都是成功返回0,出错返回-1;最后一个sigismember函数包含返回1,不包含返回0,出错返回-1。
注意:在使用sigget_t类型的变量之前,一定要调用sigemptyset函数或者sigfillset函数做初始化,使信号集处于确定的状态。
sigprocmask()
how:有三个可取值(如下图,假设当前信号屏蔽字为mask)
set:指向一个信号集的指针
oldset:用于备份原来的信号屏蔽字,不想备份时可设置为NULL
1)若set为非空指针,则根据参数how更改进程的信号屏蔽字;
2)若oldset是非空指针,则读取进程当前的信号屏蔽字通过oldset参数传出;
3)若set、oldset都非空,则将原先的信号屏蔽字备份到oldset中,然后根据set和how参数更改信号屏蔽字
(3)返回值:成功返回0,出错返回-1
说明:若调用sigprocmask解除了若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
参考博客链接
5)使用信号回收子线程 设计到SIGCHLD信号的屏蔽和信号的解除
使用man 7 sinal 再向下翻
使用SIGCHLD信号来回收子线程的原理:
SIGCHLD信号 子进程暂停或者是终止的时候发出这个信号,默认忽略这个信号
我们可以通过捕捉SIGCHLD这个信号来处理这个进程
touch 10_child_catch.c
vi 10_child_catch.c
1 #include <stdio,h> 2 #include <unistd.h> //for fork() 3 #include <sys/wait.h> //for wait() 4 #include <signal.h> 5 6 void catch_sig(int num){ 7 pid_t wpid=waitpid(-1,NULL,WNOHANG); //-1表示回收任何子线程;NULL不考虑线程退出的原因,WNOHANG表示子线程没有退出不会阻塞住主线程,此时如果设置了WNOHANG,如果子线程没有退出,返回值为0,否则返回退出了的子线程id 8 if(wpid>0){ 9 printf("child %d has quited ",wpid); 10 } 11 } 12 13 int main(){ 14 int i=0; 15 pid_t pid=fork(); 16 for(i=0;i<10;++i){ 17 if(pid==0){ 18 //son 19 break; //不让子线程再创建子线程 20 } 21 } 22 if(i==10){ 23 //parent 24 //注册捕捉函数(即创建信号和对应的动作的联系) 25 struct sigaction act; 26 act.sa_flags=0; //使用结构体中的sa_handler函数指针 27 sigemptyset(&act.sa_mask); //将结构体中的sa_mask信号集清空 28 act.sa_handler=catch_sig; //创建结构体中的指针函数sa_handler=catch_sig 29 30 sigaction(SIGCHLD,&act,NULL); //将信号SIGCHLD与catch_sig函数关联 31 32 while(1){ 33 sleep(1); 34 } 35 } 36 else if(i<10){ 37 //son 38 printf("child %d ",getpid()); 39 sleep(i); //必须有这一句,如果有可能信号同时结束(很短的时间差),但是信号捕捉一次只能捕捉一个,所以去掉这一句会有僵尸进程出现 40 } 41 42 return 0; 43 }
1 gcc 10_child_catch.c -o 10_child_catch 2 ps -aux | grep 10_child_catch 查看10_child_catch执行结果中有没有僵尸进程 3 如果出现Z+的一行说明有僵尸进程
有可能信号同时结束(很短的时间差),但是信号捕捉一次只能捕捉一个,所以去掉这一句会有僵尸进程出现,解决方法是在信号处理函数中使用whle循环来回收子线程
1 //对于如果有多个信号同时结束了的时候,不同全捕捉问题的改进 2 #include <stdio,h> 3 #include <unistd.h> //for fork() 4 #include <sys/wait.h> //for wait() 5 #include <signal.h> 6 7 void catch_sig(int num){ 8 pid_t wpid; 9 10 //-1表示回收任何子线程;NULL不考虑线程退出的原因,WNOHANG表示子线程没有退出不会阻塞住主线程,此时如果设置了WNOHANG,如果子线程没有退出,返回值为0,否则返回退出了的子线程id 11 //如果有多个SIGCHLD信号过来,则在这里循环接收这个信号 12 while((wpid=waitpid(-1,NULL,WNOHANG)) > 0){ 13 printf("child %d has quited ",wpid); 14 } 15 } 16 17 int main(){ 18 int i=0; 19 pid_t pid=fork(); 20 for(i=0;i<10;++i){ 21 if(pid==0){ 22 //son 23 break; //不让子线程再创建子线程 24 } 25 } 26 if(i==10){ 27 //parent 28 //注册捕捉函数(即创建信号和对应的动作的联系) 29 struct sigaction act; 30 act.sa_flags=0; //使用结构体中的sa_handler函数指针 31 sigemptyset(&act.sa_mask); //将结构体中的sa_mask信号集清空 32 act.sa_handler=catch_sig; //创建结构体中的指针函数sa_handler=catch_sig 33 34 sigaction(SIGCHLD,&act,NULL); //将信号SIGCHLD与catch_sig函数关联 35 36 while(1){ 37 sleep(1); 38 } 39 } 40 else if(i<10){ 41 //son 42 printf("child %d ",getpid()); 43 sleep(i); //必须有这一句,如果有可能信号同时结束(很短的时间差),但是信号捕捉一次只能捕捉一个,所以去掉这一句会有僵尸进程出现 44 } 45 46 return 0; 47 }
上面的程序还是有bug的:加入在创建信号和动作的联系之前(即在这sigaction(SIGCHLD,&act,NULL);句话执行之前),所有的子进程结束了,那么所有的
子进程都会成为僵尸进程。解决方法:在子进程退出之前将SIGCHLD信号屏蔽,等到创建了SIGCHLD信号和动作的联系之后,再解除SIGCHLD信号的屏蔽
所以要在子进程创建之前就将SIGCHLD信号屏蔽,那么在sigaction(SIGCHLD,&act,NULL);之前有子进程结束(SIGCHLD信号发出),则不会捕捉到SIGCHLD信号
但是在解除了SIGCHLD信号的屏蔽了之后,已经发出了的SIGCHLD信号还是会被系统捕捉得到的。
1 #include <stdio,h> 2 #include <unistd.h> //for fork() 3 #include <sys/wait.h> //for wait() 4 #include <signal.h> 5 6 void catch_sig(int num){ 7 pid_t wpid; 8 9 //-1表示回收任何子线程;NULL不考虑线程退出的原因,WNOHANG表示子线程没有退出不会阻塞住主线程,此时如果设置了WNOHANG,如果子线程没有退出,返回值为0,否则返回退出了的子线程id 10 //如果有多个SIGCHLD信号过来,则在这里循环接收这个信号 11 while((wpid=waitpid(-1,NULL,WNOHANG)) > 0){ 12 printf("child %d has quited ",wpid); 13 } 14 } 15 16 int main(){ 17 int i=0; 18 19 //在创建进程之前屏蔽掉SIGCHLD信号 20 sigset_t myset; //创建信号集myset和oldset 21 sigemptyset(&myset); //将信号集myset清空 22 sigaddset(&myset,SIGCHLD); //将信号SIGCHLD添加到信集myset中 23 sigprocmask(SIG_BLOCK,&myset,NULL); //表示将myset信号集中的信屏蔽 24 25 pid_t pid=fork(); 26 for(i=0;i<10;++i){ 27 if(pid==0){ 28 //son 29 break; //不让子线程再创建子线程 30 } 31 } 32 if(i==10){ 33 //parent 34 //注册捕捉函数(即创建信号和对应的动作的联系) 35 struct sigaction act; 36 act.sa_flags=0; //使用结构体中的sa_handler函数指针 37 sigemptyset(&act.sa_mask); //将结构体中的sa_mask信号集清空 38 act.sa_handler=catch_sig; //创建结构体中的指针函数sa_handler=catch_sig 39 40 sigaction(SIGCHLD,&act,NULL); //将信号SIGCHLD与catch_sig函数关联 41 42 //在创建了SIHCHLD信号和动作的联系之后,再将SIGCHLD信号解除屏蔽 43 //如果在屏蔽期间信号发生了之后,解除屏蔽后还会再次捕捉到该信号 44 sigprocmask(SIG_UNBLOCK,&myset,NULL); //解除对myset信号集中的信号的屏蔽 45 46 while(1){ 47 sleep(1); 48 } 49 } 50 else if(i<10){ 51 //son 52 printf("child %d ",getpid()); 53 sleep(i); //必须有这一句,如果有可能信号同时结束(很短的时间差),但是信号捕捉一次只能捕捉一个,所以去掉这一句会有僵尸进程出现 54 } 55 56 return 0; 57 }
6)sigaction结构体中的sa_mask信号集的解释
sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。
在调用信号处理程序时就能阻塞某些信号。注意仅仅是在信号处理程序正在执行时才能阻塞某些信号,如果信号处理程序执行完了,那么依然能接收到这些信号。
在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号,也就是说自己也被阻塞,除非设置了SA_NODEFER。
因此保证了在处理一个给定的信号时,如果这种信号再次发生,通常并不将它们排队,所以如果在某种信号被阻塞时它发生了5次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。
Q3:对于不同信号,当信号A被捕捉到并信号A的handler正被调用时,信号B产生了,
3.1如果信号B没有被设置阻塞,那么正常接收信号B并调用自己的信号处理程序。另外,如果信号A的信号处理程序中有sleep函数,那么当进程接收到信号B并处理完后,sleep函数立即返回(如果睡眠时间足够长的话)
3.2如果信号B有被设置成阻塞,那么信号B被阻塞,直到信号A的信号处理程序结束,信号B才被接收并执行信号B的信号处理程序。
如果在信号A的信号处理程序正在执行时,信号B连续发生了多次,那么当信号B的阻塞解除后,信号B的信号处理程序只执行一次。
如果信号A的信号处理程序没有执行或已经执行完,信号B不会被阻塞,正常接收并执行信号B的信号处理程序。
Q4:对于相同信号,当一个信号A被捕捉到并信号A的handler正被调用时,
4.1 又产生了一个信号A,第二次产生的信号被阻塞,直到第一次产生的信号A处理完后才被递送;
4.2 如果连续产生了多次信号,当信号解除阻塞后,信号处理函数只执行一次。
例如:
1 struct sigaction act; 2 act.flags = 0; 3 sigemptyset(&act.sa_mask); //将结构体中的sa_mask清空,当recycle()执行的时,这里的sa_mask默认将当前信号(SIGCHLD)本身阻塞 4 act.sa_handler = recycle; //recycle()为SIGCHLD信号发生时执行的动作 5 sigaction(SIGCHLD,&act,NULL);
举例1:
重点代码在于:
1 struct sigaction act, oact; 2 sigset_t oldmask; 3 4 act.sa_handler = sig_usr; 5 sigemptyset(&act.sa_mask); 6 //sigaddset(&act.sa_mask, SIGUSR1); //这一句不加也是可以得,因为sa_mask默认将当前信号本身阻塞 7 sigaddset(&act.sa_mask, SIGQUIT); //在执行sig_usr()期间,如果有SIGQUIT信号,那么在sig_usr()执行完毕后也会捕捉到 8 act.sa_flags = 0|SA_INTERRUPT; 9 sigaction(SIGUSR1, &act, &oact); //通过signalaction()将SIGUSR1和sig_usr()函数关联,此时SIGUSR1会被阻塞;即在sig_usr()执行期间,SIGUSR1发生了,系统直接不会忽略,在sig_usr()执行完后,继续捕捉以前发生的SIGUSR1
使用sigaction()将信号SIGUSR1、信号SIGQUIT和函数usr_sig()关联,并将SIGUSR1和SIGQUIT添加到sa_mask中(其中SIGQUIT默认添加),表明在函数sig_usr()执行期间,假如有SIGUSR1和SIGQUIT信号发出,那么在sig_usr()函数执行完毕后,会再次捕捉以前发送的SIGUSR1或SIGQUIT信号,如果有多次发送,只捕捉一次
1 //通过sigactin()将信号和对应的动作关联:默认情况下,该函数指针执行的时候,再次发送该信号会发生阻塞,直到该函数执行完毕, 2 //如果在函数执行期间,该信号发出了多次,那么函数执行完只执行一次 3 4 5 //通过signal()将信号和对应的动作关联:不会将该信号阻塞,即在动作执行期间,该信号发生了,那么系统直接忽略 6 7 //将SIGUSR1设置为阻塞,将SIGUSR2不设置为阻塞 8 //其中SIGUSR1信号有可以由终端命令产生: kill -s SIGUSR1 pid 其中pid为进程号,同理SIGUSR2 9 //将SIGQUIT设置为阻塞,用来退出 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <unistd.h> 13 #include <string.h> 14 #include <signal.h> 15 #include <errno.h> 16 17 #define BUFSIZE (1024) 18 19 void sig_usr(int signo) 20 { 21 int nRemainSecond = 0; 22 23 if (signo == SIGUSR1) 24 { 25 printf("received SIGUSR1=%d ", SIGUSR1); 26 nRemainSecond = sleep(10); 27 printf("over...nRemainSecond=%d ", nRemainSecond); 28 } 29 else if (signo == SIGUSR2) 30 { 31 printf("received SIGUSR2=%d ", SIGUSR2); 32 } 33 34 } 35 36 int main(int argc, char** argv) 37 { 38 int nSize = 0; 39 char acBuf[BUFSIZE] = {0}; 40 struct sigaction act, oact; 41 sigset_t oldmask; 42 43 act.sa_handler = sig_usr; 44 sigemptyset(&act.sa_mask); 45 //sigaddset(&act.sa_mask, SIGUSR2); //这一句不加也是可以得,因为sa_mask默认将当前信号本身阻塞 46 sigaddset(&act.sa_mask, SIGQUIT); 47 act.sa_flags = 0|SA_INTERRUPT; 48 sigaction(SIGUSR1, &act, &oact); //通过signalaction()将SIGUSR1和sig_usr()函数关联,此时SIGUSR1会被阻塞;即在sig_usr()执行期间,SIGUSR1发生了,系统直接不会忽略,在sig_usr()执行完后,继续捕捉以前发生的SIGUSR1 49 50 signal(SIGUSR2, sig_usr); //通过signal()将SIGUSR2和sig_usr()函数关联,此时SIGUSR2不会被阻塞;即在sig_usr()执行期间,SIGUSR2发生了,系统直接忽略 51 52 while(1) 53 { 54 memset(acBuf, '