目錄
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=1\x7" " Vertical bar in insert mode 92 let &t_EI = "\<Esc>]50;CursorShape=0\x7" " 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下加或者不加\n是有區別的,如下
這是由於第一個printf()沒有加\n,只有加了\n才會輸出到屏幕上,所以第一個printf()內容
會保存在輸入緩沖區,在子進程中也會有Begin......字符串
1 #include <stdio.h> 2 #include <unistd.h> 3 int main() 4 { 5 printf("Begin......\n"); 6 pid_t pid=fork(); //pid_t為變量類型名字,執行完該句后開始有兩個進程 7 printf("End......\n"); 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......\n"); 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\n",getpid(),getppid()); 17 } 18 else if(pid>0) 19 { 20 //如果是父進程,當前進程是父進程,所以getpid()獲取的是當前父進程的id 21 printf(”childpid=%d,self=%d,ppid=%d\n",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\n",getpid(),getppid()); 17 } 18 else if(pid>0) 19 { 20 //如果是父進程,當前進程是父進程,所以getpid()獲取的是當前父進程的id 21 printf(”childpid=%d,self=%d,ppid=%d\n",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\n",getpid(),getppid()); 17 } 18 else if(pid>0) 19 { 20 printf("I am father,pid=%d,ppid=%d\n",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\n",getpid(),getppid()); 17 break; //子線程跳出for循環,防止子線程再次執行for循環,子線程再次創建子線程 18 } 19 else if(pid>0) 20 { 21 printf("I am father,pid=%d,ppid=%d\n",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\n",var,getpid(),getppid()); 13 var=10001; 14 printf("var=%d,child,pid=%d,ppid=%d\n",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\n",var,getpid(),getppid()); 23 var=2000; 24 printf("var=%d,parent,pid=%d,ppid=%d\n",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\n",getpid(),getppid()); 14 sleep(1); 15 } 16 } 17 else if(pid>0) 18 { 19 //parent 20 printf("parent,pid=%d,ppid=%d\n",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\n",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\n",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\n"); 11 } 12 else if(pid>0) 13 { 14 //parent 15 printf("I am parent,I will wait for chid die\n"); 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\n"); 12 } 13 else if(pid>0) 14 { 15 //parent 16 printf("I am parent,I will wait for chid die\n"); 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\n",getpid()); 13 sleep(2); 14 } 15 else if(pid>0) 16 { 17 //parent 18 printf("parent,pid=%d\n",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\n",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\n"); //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\n",wpid); 38 } 39 } 40 while(1) 41 { 42 //printf("main_treading running\n"); 43 sleep(1); //保證主線程不會退出 44 } 45 } 46 47 if(i<5) 48 { 49 //son 50 sleep(i); 51 printf("child,pid=%d\n",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!\n"); 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\n",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\n"); 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\n"); 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\n",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\n",*mem); 29 sleep(3); 30 printf("child *mem=%d\n",*mem); //打印父進程中修改的內容 31 } 32 else if(pid>0){ 33 //parent 34 sleep(1); //父進程睡1s,此時子進程肯定將首地址為mem中的內容修改了 35 printf("parent *mem=%d\n",*mem); //打印子進程中修改的內容 36 *mem=1001; 37 printf("parent *mem=%d\n",*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\n",*mem); 27 sleep(3); 28 printf("child *mem=%d\n",*mem); //打印父進程中修改的內容 29 } 30 else if(pid>0){ 31 //parent 32 sleep(1); //父進程睡1s,此時子進程肯定將首地址為mem中的內容修改了 33 printf("parent *mem=%d\n",*mem); //打印子進程中修改的內容 34 *mem=10001; 35 printf("parent *mem=%d\n",*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\n"); 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\n",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\n",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\n"); 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\n"); 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\n"); //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\n",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\n"); //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\n",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("\n"); 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\n",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\n"); 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\n",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\n",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\n",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\n",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\n",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\n",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\n", SIGUSR1); 26 nRemainSecond = sleep(10); 27 printf("over...nRemainSecond=%d\n", nRemainSecond); 28 } 29 else if (signo == SIGUSR2) 30 { 31 printf("received SIGUSR2=%d\n", 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, '\0', BUFSIZE); 55 nSize = read(STDIN_FILENO, acBuf, BUFSIZE); //該句表示從終端讀BUFSIZE個字節的數據到acBuf中,其中STDIN_FILENO表示接收鍵盤的輸入 56 if (errno == EINTR) //如果read()在阻塞的時候被信號打斷了 57 printf("interrupt, size=%d\n", nSize); 58 59 if (1 == nSize && acBuf[0] == 10) 60 ; 61 else if (nSize != -1) //讀成功了 62 { 63 printf("nSize=%d, acBuf=%s", nSize, acBuf); 64 } 65 } 66 67 return 0; 68 }
測試結果:
1、 在阻塞信號1對應的函數執行期間發送阻塞信號2,此時阻塞信號1對應的函數執行完畢后,再去執行阻塞信號2對應的函數

以下為主函數中的read()函數被信號打斷,進而去執行信號對應的函數,沒有執行read()被信號返回值打斷的錯誤代碼,是由於發送了kill -QUIT 3874直接將線程結束了的原因

2、在阻塞信號對應的函數執行期間發送未阻塞信號,此時阻塞信號對應的函數會立即終止(返回),進而去執行未阻塞信號對應的函數

3、在未阻塞信號對應的函數執行期間發送阻塞信號,此時未阻塞信號對應的函數會立即終止(返回),進而去執行阻塞信號對應的函數,等到阻塞信號對應的函數中完畢后,再回去執行未阻塞信號的函數
已經改函數代碼:

