ubuntu下配置C++编译环境,xshell和xftp的使用方法(最后)

目录

一、入门

  1、C++编译环境安装以及第一个C++程序

  2、编译多个cpp文件

  3、使用make+makefile编译多个cpp文件

  4、makefile系统的学习

二、Linux多进程相关

  1、进程基本知识

  2、fork()函数

  3、进程间数据共享

  4、exec()族函数

  5、孤儿进程与僵尸进程

  6、wait()使用方法及使用wait()解决僵尸进程

  7、waitpid()使用方法及使用waitpid()解决僵尸进程

  8、homweork

  9、进程间通信方式

    1)普通管道pipe---操作的是内核缓冲区(内存中的一块存储空间)

    2)FIFO(命名管道)

    3)mmap() 内存映射   匿名映射

    4)信号    对未决信号和阻塞信号的进一步理解以及屏蔽信号、解除屏蔽的方法

    5)使用信号回收子线程  设计到SIGCHLD信号的屏蔽和信号的解除

    6)sigaction结构体中的sa_mask信号集的解释  另在最后附上了常见信号的解释

  10、守护进程

二(1)、Linux多线程相关            m211

  (0)并行并发以及多进程多线程要解决的问题

    (1)多线程概念以及优缺点    m2111

  (2)创建一个线程                  m2112

  (3)线程退出方法

  (4)线程回收方法(在子线程结束之前主线程阻塞)

  (5)杀死线程

  (6)实现线程分离:pthread_detach(),执行这个函数之后,这个线程就不要回收,系统自动回收

  (7)创建多个线程+线程传参 线程传入参数为pthread_create()的最后一个形参

  (8)线程属性设置函数 设置pthread_creat()第三个形参

  (9)最多可以创建的线程数目

  (10)可以参考别人写的有关Linux多线程函数       m21110

  (11)线程的访问控制                m21111

    1)线程互斥

    2)线程同步

    3)线程互斥和线程同步的区别 

  (12)线程中的互斥锁和自旋锁

  (13)使用线程实现简单的生产者和消费者--另外附上xshell和xftp的使用方法

三、Linux下int main(int argc, char* argv[])的含义及解释

       1、argc和argv具体含义及解释

  2、测试

  3、Linux下调试方法---使用gdb

四、ubuntu下常用函数详细解释

  1、open()

  2、read()

  3、write()

  4、ftruncate()和truncate

  5、struct stat结构体(包含了文件的信息)

  6、SIGALRM信号和alarm()函数和signal()函数

五、Linux文件权限

  1、拥有者、用户组、其他人的概念

  2、查看文件权限的命令:ls -al、含义解释及举例

  3、修改文件属性

    (1)修改文件所在的用户组:chgrp命令

    (2)修改文件拥有着:chown命令

    (3)修改文件的读写属性:chmod命令

    (4)绝对路径和相对路径

    (5)Linux支持的文件系统

    (6)目录与路径---常见的几个家目录

    (7)Linux常见的命令---pwd、mkdir、cp、mvlsof、netstat

    (8)文件默认权限

last、一些小问题

  1、管理员模式下终端字体没有颜色

  2、xshell和xftp的使用方法

一、入门

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下的配置文件

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 */
View Code

 

进程控制块相关

 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)进程可以使用的资源上限
View Code

环境变量
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......
fork()举例(printf使用\n)
 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()举例(printf()使用\n)

使用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 }
View Code

但是上面的程序有可能出现父进程先执行完,但是子进程还没有执行完,所以该子进程即为孤儿进程
解决方法如下(父进程睡一会):

 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=200024         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 这表明父进程已死,子进程继续显示
View Code

 

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               段错误:操作非法地址 
execl()和execlp()函数原形及参数介绍
 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 }
execl()函数示例
 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 010 }
11 以上程序会执行ls -l这个命令行后结束
execlp()函数示例

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的参数
孤儿进程示例:父进程死了,子进程会被init进程领养
 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,杀死父进程
僵尸(zombie)进程示例:子进程死了,父进程没有回收子进程的资源及解决僵尸进程方法

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()解决僵尸进程:即让主进程等待,阻塞在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+则说明有僵尸进程
使用wait()函数解决僵尸进程

 

 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 }
使用wait()查看进程死亡原因

 

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 */
waitpid()函数原型介绍
 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 }
使用waitpid()回收一个线程
 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 }
使用wiatpid()回收多个线程

使用多个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 */
ps aux|grep xxx使用管道和多进程实现

测试结果:

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 }
读端全部关闭:会报错,产生一个信号SIGPIPE,程序会异常终止

程序执行结果:
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 } 
04_fifo_w.c

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 }
04_fifo_r.c

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 }
View Code

示例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 }
使用mmap()实现父子进程间的通信

在以上的代码中,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 }
无血缘关系使用mmap()实现进程通信,mmap()返回值为结构体

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_r.c

 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 919(暂停信号 )号信号不能捕捉,不能忽略,甚至不能阻塞
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的进程杀掉
kill()函数
 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 }
使用kill()在父进程中将子进程3删除掉

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 }
raise()举例

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 }
abort()函数举例

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
setitimer()头文件及原型参数介绍
 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 }
使用setitimer()非周期性的发送信号,只发送一次
 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 }
使用setitimer()周期性的发送信号
 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 }
使用setitimer()实现alarm()函数

使用重定向提高程序运行效率一个例子:

 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
View Code
 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-31信号在当前进程是否是未决信号

信号捕捉(即创建信号和函数的对应关系):防止进程意外死亡

 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,使用signal()函数和捕捉方法2:使用sigaction()函数
 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 }
举例:使用sigaction()捕捉信号

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 }
创建一个守护进程:每分钟在$Home/log/创建一个文件,程序名.时间戳

*****对未决信号和阻塞信号的进一步理解以及屏蔽信号、解除屏蔽的方法

信号常见的感念:

    实际执行信号的处理动作(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 }
使用SIGCHLD信号回收子线程
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 }
View Code

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 }
sa_mask_test.c

测试结果:

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
View Code

 

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 }
03_pthread_create.c

 

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 }
04_pthread_exit.c

 

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 }
主线程结束但是子线程还没有结束也是不可以的,所以也要回收 05_pthread_rtn.c

  

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 }
05_pthread_cancel.c

 

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 } 
05_pthread_detach.c

 

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 }
线程属性设置,涉及到pthread_create()第三个参数

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(&notempty, 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(&notfull, 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 }
View Code

输入如下命令:

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 }
View Code

 

 

 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 }
View Code

 

 

 

 

 

 

 

 

 

三、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 }            
View Code

结果为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 权限,代表其他用户具有可执行的权限。
open()函数参数说明

返回值:

若所有欲核查的权限都通过了检查则返回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 存取错误。
View Code

 

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 };
View Code

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()和SIGALRM信号和alarm()测试

运行结果:

 

 

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中

 lsof和netstat查看端口使用情况命令

 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、管理员模式下终端字体没有颜色

 

 但是这样也并没有改过来管理员模式下终端字体颜色

 

 

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

 

posted @ 2020-04-25 22:49  兵临城下的匹夫  阅读(1102)  评论(0编辑  收藏  举报
TOP