常見信號的解釋
1 1) SIGHUP (掛起) 當運行進程的用戶注銷時通知該進程,使進程終止 2 3 2) SIGINT (中斷) 當用戶按下時,通知前台進程組終止進程 4 5 3) SIGQUIT (退出) 用戶按下或時通知進程,使進程終止 6 7 4) SIGILL (非法指令) 執行了非法指令,如可執行文件本身出現錯誤、試圖執行數據段、堆棧溢出 8 9 5) SIGTRAP 由斷點指令或其它trap指令產生. 由debugger使用 10 11 6) SIGABRT (異常中止) 調用abort函數生成的信號 12 13 7) SIGBUS 非法地址, 包括內存地址對齊(alignment)出錯. eg: 訪問一個四個字長的整數, 但其地址不是4的倍數. 14 15 8) SIGFPE (算術異常) 發生致命算術運算錯誤,包括浮點運算錯誤、溢出及除數為0. 16 17 9) SIGKILL (確認殺死) 當用戶通過kill -9命令向進程發送信號時,可靠的終止進程 18 19 10) SIGUSR1 用戶使用 20 21 11) SIGSEGV (段越界) 當進程嘗試訪問不屬於自己的內存空間導致內存錯誤時,終止進程 22 23 12) SIGUSR2 用戶使用 24 25 13) SIGPIPE 寫至無讀進程的管道, 或者Socket通信SOCT_STREAM的讀進程已經終止,而再寫入。 26 27 14) SIGALRM (超時) alarm函數使用該信號,時鍾定時器超時響應 28 29 15) SIGTERM (軟中斷) 使用不帶參數的kill命令時終止進程 30 31 17) SIGCHLD (子進程結束) 當子進程終止時通知父進程 32 33 18) SIGCONT (暫停進程繼續) 讓一個停止(stopped)的進程繼續執行. 本信號不能被阻塞. 34 35 19) SIGSTOP (停止) 作業控制信號,暫停停止(stopped)進程的執行. 本信號不能被阻塞, 處理或忽略. 36 37 20) SIGTSTP (暫停/停止) 交互式停止信號, Ctrl-Z 發出這個信號 38 39 21) SIGTTIN 當后台作業要從用戶終端讀數據時, 終端驅動程序產生SIGTTIN信號 40 41 22) SIGTTOU 當后台作業要往用戶終端寫數據時, 終端驅動程序產生SIGTTOU信號 42 43 23) SIGURG 有"緊急"數據或網絡上帶外數據到達socket時產生. 44 45 24) SIGXCPU 超過CPU時間資源限制. 這個限制可以由getrlimit/setrlimit來讀取/改變。 46 47 25) SIGXFSZ 當進程企圖擴大文件以至於超過文件大小資源限制。 48 49 26) SIGVTALRM 虛擬時鍾信號. 類似於SIGALRM, 但是計算的是該進程占用的CPU時間. 50 51 27) SIGPROF (梗概時間超時) setitimer(2)函數設置的梗概統計間隔計時器(profiling interval timer) 52 53 28) SIGWINCH 窗口大小改變時發出. 54 55 29) SIGIO(異步I/O) 文件描述符准備就緒, 可以開始進行輸入/輸出操作. 56 57 30) SIGPWR 電源失效/重啟動 58 59 31) SIGSYS 非法的系統調用。 60 61 在以上列出的信號中, 62 程序不可捕獲、阻塞或忽略的信號有:SIGKILL,SIGSTOP 63 不能恢復至默認動作的信號有:SIGILL,SIGTRAP 64 默認會導致進程流產的信號有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ 65 默認會導致進程退出的信號有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM 66 默認會導致進程停止的信號有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU 67 默認進程忽略的信號有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
11、多進程
1)多進程概念以及優缺點
線程
一個進程中可以有多個線程,一個進程中不同線程其實對應多個函數
由於函數調用的時候,會將函數運行時候需要的信息(如形參,函數地址)全部壓入棧中,故除了
棧以外的數據段(如代碼段、.data段、.bass段、堆),不同線程之間都是共享的
即多線程通信簡單
假如電腦中只有一個CPU的話,多線程是沒有用的,一個時刻只能有一個線程在工作
ps -Lf pid 查看進程號為953的進程包含線程的個數
線程優點:
提高並發性,提高CPU的利用率
占用資源小
通信方便
缺點:編寫調試困難;使用庫函數:不穩定;對信號的支持性不好
2)創建一個線程
1 創建線程函數
2 #include <pthread.h>
3 int thread_create(pthread *thread,const pthread_attr_t *attr,void*(*start_routine)(void*),void *arg); 4 thread:線程id,傳出參數 5 attr:代表線程屬性,一般不用 6 第三個參數:函數指針,形式必須為 void* fun(void*)的格式 7 arg: 線程執行函數的參數 8 返回值:成功返回0,失敗返回一個error number 9 編譯的時候需要加 -lpthread (這是由於在Linux中線程使用的是庫函數,第一個為字母l)
03_pthread_create.c
1 gcc 03_pthread_create.c -lpthread -o 03_pthread_create
2 ./03_pthread_create
1 //03_pthread_create.c 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <pthread.h> 5 6 //線程執行函數 7 void* thr(void* arg){ 8 printf("I am a thread,tid=%lu",pthread_self()); 9 rerurn NULL; 10 } 11 12 int main(){ 13 14 //創建線程 15 pthread_t tid; 16 thread_create(tid,NULL,thr,NULL); 17 18 printf("I am a main thread,tid=%lu\n",pthread_self()); //pthread_self()獲取當前線程的線程號 19 sleep(1); //通過睡眠讓線程有機會執行 20 21 return 0; 22 }
3)線程退出方法
線程退出方法
1、在線程中使用pthread_exit() 2、在線程中調用return代表此線程結束(主控線程return代表退出進程) 3、在任何一個線程中調用exit都會導致退出整個進程
04_pthread_exit.c
1 //04_pthread_exit.c 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <pthread.h> 5 6 void* thr(void* arg){ 7 printf("I am a thread,tid=%lu",pthread_self()); 8 pthread_exit(NULL); 9 } 10 11 int main(){ 12 13 //創建線程 14 pthread_t tid; 15 thread_create(tid,NULL,thr,NULL); 16 17 printf("I am a main thread,tid=%lu\n",pthread_self()); //pthread_self()獲取當前線程的線程號 18 sleep(1); //通過睡眠讓線程有機會執行 19 pthread_exit(NULL); //在main中調用pthread_exit僅僅是主線程退出,進程不會結束,進程內的其他線程也不會結束,直到所有線程結束進程才會結束 20 21 return 0; 22 }
4)線程回收方法(在子線程結束之前主線程阻塞)
1 線程回收函數
2 int pthread_join(pthread_t thread,void** retval); 3 thread:創建的時候傳出的第一個參數,指定回收哪個線程 4 retval:代表傳出線程的退出信息 5 返回值:成功返回0
主線程結束但是子線程還沒有結束也是不可以的,所以也要回收05_pthread_rtn.c
1 //主線程結束但是子線程還沒有結束也是不可以的,所以也要回收 2 //05_pthread_rtn.c 3 #include <stdio.h> 4 #include <unistd.h> 5 #include <pthread.h> 6 7 void* thr(void* arg){ 8 printf("I am a thread, tid=%lu\n",pthread_self()); 9 rerurn (void*)100; //使用pthread_exit((void*)100);也是可以得 10 } 11 12 int main(){ 13 pthread_t tid; 14 pthread_create(&tid,NULL,thr,NULL); 15 void* ret; 16 pthread_join(tid,&ret); //等待線程號為tid的線程結束,主線程會阻塞在這里,ret為該線程返回的值 17 18 printf("ret exit with %d\n",(int)ret); 19 20 pthread_exit(NULL); //退出主線程 21 }
5)殺死線程
View Code
1 //05_pthread_cancel.c 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <pthread.h> 5 6 7 void* thr(void* arg){ 8 while(1){ 9 printf("I am a thread, tid=%lu\n",pthread_self()); 10 sleep(1); 11 } 12 rerurn NULL; 13 } 14 15 int main(){ 16 pthread_t tid; 17 pthread_create(&tid,NULL,thr,NULL); 18 19 sleep(5); 20 pthread_cancel(tid); //將線程號為tid的線程殺死,成功返回0,失敗返回一個error 21 22 pthread_join(tid,&ret); 23 printf("ret exit with %d\n",(int)ret); //由於tid的線程已經被殺死,所以ret=-1 24 25 rerutn 0; 26 }
6)實現線程分離:pthread_detach(),執行這個函數之后,這個線程就不要回收,系統自動回收
05_pthread_detach.c
1 //05_pthread_detach.c 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <pthread.h> 5 #include <string.h> //for strerror() 6 7 8 void* thr(void* arg){ 9 10 printf("I am a thread, tid=%lu\n",pthread_self()); 11 sleep(3); 12 13 rerurn NULL; 14 } 15 16 int main(){ 17 pthread_t tid; 18 pthread_create(&tid,NULL,thr,NULL); 19 20 21 pthread_detach(tid); //此時不用回收tid線程,如果再使用pthread_join()的話就會報錯了 22 23 int ret=0; 24 if((ret=pthread_join(tid,NULL)) > 0){ //如果阻塞失敗 25 printf("join error:%d,%s\n",ret,strerror(ret)); 26 } 27 28 rerutn 0; 29 }
7)創建多個線程+線程傳參 線程傳入參數為pthread_create()的最后一個形參
View Code
1 //創建多個線程+線程傳參 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <pthread.h> 5 #include <string.h> //for strerror() 6 7 8 void* thr(void* arg){ 9 10 int num=(int)arg; 11 printf("I am %d thread, tid=%lu\n",num,pthread_self()); 12 13 rerurn (void*)(100+num); 14 } 15 int main(){ 16 //創建5個線程,這5個線程都執行thr()函數 17 pthread_t tid[5]; 18 for(int i=0;i<5;++i){ 19 pthread_create(&tid,NULL,thr,(void*)i); //參數i可以傳遞給線程形參arg 20 } 21 22 //阻塞,等待線程結束 23 for(int i=0;i<5;++i){ 24 void* ret; 25 pthread_join(tid[i],&ret); //優先等待第一個線程 26 printf("thread i=%d,ret=%d\n",i,(int)ret); 27 } 28 29 30 rerutn 0; 31 }
8)線程屬性設置函數 設置pthread_creat()第三個形參
1 int pthread_attr_init(pthread_attr_t *attr); //初始化線程屬性 2 int pthread_attr_destroy(pthread_attr_t *attr); //銷毀線程屬性 3 4 //設置屬性分離態 5 int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate); 6 attr:初始化的屬性 7 detachstate可以選擇如下: 8 PTHREAD_CREATE_DETACHED //實現線程分離,默認,不允許使用join回收線程,系統自動回收線程 9 PTHREAD_CREATE_JOINABLE //允許使用join回收線程
1 //線程屬性設置 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <pthread.h> 5 #include <string.h> //for strerror() 6 7 8 void* thr(void* arg){ 9 10 printf("I am thread, tid=%lu\n",pthread_self()); 11 12 rerurn (void*)(100+num); 13 } 14 int main(){ 15 //屬性初始化 16 pthread_attr_t attr; 17 pthread_attr_init(&attr); //attr默認是PTHREAD_CREATE_DETACHED 18 19 pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //設置attr的屬性為PTHREAD_CREATE_DETACHED,不使用join阻塞回收線程 20 21 //創建線程 22 pthread_t tid; 23 pthread_create(&tid,attr,thr,(void*)i); 24 25 int ret=0; 26 if((ret=pthread_join(tid,NULL)) > 0){ //如果阻塞失敗 27 printf("join error:%d,%s\n",ret,strerror(ret)); 28 } 29 30 //屬性銷毀 31 pthread_attr_destroy(&attr); 32 33 rerutn 0; 34 }
9)最多可以創建的線程數目
最多可以創建的線程數目:
cpu核數*2+2
(11)線程的訪問控制 m21111
1)線程互斥
互斥意味着“排它”,即兩個線程不能同時進入被互斥保護的代碼,但是兩個線程誰先進入被保護的代碼是不確定的。
在兩個線程執行的過程中先將互斥量加鎖的,先執行接下來的代碼,沒有將互斥量加鎖的只能阻塞等待對方對互斥量解鎖。
Linux中創建互斥量、對互斥量加鎖的步驟如下:
1 pthread_mutex_t my_mutex; //創建互斥量,my_mutex一般是一個全局變量 2 void pthreadOne(){ 3 pthread_mutex_init(&my_mutex, NULL); //互斥量初始化 4 pthread_mutex_lock(my_mutex); //對互斥量上鎖 5 ... //被保護的代碼 6 pthread_mutex_unlock(my_mutex); //對互斥量解鎖 7 }
2)線程同步
同步是在互斥的基礎上,通過其他機制實現對資源的有序訪問。同步協調多個相互關聯的線程合作完成工作,彼此之間知道對方的存在,執行順序是有序的,比如生產者和消費者模型,必須是生產者先將產品生產出來(先執行生產者線程),消費者再去消費(執行消費者線程)。
Linux下的C語言編程有多種線程同步機制,最典型的是條件變量(condition variable),使用條件變量實現線程同步的步驟框架:所以實現同步最關鍵的還是在於:
(1)在LInux中是寫了一個if判斷語句,if括號中的條件是該線程執行不下去的條件,然后把pthread_cond_wait()放到if里面;
(2)在windows中是使用了Lambda表達式,根據該表達式的返回值來決定pthread_cond_wait()是否該發生阻塞,具體可以參考這里
1 #include <stdio.h> 2 #include <pthread.h> 3 4 pthread_cond_t notempty; //創建一個條件變量,該條件變量一般是一個全局變量 5 pthread_cond_t notfull; //創建一個條件變量,該條件變量一般是一個全局變量 6 pthread_mutex_t my_mutex; //創建互斥量,my_mutex一般是一個全局變量 7 void pthreadOne(){ 8 pthread_mutex_lock(&my_mutex); //對互斥量加鎖 9 if(){ //該線程執行不下去的條件,進入后會將my_mutex解鎖,並阻塞等待另一個線程的喚醒,喚醒后對my_mutex加鎖,並向下執行 10 pthread_cond_wait(¬empty, my_mutex); 11 } 12 ... //do something 13 pthread_cond_signal(notfull); //喚醒線程二中的pthread_cond_wait() 14 pthread_mutex_unlock(&my_mutex); //對互斥量解鎖 15 } 16 void pthreadTwo(){ 17 pthread_mutex_lock(&my_mutex); //對互斥量加鎖 18 if(){ //該線程執行不下去的條件,進入后會將my_mutex解鎖,並阻塞等待另一個線程的喚醒,喚醒后對my_mutex加鎖,並向下執行 19 pthread_cond_wait(¬full, my_mutex); 20 } 21 ... //do something 22 pthread_cond_signal(notempty); //喚醒線程一中的pthread_cond_wait() 23 pthread_mutex_unlock(&my_mutex); //對互斥量解鎖 24 } 25 int main(){ 26 pthread_mutex_init(my_mutex, NULL); //互斥量初始化 27 pthread_cond_init(notempty, NULL); //條件變量初始化 28 pthread_cond_init(notfull, NULL); //條件變量初始化 29 }
使用線程同步的例子:生產者和消費者
1 #include <stdio.h> 2 #include <pthread.h> 3 #define BUFFER_SIZE 16 // 緩沖區數量 4 struct prodcons 5 { 6 // 緩沖區相關數據結構 7 int buffer[BUFFER_SIZE]; /* 實際數據存放的數組*/ 8 pthread_mutex_t lock; /* 互斥體lock 用於對緩沖區的互斥操作 */ 9 int readpos, writepos; /* 讀寫指針*/ 10 pthread_cond_t notempty; /* 緩沖區非空的條件變量 */ 11 pthread_cond_t notfull; /* 緩沖區未滿的條件變量 */ 12 }; 13 /* 初始化緩沖區結構 */ 14 void init(struct prodcons *b) 15 { 16 pthread_mutex_init(&b->lock, NULL); 17 pthread_cond_init(&b->notempty, NULL); 18 pthread_cond_init(&b->notfull, NULL); 19 b->readpos = 0; 20 b->writepos = 0; 21 } 22 /* 將產品放入緩沖區,這里是存入一個整數*/ 23 void put(struct prodcons *b, int data) 24 { 25 pthread_mutex_lock(&b->lock); 26 /* 等待緩沖區未滿*/ 27 if ((b->writepos + 1) % BUFFER_SIZE == b->readpos) 28 { 29 /*pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *my_mutex)會做三件事 30 * 1)將my_mutex解鎖 31 * 2)阻塞當前的線程在pthread_cond_wait()函數這里 32 * 3)在當前線程被喚醒的時候,再次對my_mutex互斥量加鎖,並繼續向下執行下去 33 * 需要注意的是pthread_cond_wait()可以被pthread_cond_signal()喚醒 34 */ 35 pthread_cond_wait(&b->notfull, &b->lock); 36 } 37 /* 寫數據,並移動指針 */ 38 b->buffer[b->writepos] = data; 39 b->writepos++; 40 if (b->writepos >= BUFFER_SIZE) 41 b->writepos = 0; 42 /* 設置緩沖區非空的條件變量*/ 43 pthread_cond_signal(&b->notempty); //喚醒第54行的pthread_cond_wait() 44 pthread_mutex_unlock(&b->lock); 45 } 46 /* 從緩沖區中取出整數*/ 47 int get(struct prodcons *b) 48 { 49 int data; 50 pthread_mutex_lock(&b->lock); 51 /* 等待緩沖區非空*/ 52 if (b->writepos == b->readpos) //如果此時線程一寫的位置恆等於線程二讀的位置,那么執行pthread_cond_wait(),阻塞等待線程一喚醒 53 { 54 pthread_cond_wait(&b->notempty, &b->lock); 55 } 56 /* 讀數據,移動讀指針*/ 57 data = b->buffer[b->readpos]; 58 b->readpos++; 59 if (b->readpos >= BUFFER_SIZE) 60 b->readpos = 0; 61 /* 設置緩沖區未滿的條件變量*/ 62 pthread_cond_signal(&b->notfull); //喚醒第35行的pthread_cond_wait() 63 pthread_mutex_unlock(&b->lock); 64 return data; 65 } 66 67 /* 測試:生產者線程將1 到10000 的整數送入緩沖區,消費者線 68 程從緩沖區中獲取整數,兩者都打印信息*/ 69 #define OVER ( - 1) 70 struct prodcons buffer; 71 void *producer(void *data) 72 { 73 int n; 74 for (n = 0; n < 10000; n++) 75 { 76 printf("%d --->\n", n); 77 put(&buffer, n); 78 } put(&buffer, OVER); 79 return NULL; 80 } 81 82 void *consumer(void *data) 83 { 84 int d; 85 while (1) 86 { 87 d = get(&buffer); 88 if (d == OVER) 89 break; 90 printf("--->%d \n", d); 91 } 92 return NULL; 93 } 94 95 int main(void) 96 { 97 pthread_t th_a, th_b; 98 void *retval; 99 init(&buffer); 100 /* 創建生產者和消費者線程*/ 101 pthread_create(&th_a, NULL, producer, 0); 102 pthread_create(&th_b, NULL, consumer, 0); 103 /* 等待兩個線程結束*/ 104 pthread_join(th_a, &retval); 105 pthread_join(th_b, &retval); 106 return 0; 107 }
執行步驟
/* 最開始結構體中的writepos是等於readpos的(都等於0),所以get()中的pthread_cond_wait()會發生阻塞, 而put()中的pthread_cond_wait()不會發生阻塞,接着去執行put()中其余的代碼,直到執行到put()中的pthread_cond_signal() 將get()中的pthread_cond_wait()喚醒,此時會等待put()中對b->lock互斥量的解鎖,put()中對b->lock解鎖完畢后,get()中的 pthread_cond_wait()會對b->block加鎖,后繼續執行get()中的其余代碼,即讀數據。 */
3)線程互斥和線程同步的區別
互斥是通過競爭對資源的獨占使用,彼此之間不需要知道對方的存在,執行順序是一個亂序。
同步是協調多個相互關聯線程合作完成任務,彼此之間知道對方存在,執行順序往往是有序的。
(12)線程中的互斥鎖和自旋鎖
0問題的引入,如下多線程代碼
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> //for sleep() 4 5 #define THREAD_NUM 10 6 7 //arg是thread_creat()第四個參數的形參 8 void *thread_proc(void *arg){ 9 int *pCount=(int*)arg; 10 int i=0; 11 while(i++ < 100){ 12 (*pCount)++; 13 sleep(1); 14 } 15 } 16 17 int main(){ 18 pthread_t threadId[THREAD_NUM]={0}; //傳入參數,用來保存10個線程的線程id 19 int count=0; 20 for(int i=0;i<THREAD_NUM;++i){ 21 pthread_create(&threadId[i],NULL,thread_proc,&count); //thread_proc()是子線程要執行的函數 22 } 23 for(int i=0;i<100;i++){ 24 printf("count--->%d\n",count); 25 sleep(1); 26 } 27 return 0; 28 }
輸入如下命令:
gcc linux_lock.c -o test -lpthread //勿忘加上-lpthread
./test
可能出現的問題:
一個進程加100,那么10個進程就將count加到了1000,但是上面那個程序執行的count++並不是原子操作,匯編器會將這一句轉換為
三句,那么線程1在執行第二句還沒有執行完的時候被線程2打斷了,那么此次執行就會失敗,所以上面的代碼並不會將cout加到1000
1 使用互斥鎖
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> //for sleep() 4 5 #define THREAD_NUM 10 6 7 pthread_mutex_t mutex; //創建互斥量 8 9 //arg是thread_creat()第四個參數的形參 10 void *thread_proc(void *arg){ 11 int *pCount=(int*)arg; 12 int i=0; 13 while(i++ < 100000){ 14 //加鎖 15 pthread_mutex_lock(&mutex); 16 (*pCount)++; 17 //解鎖 18 pthread_mutex_unlock(&mutex); 19 usleep(1); 20 } 21 } 22 23 int main(){ 24 //傳入參數,用來保存10個線程的線程id 25 pthread_t threadId[THREAD_NUM]={0}; 26 27 int count=0; 28 29 //初始化互斥量 30 pthread_mutex_init(&mutex,NULL); 31 32 33 for(int i=0;i<THREAD_NUM;++i){ 34 //thread_proc()是子線程要執行的函數 35 pthread_create(&threadId[i],NULL,thread_proc,&count); 36 } 37 38 for(int i=0;i<100;i++){ 39 printf("count--->%d\n",count); 40 sleep(1); 41 } 42 return 0; 43 }

2使用自旋鎖
1 //自旋鎖 2 #include <stdio.h> 3 #include <pthread.h> 4 #include <unistd.h> //for sleep() 5 6 #define THREAD_NUM 10 7 8 //創建自旋鎖 9 pthread_spinlock_t spinlock; 10 11 //arg是thread_creat()第四個參數的形參 12 void *thread_proc(void *arg){ 13 int *pCount=(int*)arg; 14 int i=0; 15 while(i++ < 100000){ 16 pthread_spin_lock(&spinlock); 17 (*pCount)++; 18 pthread_spin_unlock(&spinlock); 19 sleep(1); 20 } 21 } 22 23 int main(){ 24 pthread_t threadId[THREAD_NUM]={0}; //傳入參數,用來保存10個線程的線程id 25 26 int count=0; 27 28 //自旋鎖的初始化 29 pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED); 30 31 for(int i=0;i<THREAD_NUM;++i){ 32 pthread_create(&threadId[i],NULL,thread_proc,&count); //thread_proc()是子線程要執行的函數 33 } 34 for(int i=0;i<100;i++){ 35 printf("count--->%d\n",count); 36 sleep(1); 37 } 38 return 0; 39 }

上面的自旋鎖實現中是有bug的,在主線程中的for循環中打印循環的次數太少,導致主線程先結束了,然后10個子線程會自動結束,所以下面的程序將主線程中的打印循環增加了
1 //自旋鎖 2 #include <stdio.h> 3 #include <pthread.h> 4 #include <unistd.h> //for sleep() 5 6 #define THREAD_NUM 10 7 8 //創建自旋鎖 9 pthread_spinlock_t spinlock; 10 11 //arg是thread_creat()第四個參數的形參 12 void *thread_proc(void *arg){ 13 int *pCount=(int*)arg; 14 int i=0; 15 while(i++ < 100000){ 16 pthread_spin_lock(&spinlock); 17 (*pCount)++; 18 pthread_spin_unlock(&spinlock); 19 sleep(1); 20 } 21 } 22 23 int main(){ 24 pthread_t threadId[THREAD_NUM]={0}; //傳入參數,用來保存10個線程的線程id 25 26 int count=0; 27 28 //自旋鎖的初始化 29 pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED); 30 31 for(int i=0;i<THREAD_NUM;++i){ 32 pthread_create(&threadId[i],NULL,thread_proc,&count); //thread_proc()是子線程要執行的函數 33 } 34 for(int i=0;i<10000;i++){ 35 printf("count--->%d\n",count); 36 sleep(1); 37 } 38 return 0; 39 }

3 互斥鎖和自旋鎖的區別
互斥鎖:線程1在在發生阻塞的時候,CPU可以去做一些其他的事情;適用於某一個線程加了自旋鎖之后,但是該線程持續的時間較長(即要保護的資源代碼較復雜)的情況。
自旋鎖:假如某一個線程獲得了自旋鎖,那么該線程會一直占用着CPU;適用於某一個線程加了自旋鎖之后,但是該線程持續的時間較短的情況。
mutex:對操作時間比較長的加鎖
spinlock:對操作時間比較短的加鎖
(13)使用線程實現簡單的生產者和消費者--另外附上xshell和xftp的使用方法
1)xshell使用方法



xftp的使用方法


實現代碼:
1 /* 2 生產者fun1()、消費者模型fun2() 3 */ 4 5 #include "stdio.h" 6 #include "stdlib.h" 7 #include "string.h" 8 #include "pthread.h" 9 #include "unistd.h" 10 11 //創建全局互斥量 12 pthread_mutex_t mutex; 13 //創建全局條件變量 14 pthread_cond_t cond; 15 //控制符 16 int flag=0; 17 18 //線程1 19 void* fun1(void* arg){ 20 while(1){ 21 //加鎖 22 pthread_mutex_lock(&mutex); 23 //改變標志位 24 flag=1; 25 printf("線程1開始執行\n"); 26 sleep(1); 27 //解鎖 28 pthread_mutex_unlock(&mutex); 29 //喚醒pthread_cond_wait(),如果沒有線程處於pthread_cond_wait()也會成功返回 30 pthread_cond_signal(&cond); 31 } 32 return NULL; 33 } 34 35 void* fun2(void* arg){ 36 while(1){ 37 //上鎖 38 pthread_mutex_lock(&mutex); 39 //如果flag==0成立,即fun1()沒有被執行,那么fun2()會被阻塞在if里面,保證了fun1()先執行 40 /* 41 *1)假如fun1()先獲得mutex鎖,那么會先執行fun1(),執行完fun1()后flag=1,不會執行下面的if 42 *2)假如fun2()先獲得mutex鎖,那么會先執行fun2(),此時falg==0成立,會執行pthread_cond_wait(&cond,&mutex); 43 * 該句會首先將mutex解鎖,並阻塞在這里,直到調用了pthread_cond_signal(&cond); 44 */ 45 if(flag==0){ 46 pthread_cond_wait(&cond,&mutex); 47 } 48 printf("線程2開始執行\n"); 49 //sleep(1); //這里最好不要有sleep(),否則實際運行中fun1()會比fun2()運行的次數多的多 50 //因為保證了從開始到結束先執行的都是fun1(),再執行fun2(),所以下面這一句就保證了一個周期內flag的初始值都是0 51 flag=0; 52 pthread_mutex_unlock(&mutex); 53 } 54 return NULL; 55 } 56 57 int main(){ 58 printf("main thread start......\n"); 59 sleep(1); 60 //創建接收線程id的變量 61 pthread_t tid1,tid2; 62 //初始化互斥量和條件變量 63 int ret=0; 64 ret=pthread_mutex_init(&mutex,NULL); 65 if(ret!=0){ 66 printf("pthread_mutex_init failed\n"); 67 return -1; 68 } 69 ret=pthread_cond_init(&cond,NULL); 70 if(ret!=0){ 71 printf("pthread_cond_init failed\n"); 72 return -1; 73 } 74 //創建進程 75 pthread_create(&tid1,NULL,fun1,NULL); 76 pthread_create(&tid2,NULL,fun2,NULL); 77 78 //等待兩個線程執行完畢 79 void* val; 80 pthread_join(tid1,&val); 81 pthread_join(tid2,&val); 82 83 return 0; 84 }

三、Linux下int main(int argc, char* argv[])的含義及解釋
1、argc、argv的具體含義及解釋
帶形參的main函數,如 main( int argc, char* argv[], char *env ) ,是UNIX、Linux以及Mac OS操作系統中C/C++的main函數標准寫法,並且是血統最純正的main函數寫法。
*argc和argv參數在用命令行編譯程序時有用。main( int argc, char* argv[], char **env ) 中
第一個參數,int型的argc,為整型,用來統計程序運行時發送給main函數的命令行參數的個數,在VS中默認值為1。
第二個參數,char型的argv[],為字符串數組,用來存放指向的字符串參數的指針數組,每一個元素指向一個參數。各成員含義如下:
argv[0]指向程序運行的全路徑名
argv[1]指向在DOS命令行中執行程序名后的第一個字符串
argv[2]指向執行程序名后的第二個字符串
argv[3]指向執行程序名后的第三個字符串
argv[argc]為NULL
第三個參數,char**型的env,為字符串數組。env[]的每一個元素都包含ENVVAR=value形式的字符串,其中ENVVAR為環境變量,value為其對應的值。平時使用到的比較少
2、測試
1 #include <stdio.h> 2 using namespace std; 3 4 int main(int argc, char ** argv) 5 { 6 int i; 7 for (i = 0; i < argc; i++) 8 printf("Argument %d is %s\n", i, argv[i]); 9 return 0; 10 }
測試結果;
3、Linux下調試方法---使用gdb
調試函數的一系列命令,源代碼如下main.c
1 #include <stdio.h> 2 3 int add_range(int low, int high) 4 { 5 int i,sum; 6 for(i=low;i<=high;i++) 7 sum=sum+i; 8 return sum; 9 } 10 int main(void) 11 { 12 int result[100]; 13 result[0]=add_range(1,10); 14 result[1]=add_range(1,100); 15 printf("result[0]=%d\nresult[1]=%d\n",result[0],result[1]); 16 return 0; 17 }
結果為55 5015 與正確結果不同,調試如下
1、使用gcc -g main.c -o main #生成linux下編譯后的文件
2、gdb main #進入main函數的調試
3、start #開始調試
4、n(next) #下一步
5、s(step) #跳進函數
6、p(print) sum #查看sum變量的值
7、finish # 跳出當前函數,回到main函數
第一次輸入123正確,第二次錯誤
調試命令如下:
start 啟動調試
display sum 每次定下來都顯示sum的值
undisplay 取消對這個變量的跟蹤
b(break) 9 在第9行設置一個斷點 參數也可以是函數名
c(continue) 表示連續運行,跳到下一個斷點
i breakpoints 顯示已經設置的斷點
delete breakpoints 2 刪除斷點2
delete breakpoints 刪除所有的斷點
disable breakpoints 3 使某個斷點失效
break 9 if sum != 0 滿足條件才可以使用該斷點
r 重新從程序開始連續執行
x 命令打印存儲器中的內容 x/7b input 7b是打印格式,b表示每個字節一組,7表示打印7組
watch input[5] 跟蹤某變量

四、ubuntu下常用函數詳細解釋
1、open()函數
1 #include<sys/types.h> 2 #include<sys/stat.h> 3 #include<fcntl.h>
函數原型:
1 int open( const char * pathname, int flags); 2 int open( const char * pathname,int flags, mode_t mode);
函數參數說明:
1 參數pathname 指向欲打開的文件路徑字符串。 2 3 下列是參數flags 所能使用的旗標: 4 5 O_RDONLY 以只讀方式打開文件 6 7 O_WRONLY 以只寫方式打開文件 8 9 O_RDWR 以可讀寫方式打開文件。上述三種旗標是互斥的,也就是不可同時使用,但可與下列的旗標利用OR(|)運算符組合。 10 11 O_CREAT 若欲打開的文件不存在則自動建立該文件。 12 13 O_EXCL 如果O_CREAT也被設置,此指令會去檢查文件是否存在。文件若不存在則建立該文件,否則將導致打開文件錯誤。此外,若O_CREAT與O_EXCL同時設置,並且欲打開的文件為符號連接,則會打開文件失敗。 14 15 O_NOCTTY 如果欲打開的文件為終端機設備時,則不會將該終端機當成進程控制終端機。 16 17 O_TRUNC 若文件存在並且以可寫的方式打開時,此旗標會令文件長度清為0,而原來存於該文件的資料也會消失。 18 19 O_APPEND 當讀寫文件時會從文件尾開始移動,也就是所寫入的數據會以附加的方式加入到文件后面。 20 21 O_NONBLOCK 以不可阻斷的方式打開文件,也就是無論有無數據讀取或等待,都會立即返回進程之中。 22 23 O_NDELAY 同O_NONBLOCK。 24 25 O_SYNC 以同步的方式打開文件。 26 27 O_NOFOLLOW 如果參數pathname 所指的文件為一符號連接,則會令打開文件失敗。 28 29 O_DIRECTORY 如果參數pathname 所指的文件並非為一目錄,則會令打開文件失敗。 30 31 此為Linux2.2以后特有的旗標,以避免一些系統安全問題。參數mode 則有下列數種組合,只有在建立新文件時才會生效,此外真正建文件時的權限會受到umask值所影響,因此該文件權限應該為(mode-umaks)。 32 33 S_IRWXU00700 權限,代表該文件所有者具有可讀、可寫及可執行的權限。 34 35 S_IRUSR 或S_IREAD,00400權限,代表該文件所有者具有可讀取的權限。 36 37 S_IWUSR 或S_IWRITE,00200 權限,代表該文件所有者具有可寫入的權限。 38 39 S_IXUSR 或S_IEXEC,00100 權限,代表該文件所有者具有可執行的權限。 40 41 S_IRWXG 00070權限,代表該文件用戶組具有可讀、可寫及可執行的權限。 42 43 S_IRGRP 00040 權限,代表該文件用戶組具有可讀的權限。 44 45 S_IWGRP 00020權限,代表該文件用戶組具有可寫入的權限。 46 47 S_IXGRP 00010 權限,代表該文件用戶組具有可執行的權限。 48 49 S_IRWXO 00007權限,代表其他用戶具有可讀、可寫及可執行的權限。 50 51 S_IROTH 00004 權限,代表其他用戶具有可讀的權限 52 53 S_IWOTH 00002權限,代表其他用戶具有可寫入的權限。 54 55 S_IXOTH 00001 權限,代表其他用戶具有可執行的權限。
返回值:
若所有欲核查的權限都通過了檢查則返回0值,表示成功,只要有一個權限被禁止則返回-1。
錯誤代碼:
1 EEXIST 參數pathname 所指的文件已存在,卻使用了O_CREAT和O_EXCL旗標。 2 3 EACCESS 參數pathname所指的文件不符合所要求測試的權限。 4 5 EROFS 欲測試寫入權限的文件存在於只讀文件系統內。 6 7 EFAULT 參數pathname指針超出可存取內存空間。 8 9 EINVAL 參數mode 不正確。 10 11 ENAMETOOLONG 參數pathname太長。 12 13 ENOTDIR 參數pathname不是目錄。 14 15 ENOMEM 核心內存不足。 16 17 ELOOP 參數pathname有過多符號連接問題。 18 19 EIO I/O 存取錯誤。
2、read()
需要包含的頭文件:
1 #include<unistd.h>
函數原型:
1 ssize_t read(int fd,void * buf ,size_t count);
函數即參數說明:
read()會把參數fd 所指的文件傳送count個字節到buf指針所指的內存中。若參數count為0,則read()不會有作用並返回0。返回值為實際讀取到的字節數,如果返回0,表示已到達文件尾或是無可讀取的數據,此外文件讀寫位置會隨讀取到的字節移動。
附加說明:
如果順利read()會返回實際讀到的字節數,最好能將返回值與參數count作比較,若返回的字節數比要求讀取的字節數少,則有可能讀到了文件尾、從管道(pipe)或終端機讀取,或者是read()被信號中斷了讀取動作。當有錯誤發生時則返回-1,錯誤代碼存入errno中,而文件讀寫位置則無法預期。
錯誤代碼:
EINTR 此調用被信號所中斷。
EAGAIN 當使用不可阻斷I/O 時(O_NONBLOCK),若無數據可讀取則返回此值。
EBADF 參數fd 非有效的文件描述詞,或該文件已關閉。
3、write()
需要包含的頭文件:
1 #include<unistd.h>
函數原型:
1 ssize_t write (int fd,const void * buf,size_t count);
函數即參數說明:
write()會把參數buf所指的內存寫入count個字節到參數fd所指的文件內。當然,文件讀寫位置也會隨之移動。
返回值:
如果順利write()會返回實際寫入的字節數。當有錯誤發生時則返回-1,錯誤代碼存入errno中。
錯誤代碼:
EINTR 此調用被信號所中斷。
EAGAIN 當使用不可阻斷I/O 時(O_NONBLOCK),若無數據可讀取則返回此值。
EADF 參數fd非有效的文件描述詞,或該文件已關閉。
4、ftruncate()和truncate
作用:將文件大小指定為參數length的值
二者都需要包含的頭文件:
1 #include <unistd.h> 2 #include <sys/types.h>
函數原型:
1 int truncate(const char *path, off_t length); 2 int ftruncate(int fd, off_t length);
函數參數:
可以看到兩者有不同的使用方式,truncate是通過文件路徑來裁剪文件大小,而ftruncate是通過文件描述符進行裁剪;
返回值:成功返回0,失敗返回-1
二者均要求文件具有可寫權限
注意:
如果要裁剪的文件大小大於設置的off_t length,文件大於length的數據會被裁剪掉
如果要裁剪的文件小於設置的offt_t length,則會進行文件的擴展,並且將擴展的部分都設置為\0,文件的偏移地址不會發生變化
5、struct stat結構體(包含了文件的信息)
需要包含的頭文件
1 #include <sys/types.h> 2 #include <sys/stat.h>
結構體原型:
1 struct stat 2 { 3 4 dev_t st_dev; /* ID of device containing file -文件所在設備的ID*/ 5 6 ino_t st_ino; /* inode number -inode節點號*/ 7 8 mode_t st_mode; /* protection -保護模式?*/ 9 10 nlink_t st_nlink; /* number of hard links -鏈向此文件的連接數(硬連接)*/ 11 12 uid_t st_uid; /* user ID of owner -user id*/ 13 14 gid_t st_gid; /* group ID of owner - group id*/ 15 16 dev_t st_rdev; /* device ID (if special file) -設備號,針對設備文件*/ 17 18 off_t st_size; /* total size, in bytes -文件大小,字節為單位*/ 19 20 blksize_t st_blksize; /* blocksize for filesystem I/O -系統塊的大小*/ 21 22 blkcnt_t st_blocks; /* number of blocks allocated -文件所占塊數*/ 23 24 time_t st_atime; /* time of last access -最近存取時間*/ 25 26 time_t st_mtime; /* time of last modification -最近修改時間*/ 27 28 time_t st_ctime; /* time of last status change - */ 29 30 };
stat()函數原型
1 int stat(const char *filename, struct stat *buf); //! prototype,原型
將文件名字為filename的文件信息保存在buf中
6、SIGALRM信號和alarm()函數和signal()函數
在進行阻塞式系統調用時,為避免進程陷入無限期的等待,可以為這些阻塞式系統調用設置定時器。Linux提供了alarm系統調用和SIGALRM信號實現這個功能。
要使用定時器,首先要安裝SIGALRM信號。如果不安裝SIGALRM信號,則進程收到SIGALRM信號后,缺省的動作就是終止當前進程。SIGALRM信號安裝成功后,在什么情況下進程會收到該信號呢?這就要依賴於Linux提供的定時器功能。在Linux系統下,每個進程都有惟一的一個定時器,該定時器提供了以秒為單位的定時功能。在定時器設置的超時時間到達后,調用alarm的進程將收到SIGALRM信號。alarm系統調用的原型為:
#include <unistd.h> unsigned int alarm(unsigned int seconds);
參數說明:seconds:要設定的定時時間,以秒為單位。在alarm調用成功后開始計時,超過該時間將觸發SIGALRM信號
返回值:返回當前進程以前設置的定時器剩余秒數。
安裝SIGALRM信號方法:signal(SIGALRM,test);
其中test是一個函數,執行SIGNALRM信號發出以后要執行的動作
測試用例:
1 #include <stdio.h> 2 #include <signal.h> //for signal() 3 #include <unistd.h> //for sleep() 4 5 6 void test(){ 7 8 printf("The timer is over\n"); 9 10 } 11 12 13 int main(){ 14 15 if((signal(SIGALRM,test))==SIG_ERR){ 16 17 perror("siganl"); 18 19 return -1; 20 21 } 22 23 alarm(5); //5s之后發出SIGNALRM信號,且只發出SIGALRM信號一次 24 25 for(int i=1;i<12;++i){ 26 27 printf("sleep for %d s\n",i); 28 29 sleep(1); 30 31 } 32 33 return 0; 34 35 }
運行結果:

signal()函數:作用是將信號signum和函數handler()關聯
1 #include <signal.h> 2 sighandler_t siganl(int signum,sighandler_t handler);
返回值:
成功:返回信號處理函數的一個值,失敗則返回SIG_ERR
需要注意的是信號對應的函數返回值必須為空,形參必須為int類型,也可以沒有
五、Linux文件權限
1、擁有者、用戶組、其他人的概念
1 /* 2 假如有兩個開發團隊:testgroup1和treatgroup,其中testgroup1中有成員test1、test2、test3,treatgroup中有treat1、treat2 3 且有一個文件a是test1創建的,那么對文件a來說tes1是文件a的擁有者,testgroup是文件a的用戶組, 4 再假如treat1和test1認識,那么treat1就是其他人 5 */
2、查看文件權限的命令:ls -al、含義解釋及舉例




舉例1:

也就是說用戶組或者是其他人如果沒有x權限的話,是無法進入到該目錄的



3、修改文件屬性
(1)修改文件所在的用戶組:chgrp命令

(2)修改文件擁有者:chown命令

chown命令的使用場景

(3)修改文件的讀寫屬性:chmod命令





(4)絕對路徑和相對路徑
1 /* 2 絕對路徑:由根目錄(/)為開始寫的路徑。如:/home/dmtsai/.bashrc 3 相對路徑:只要開頭不是以/就是相抵路徑的寫法。如:./home/dmtsai或../../home/dmtsai等 4 相對路徑是以“當前所在路徑的相對位置”來表示的。舉例來說,當前在/home目錄下,如果想進入到ar/log目錄,可以有如下寫法: 5 cd ar/log (絕對路徑寫法) 6 cd ..ar/log (相對路徑寫法),因為當前在/home中,所以要先回到上一層(../)之后,才能繼續向ar移動 7 8 要注意的是: 9 .:表示當前目錄,也可以使用./來表示 10 ..:表示上一層目錄,也可以使用../來表示 11 */
舉例:
1 /* 2 假設要在任意一個目錄下切換到根目錄下的etc,就要使用cd /etc ,這就是絕對路徑。如果使用cd etc則表示切換到當前這個目錄下的etc目錄下。 3 再例如要從/usr/share/doc切換到/usr/share/man,可以寫成cd ../man 這就是相對路徑的寫法,而cd /usr/share/man就是 4 絕對路徑的寫法。 5 6 7 系統用戶個人家目錄在/home里,這個目錄可能是使用最頻繁的目錄 8 /usr程序安裝目錄 9 ar記錄了常用的數據 10 /etc防止系統配置文件 11 */
(5)Linux支持的文件系統
每種操作系統都有其獨特的讀取文件的方法,也就是說操作系統對硬盤讀取的方法不同,就差生了不同的文件系統。
舉例來說Windows8默認的文件系統是FAT(或FAT16)、Windows2000有NTFS文件系統、Linux支持ext2文件系統,但是ext2缺乏日志管理,於是就產生了ext3
ext2和ext3的區別:要向硬盤寫入數據的時候,ext2直接將數據寫入,但ext3會將這個“要開始寫入的信息”寫入日志記錄區,然后才開始寫入數據
在寫如數據之后,又將“完成寫入的動作”的信息寫入日志記錄區。
(6)目錄與路徑---常見的幾個家目錄
這里主要介紹了cd命令
1 /* 2 cd命令 3 .:表示磁層目錄 4 ..:表示上一層目錄 5 -:表示前一個工作目錄 6 ~:表示“當前用戶身份”所在的家目錄 7 ~account:表示account用戶的家目錄 8 我知道根目錄(/)是所有目錄的最頂層,那么/有..嗎?可以使用ls -al 查看,答案是有的 9 dmtsai用戶的家目錄是/home/dmtsai,root家目錄是/root,假設root以root身份在linux系統中,這幾個特殊目錄的意義是: 10 [root@linux ~]# cd ~dmtsai #表示去用戶dmtsai的家目錄,即/home/dmtsai 11 [root@linux ~]# cd ~ #表示會到root用戶(當前用戶)的家目錄,即/root目錄 12 [root@linux ~]# cd #沒有加上任何路徑,也表示回到自己的家目錄 13 [root@linux ~]# cd .. #表示去當前目錄的上一層目錄,即去/root的上一層目錄 14 [root@linux ~]# cd - #表示回到剛才的目錄,也就是/root 15 [root@linux ~]# cd /var/spool/mail #這是絕對路徑的寫法,直接指定要去的完整路徑的名稱,可以在任意目錄下切換到目的目錄下 16 [root@linux ~]# cd ../mqueue #這是相對路徑的寫法,我們由/var/spool/mail路徑切換到/var/spool/mqueue這樣寫 17 注意目錄名字和cd之間有一個空格,登錄linux系統后,root會在root的家目錄下,即/root下 18 */
(7)Linux常見的命令---pwd、mkdir、cp、mv
pwd命令
1 pwd(顯式當前所在的目錄) 2 [root@linux ~]# pwd [-P] 3 參數: -P:顯式出實際路徑,而非使用連接路徑(鏈接文件即快捷方式) 4 范例: 5 [root@linux ~]# pwd 6 /root 7 [root@linux ~]# cd /var/mail 8 [root@linux mail]# pwd #提示符中只顯式mail,要顯式完全的路徑可以使用pwd 9 /var/mail #/var/mail是連接文件,顯式的是連接路徑 10 [root@linux mail]# pwd -P 11 /var/spool/mail #/var/mail是連接文件,顯式的是實際路徑(對連接文件來說這才是正確的路徑名字) 12 [root@linux mail]# ls -l /var/mail 13 lrwxrwxrwx 1 root root 10 Jun 25 08:25 /var/mail 14 由於/var/mail是連接文件(l),連接到/var/spool/mail,所以加上pwd -P后,不會以鏈接文件的數據顯式,而是顯式正確的完整路徑
mkdir命令
1 mkdir(創建新目錄) 2 [root@linux ~]# mkdir [-mp] 目錄名 3 參數: 4 -m:設置文件的權限, 5 -p:創建多級目錄 6 [root@linux ~]# mkdir -m 711 test2 #創建test2目錄,權限為rwx--x--x 7 [root@linux ~]# mkdir -p test1/test2/test3/test4 #創建多級目錄
cp命令
1 cp(復制文件或目錄copy)命令的使用方法 2 3 范例1:假如要將家目錄下的.bashrc復制到/tmp下,並重命名為bashrc 4 [root@linux ~]# cd /temp #先切換到/tmp目錄下 5 [root@linux tmp]# cp ~/.bashrc bashrc #將家目錄下的.bashrc復制到/tmp下,復制過來的bashrc的文件擁有者為當前操作用戶,在這里即為root 6 [root@linux tmp]# cp ~/.bashrc -i bashrc #加上-i參數,如果在/tmp目錄下已經存在了bashrc文件,那么會詢問你是否覆蓋掉原來的bashrc文件 7 [root@linux tmp]# cp ~/.bashrc -f bashrc #不管/tmp目錄下有沒有bashrc文件直接覆蓋掉 8 范例2:將/etc/目錄下的所有文件復制到/tmp下,注意復制目錄要使用-r參數,但是使用-r參數會使原文件的屬性發生改變 9 [root@linux tmp]# cp -r /etc/ /tmp #注意/etc/是源文件,/tmp是目標路徑,原文件的屬性會發生變化 10 [root@linux tmp]# cp -a /etc/ /tmp #原文件的屬性不會發生變化
mv命令
1 mv(移動文件與目錄或重命名)命令 2 [root@linux ~]# cd /tmp 3 [root@linux tmp]# cp ~/.bashrc bashrc #將家目錄中的.bashrc復制到/tmp中並命名為bashrc 4 [root@linux tmp]# mkdir mvtest #在/tmp中創建一個目錄mvtest 5 [root@linux tmp]# mv bashrc mvtest #將/tmp中的bashrc移動到mvtest中 6 [root@linux tmp]# mv mvtest mvtest2 #將/tmp中的mvtest重命名為mvtest2 7 [root@linux tmp]# mv bashrc1 bashrc2 mvtest2 #將/tmp中的bashrc1、bashrc2移動到mvtest中
1 lsof -i:8000 #查看端口8000使用情況 2 如下執行結果: 3 # lsof -i:8000 4 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME 5 lwfs 22065 root 6u IPv4 4395053 0t0 TCP *:irdmi (LISTEN) 6 可以看到8000端口已經被輕量級文件系統轉發服務lwfs占用 7 8 netstat -tunlp |grep 8000 #查看端口8000使用情況 9 # netstat -tunlp | grep 8000 10 tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 22065/lwfs 11 其中: 12 -t (tcp) 僅顯示tcp相關選項 13 -u (udp)僅顯示udp相關選項 14 -n 拒絕顯示別名,能顯示數字的全部轉化為數字 15 -l 僅列出在Listen(監聽)的服務狀態 16 -p 顯示建立相關鏈接的程序名
(8)文件默認權限
文件默認權限:umask
查看umask默認值方法:
1 //方法一 2 root@linux ~]# umask 3 0022 #第一個0先不用管,第二個0表示該文件的擁有者不去掉任何權限,第三個參數2表示該文件擁有者所在組去掉寫權限,第四個參數表示其他人去掉寫權限 4 //方法二 5 [root@linux ~]# umask -S 6 u=rwx,g=rx,o=rx
創建的文件或目錄的默認權限
(1)默認情況下創建的文件是沒有可執行(x)權限的,即只有rw權限,默認屬性如下:-rw-rw-rw
(2)默認情況下創建的目錄默認所有權限均開放:即drwxrwxrwx
umask指定的是“上面的默認權限值減去的權限”,比如我們要去掉r默認權限那么就輸入4;要去掉讀寫默認權限就輸入6
如果umask的值為0022,那么
當用戶創建文件時候:(-rw-rw-rw-)-(-----w--w-) = -rw-r--r--
當用戶創建目錄時候:(drwxrwxrwx)-(-----w--w-) = drwx-r-x-rx
測試:
1 [root@linux ~]# umask #首先查看一下umask的值 2 0022 #表示擁有者不去掉任何權限、所在組去掉寫權限、其他人去掉寫權限 3 [root@linux ~]# touch test1 #創建文件test1 4 [root@linux ~]# mkdir test2 #創建目錄test2 5 [root@linux ~]# ll #查看當前目錄下所有文件的屬性 6 -rw-r--r-- 1 root root 0 Jul 20 00:36 test1 #文件test1的屬性為-rw-rw-rw-去掉-----w--w-后的屬性 7 drwx-r-x-rx 2 root root 4096 Jul 20 00:36 test2 #目錄test2的屬性為drwxrwxrwx去掉-----w--w-后的屬性
一般文件擁有者所在的組對該文件是有寫權限的,其他人沒有寫權限,即umask的值應該為002,如何改變umaks的值?可以參考下面的方法
[root@linux ~]# umask 002 #更改umask的默認值
精典題目:


last、一些小問題
1、管理員模式下終端字體沒有顏色


但是這樣也並沒有改過來管理員模式下終端字體顏色

