Linux系统编程 引言 概念 :
Linux系统编程就是基于linux提供的系统调用函数接口来进行编程
系统调用严格意义来讲是系统函数而非真系统调用 ,比方说read函数,他其实是将真正的系统调用函数sys_read函数封装了一层来提供的 ,所以read函数只能说是系统函数
对于Linux的C++开发程序员来说,在Linux系统下开发以及懂得使用Linux的系统函数尤为重要
静态库和动态库 静态库 概念 :
静态库
是在可执行程序运行前就加入到执行码中,成为执行程序的一部分
在生成可执行程序后,可执行程序的占用空间包括了静态库的大小
静态库适用于对于空间要求较低,对时间要求较高的核心程序
制作静态库及使用 :
准备好要制作静态库的程序文件和声明文件.h
生成目标文件
:
1 2 gcc -c 程序文件.cpp -o 程序文件.o //可以生成多个目标文件,最后将这些全部做到一个静态库中
生成静态库
:
1 ar rcs lib库名.a 目标文件1. o 目标文件2. o ...
使用静态库
:
1 2 3 4 5 6 7 8 g++ 运行程序.cpp lib库名.a -o 运行程序 gcc 运行程序.cpp lib库名.a -o 运行程序
动态库 概念 :
动态库(共享库)
是在执行程序启动时加载到执行程序中,可以被多个执行能够程序共享使用
动态库是不会占用执行程序空间 ,也就是执行程序中的占用空间里不包含动态库,而可执行程序可调用动态库
动态库适用于对空间要求较高,对时间要求较低的核心程序
制作动态库及使用 :
准备好要制作动态库的程序文件和声明文件.h
生成与位置无关的代码将.c或.cpp生成.o文件
:
1 2 gcc -c 文件.cpp -o 文件.o -fPIC //-fPIC参数将会生成与位置无关代码
制作动态库
:
1 gcc -shared -o lib库名.so 目标文件.o 目标文件1.o ...
使用动态库
:
1 2 3 4 5 6 7 8 //编译可执行程序时,指定所使用的动态库 //-l:指定库名 //-L:指定库路径# c gcc 可执行程序.cpp -o 可执行程序.out -l 库名 -L 库路径# c++ gcc 可执行程序.cpp -o 可执行程序.out -l 库名 -L 库路径
运行可执行文件.out
1 2 3 ./test.out# 报错 # ./test.out: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
使用动态库后的.out程序运行报错解决 :
原因:
链接器 :工作于链接阶段,工作时需要-l和-L
动态链接器 :工作于程序运行阶段,工作时需要提供动态库所在目录位置(会去默认目录去找)
原因:因为使用动态库,所使用的是动态链接器,则工作时需要找到动态库,因为他在默认目录未找到,所以导致报错
解决:
设置临时环境变量 :
1 2 3 4 export LD_LIBRARY_PATH=库路径 ./test.out# 运行成功
**编辑终端配置文件(永久)**:
1 2 3 4 5 6 7 8 9 10 11 12 # 去到你对应的终端,bash就是.bashrc,zsh就是.zshrc vim .zshrc # 添加,库路径建议使用绝对路径 export LD_LIBRARY_PATH=库路径# 保存 # 生效配置文件 source .zshrc ./test.out# 运行成功
open/close函数 函数原型 :
int open(const char *pathname,int flags)
:用来打开一个文件和创建一个文件(如果没有该文件就创建)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <fcntl.h> int open (const char *pathname,int flags) ; pathname:打开文件的路径名; flags:操作标志参数,多个可以使用|包括; O_RDONLY:只读; O_WRONLY:只写; O_RDWR:读和写; O_APPEND:追加; O_CREAT:创建; O_EXCL:文件是否存在; O_TRUNC:截断; O_NONBLOCK:非阻塞; 成功:返回新的文件描述符; 失败:-1 ,errno;
int open(const char *pathname,int flags,mode_t mode)
:用来打开一个文件和创建一个文件(如果没有该文件就创建)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <fcntl.h> int open (const char *pathname,int flags,mode_t mode) pathname:打开文件的路径名; flags:操作标志参数,多个可以使用|包括; O_RDONLY:只读; O_WRONLY:只写; O_RDWR:读和写; O_APPEND:追加; O_CREAT:创建; O_EXCL:文件是否存在; O_TRUNC:截断; O_NONBLOCK:非阻塞; mode:为创建新文件设置权限参数,权限受到umask影响(默认文件操作权限); 成功:返回新的文件描述符; 失败:-1 ,errno;
int close(int fd)
:关闭打开的文件描述符
1 2 3 4 5 6 7 8 9 10 11 12 #include <unistd.h> int close (int fd) ; fd:要关闭文件的文件描述符; 成功:0 ; 失败:-1 ,errno;
open常见错误 :
打开文件不存在(文件不存在,并且不设置创建参数)
以写方式打开只读文件(打开文件没有对应权限)
以只写方式打开目录
read/write函数 函数原型 :
ssize_t read(int fd,void *buf,size_t count)
:原本是用于文件读取的操作,但是也可以用与socket的接收以及读取数据的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <unistd.h> ssize_t read (int fd,void *buf,size_t count) ; fd:要操作的文件描述符; buf:是一个指向读或写数据的缓冲区指针; count:缓冲区长度; 成功:返回读到的字节数,0 ,表示已经到达文件末尾; 失败:-1 ,errno;-1 : - 如果read返回-1 并且errno等于EAGIN或EWOULDBLOCK,说明不是read失败而是read以非阻塞读文件并且文件无数据 - 如果errno==EINTR,被异常终止,需要重启 - 如果errno==ECONNRESET,说明连接被重置 char buf[BUFSIZ];
ssize_t write(int fd,const void *buf,size_t count)
:用于进行文件写数据的操作 ,但是也可以用于socket通信的发送以及写数据的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <unistd.h> ssize_t write (int fd,const void *buf,size_t count) ; fd:要操作的文件描述符; buf:待写出数据的缓冲区; count:数据大小; 成功:返回写入的字节数; 失败:-1 ,errno;
实现cp命令 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <cstdio> #include <cstdlib> #include <unistd.h> #include <fcntl.h> int main (int argc, char *argv[]) { char buf[4096 ]; int n=0 ; int fd1=open (argv[1 ],O_RDONLY); if (fd1==-1 ){ perror ("open argv1 error" ); exit (1 ); } int fd2=open (argv[2 ],O_RDWR|O_CREAT|O_TRUNC,0664 ); if (fd2==-1 ){ perror ("open argv2 error" ); exit (1 ); } while ((n=read (fd1,buf,1024 ))!=0 ){ if (n<0 ){ perror ("read error" ); break ; } write (fd2,buf,n); } close (fd1); close (fd2); return 0 ; }
lseek函数 函数原型 :
off_t lseek(int fd, off_t offset, int whence)
:用于读写文件偏移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <unistd.h> off_t lseek (int fd, off_t offset, int whence) ; fd:文件描述符; offset:偏移量; whence:起始偏移位置; SEEK_SET:文件开头; SEEK_CUR:当前位置; SEEK_END:文件末尾; 成功:较起始位置向后偏移量; 失败:-1 ,errno;
注:
lseek同样允许超过文件结尾设置偏移量,文件会因此扩展
注意文件”读”和”写”使用同一偏移位置
应用场景 :
文件的”读”和”写”使用同一偏移位置
使用lseek获取文件大小(lseek(fd,0,SEEK_END);)
使用lseek扩展文件大小,要想使文件大小真正扩展,必须引起IO操作
错误处理函数 概念 :
Linux中也提供了一种用来查看错误原因的变量以及函数
errno是Linux中的一个全局变量,当你程序发生报错时,会进行设置errno来告诉你报错的原因,而errno是一个整数 ,这个整数会对应着一个错误原因
strerror函数是可以解读errno来返回这个errno所对应的原因
perror函数是可以设置报错提示,并且将errno的错误原因一起输出
函数原型 :
阻塞、非阻塞 概念 :
阻塞
:当进程调用一个阻塞的系统函数时,该进程将会置于睡眠状态 ,这时内核调度其他进程运行,直到该进程等待的时间发生(比如网络上接收到包,或者调用sleep指定的睡眠时间到了), 它才可能继续运行
非阻塞
:当调用非阻塞的系统函数时,如果不能立即得到结果,则不会阻塞当前线程或进程 ,但是调用者需要定时轮询查看处理状态
产生阻塞的场景:读设备文件(dev目录)、读网络文件等等
阻塞、非阻塞是文件的属性
,而文件属性是可以修改的
阻塞示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <unistd.h> #include <stdlib.h> #include <stdio.h> int main () { char buf[10 ]; int n; n=read (STDIN_FILENO,buf,10 ); if (n<0 ){ perror ("read STDIN_FILENO" ); exit (1 ); } write (STDOUT_FILENO,buf,n); return 0 ; }
阻塞示例2 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main () { char buf[10 ]; int fd,n; fd=open ("/dev/tty" ,O_RDONLY|O_NOBLOCK); if (fd<0 ){ perror ("open /dev/tty" ); exit (1 ); } tryagain: n=read (fd,buf,10 ); if (n<0 ){ if (errno!=EAGAIN){ perror ("read /dev/tty" ); exit (1 ); }else { write (STDOUT_FILENO,"try again\n" ,strlen ("try again\n" )); sleep (2 ); goto tryagain; } } write (STDOUT_FILENO,buf,n); close (fd); return 0 ; }
非阻塞示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define MSG_TRY "try again\n" #define MSG_TIMEOUT "time out\n" int main () { char buf[10 ]; int fd,n,i; fd=open ("/dev/tty" ,O_RDONLY|O_NONBLOCK); if (fd<0 ){ perror ("open /dev/tty" ); exit (1 ); } printf ("open /dev/tty ok... %d\n" ,fd); for (i=0 ;i<5 ;++i){ n=read (fd,buf,10 ); if (n>0 ) break ; if (errno!=EAGAIN){ perror ("read /dev/tty" ); exit (1 ); }else { write (STDOUT_FILENO,MSG_TRY,strlen (MSG_TRY)); sleep (2 ); } } if (i==5 ){ write (STDOUT_FILENO,MSG_TIMEOUT,strlen (MSG_TIMEOUT)); }else { write (STDOUT_FILENO,buf,n); } close (fd); return 0 ; }
fcntl函数 概念 :
fcntl函数
可以用来改变一个已经打开的文件的访问控制属性
函数原型 :
int fcntl(int fildes,int cmd,.../* arg */)
:可以用来改变一个已经打开的文件的访问控制属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <fcntl.h> int fcntl (int fildes, int cmd, ...) ; fildes:要操作的文件描述符; cmd:函数行为参数; F_GETFL:获取文件属性; F_SETFL:设置文件属性; ...:可变参数,也就是设置文件属性时可以加入文件属性作为第三个参数; F_GETFL:会返回文件属性; F_SETFL:-1 以外的值;
文件操作 stat/lstat函数 函数原型 :
int stat(const char *path,struct stat *buf)
:获取文件属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <sys/stat.h> int stat (const char *path,struct stat *buf) ; path:文件路径或文件名; buf:inode结构体指针,传出参数; 成功:0 ; 失败:-1 ,errno;
int lstat(const char *path,struct stat *buf)
:获取文件属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <sys/stat.h> int lstat (const char *path,struct stat *buf) ; path:文件路径或文件名; buf:inode结构体指针,传出参数; 成功:0 ; 失败:-1 ,errno;
inode结构体:
应用场景 :
buf.st_size
:获取文件大小
buf.st_mode
:获取文件类型
buf.st_mode
:获取文件权限
判断文件类型 :
link/unlink函数 函数原型 :
int link(const char *oldpath,const char *newpath)
:可以为已经存在的文件创建目录项(硬链接)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <unistd.h> int link (const char *oldpath, const char *newpath) ; oldpath:要创建硬链接文件路径; newpath:硬链接文件保存的地址; 成功:0 ; 失败:-1 ,errno;
int unlink(const char *pathname)
:删除一个文件的目录项(硬链接)
1 2 3 4 5 6 7 8 9 10 11 #include <unistd.h> int unlink (const char *pathname) ; pathname:要删除目录项的文件路径; 成功:0 ; 失败:-1 ,errno;
注:
Linux删除文件的机制,是不断将inode中的st_nlink-1,直至减到为0为止。无目录项对应的文件,将会被操作系统择机释放
unlink函数特征 :清除文件时,如果文件的硬链接数到0了 ,没有dentry对应,则该文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会择机将该文件释放掉
实现mv命令 :
1 2 3 4 5 6 7 8 #include <unistd.h> int main (int argc, char *argv[]) { link (argv[1 ], argv[2 ]); unlink (argv[1 ]); return 0 ; }
隐式回收 概念 :当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空 间会被释放 ,这一特性称之为隐式回收系统资源
symlink函数 函数原型 :
int symlink(const char *oldpath,const char *newpath)
:用于创建文件的软链接
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <unistd.h> int symlink (const char *target, const char *linkpath) ; target:要创建软连接的文件目标地址; linkpath:软连接文件保存地址; 成功:0 ; 失败:-1 ,errno;
readlink函数 函数原型 :
ssize_t readlink(const char *path,char *buf,size_t bufsiz)
:用于读取符号链接(软链接)文件内容,得到链接所指向的文件名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <unistd.h> ssize_t readlink (const char *restrict pathname, char *restrict buf,size_t bufsiz) ; pathname:要读取内容的软链接文件路径; buf:将读入的内容保存的缓冲区; bufsiz:缓冲区大小; 成功:返回实际读到的字节数; 失败:-1 ,errno;
rename函数 函数原型 :
int rename(const char *oldpath,const char *newpath)
:为文件重命名
1 2 3 4 5 6 7 8 9 #include <stdio.h> int rename (const char *oldpath, const char *newpath) ; oldpath:原来的文件名; newpath:新的文件名;
目录操作 getcwd函数 函数原型 :
char *getcwd(char *buf,size_t size)
:获取进程当前工作目录 (man卷3),相当于pwd命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <unistd.h> char *getcwd (char *buf, size_t size) ; buf:将获取到的信息存入的缓冲区; size:缓冲区大小; 成功:buf中保存当前进程工作目录位置; 失败:NULL ;
chdir函数 函数原型 :
int chdir(const char *path)
:改变当前进程的工作目录 ,其实就是cd命令
1 2 3 4 5 6 7 8 9 10 11 #include <unistd.h> int chdir (const char *path) ; path:要进入的工作目录; 成功:0 ; 失败:-1 ,errno;
opendir/closedir函数 函数原型 :
DIR *opendir(const char *name)
:根据传入的目录名打开一个目录 ,DIR*类似于FILE*
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <dirent.h> DIR *opendir (const char *name) ; name:目录名; 成功:返回指向该目录结构体指针; 失败:返回NULL ;
int closedir(DIR *dirp)
:关闭指定打开的目录
1 2 3 4 5 6 7 8 9 10 11 12 #include <dirent.h> int closedir (DIR *dirp) ; dirp:目录名; 成功:0 ; 失败:-1 ,errno;
readdir函数 函数原型 :
struct dirent *readdir(DIR *dirp)
:读取指定目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <dirent.h> struct dirent *readdir (DIR *dirp); dirp:目录名; 成功:返回目录项结构体指针; 失败:返回NULL ,errno;struct dirent { ino_t d_ino; off_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[256 ]; };
rewinddir函数 函数原型 :
void rewinddir(DIR *dirp)
:回卷目录读写位置至起始位置
1 2 3 4 5 6 #include <dirent.h> void rewinddir (DIR *dirp) ; dirp:目录名;
telldir/seekdir函数 函数原型 :
long telldir(DIR *dirp)
:获取目录读写位置
1 2 3 4 5 6 7 8 9 10 11 #include <dirent.h> long telldir (DIR *dirp) ; dirp:目录名; 成功:与dirp相关的目录当前读写位置; 失败:-1 ,errno;
void seekdir(DIR *dirp,long loc)
:修改/跳转目录指定的读写位置
1 2 3 4 5 6 7 8 #include <dirent.h> void seekdir (DIR *dirp, long loc) ; dirp:目录名; loc:一般telldir的返回值决定;
递归遍历目录 思路 :
判断命令行参数,获取用户要查询的目录名(argv[1]),还需要判断argc==1->./目录
判断用户指定的是否是目录,使用stat S_ISDIR()
读目录,opendir(),readdir(),closedir()
递归读取目录文件,普通文件直接打印,目录就拼接出绝对路径,递归调用opendir()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <cstddef> #include <unistd.h> #include <sys/stat.h> #include <dirent.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #define PATH_LEN 256 void fetchdir (const char *dir,void (*fcn)(char *)) { char name[PATH_LEN]; struct dirent *sdp; DIR *dp; if ((dp=opendir (dir))==NULL ){ fprintf (stderr,"fetchdir:can't open %s\n" ,dir); return ; } while ((sdp=readdir (dp))!=NULL ) { if (strcmp (sdp->d_name,"." )==0 ||strcmp (sdp->d_name,".." )==0 ) continue ; if (strlen (dir)+strlen (sdp->d_name)+2 >sizeof (name)){ fprintf (stderr,"fetchdir:name %s %s too long\n" ,dir,sdp->d_name); }else { sprintf (name,"%s/%s" ,dir,sdp->d_name); (*fcn)(name); } } closedir (dp); }void isfile (char *name) { struct stat sbuf; if (stat (name,&sbuf)==-1 ){ fprintf (stderr, "isfile:can't access %s\n" ,name); exit (1 ); } if ((sbuf.st_mode&S_IFMT)==S_IFDIR) fetchdir (name,isfile); printf ("%8ld %s\n" ,sbuf.st_size,name); }int main (int argc, char *argv[]) { if (argc==1 ) isfile ("." ); else { while (--argc>0 ) { isfile (*++argv); } } return 0 ; }
重定向 函数原型 :
int dup(int oldfd)
:将已有的文件描述符文件里的内容复制到新建的文件描述符文件中
1 2 3 4 5 6 7 8 9 10 11 #include <unistd.h> int dup (int oldfd) ; oldfd:已有文件描述符; 成功:新文件描述符; 失败:-1 ,errno;
int dup2(int oldfd,int newfd)
:将一个文件描述符newfd重定向到指定文件描述符oldfd的文件 ,重定向完,对newfd所指向文件操作也就是对oldfd所指向文件操作
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <unistd.h> int dup2 (int oldfd, int newfd) ; oldfd:已有文件描述符; newfd:要重定向的文件描述符; 成功:返回newfd; 失败:-1 ,errno;
注:对于dup2,如果oldfd不是有效的文件描述符,会报错,但是newfd文件描述符不会关闭
fcntl实现dup :
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <pthread.h> int main (int argc, char *argv[]) { int fd1=open (argv[1 ],O_RDWR); printf ("fd1 = %d\n" ,fd1); int newfd=fcntl (fd1,F_DUPFD,0 ); printf ("newfd = %d\n" ,newfd); return 0 ; }
进程控制 fork函数 函数原型 :
pid_t fork(void)
:创建一个子进程
1 2 3 4 5 6 7 8 9 #include <unistd.h> pid_t fork (void ) ; 成功:父进程接收到返回值新子进程ID (PID),创建出来的子进程接收到返回值0 ; 失败:父进程接收到-1 ,errno,子进程没被创建成功;
创建子进程 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <cstdio> #include <cstdlib> #include <unistd.h> #include <iostream> int main (int argc, char *argv[]) { std::cout<<"before fork -1" <<std::endl; pid_t pid=fork(); if (pid==-1 ){ perror ("fork error" ); exit (1 ); }else if (pid==0 ) { std::cout<<"child is created" <<std::endl; }else if (pid>0 ) { std::cout<<"parent process: my child is " <<pid<<std::endl; } std::cout<<"end of file" <<std::endl; return 0 ; }
getpid/getppid函数 函数原型 :
pid_t getpid(void)
:获取当前进程ID(PID)
1 2 3 4 5 6 7 8 #include <unistd.h> pid_t getpid (void ) ; 成功:当前进程PID; 失败:无返回值,因为没有定义;
pid_t getppid(void)
:获取当前子进程的父进程ID(PID)
1 2 3 4 5 6 7 8 #include <unistd.h> pid_t getppid (void ) ; 成功:返回当前子进程的父进程PID; 失败:无返回值,因为没有定义;
循环创建子进程 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <cstdio> #include <cstdlib> #include <unistd.h> #include <iostream> int main (int argc, char *argv[]) { int i; for (i=0 ;i<5 ;++i){ pid_t pid=fork(); if (pid==-1 ){ perror ("fork error" ); exit (1 ); }else if (pid==0 ) { break ; } } if (5 ==i) { sleep (5 ); std::cout<<"I'm parent \n" ; } else { sleep (i); std::cout<<"I'm child" <<i+1 <<std::endl; } return 0 ; }
getuid/getgid函数 函数原型 :
uid_t getuid(void)
:获取当前进程实际用户ID
1 2 3 4 5 6 7 8 9 #include <unistd.h> uid_t getuid (void ) ; 成功:当前进程实际用户ID; 失败:无返回值,因为没有定义;
uid_t geteuid(void)
:获取当前进程有效用户ID
1 2 3 4 5 6 7 8 #include <unistd.h> uid_t geteuid (void ) ; 成功:当前进程有效用户ID; 失败:无返回值,因为没有定义;
gid_t getgid(void)
:获取当前进程使用用户组ID
1 2 3 4 5 6 7 8 9 #include <unistd.h> gid_t getgid (void ) ; 成功:当前进程使用用户组ID; 失败:无返回值,因为没有定义;
gid_t getegid(void)
:获取当前进程有效用户组ID
1 2 3 4 5 6 7 8 #include <unistd.h> gid_t getegid (void ) ; 成功:当前进程有效用户组ID; 失败:无返回值,因为没有定义;
进程共享 概念 :
父子进程共享遵循读时共享写时复制 ,例如:有一个全局变量cnt=100,当我父进程或者子进程读cnt时,将会共享这个cnt的值也就是100,但是当父进程或子进程要更改其值也就是写时,将会给当前进程copy一个cnt过来再改,因此当父进程或子进程修改cnt(只修改自己进程中的cnt),子进程或父进程读cnt得到的值还是cnt
父子进程不会共享全局变量
父子进程相同处:刚fork后,data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式
父子进程不同处:进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集
父子进程共享
:
文件描述符
mmap映射区
exec函数族 概念 :
exec函数族
可以让子进程或父进程执行指定的某个可执行程序
当进程调用exec时,则进程中的代码段将会换成你要exec的可执行程序的代码段,以此来执行指定程序 ,但是进程ID没变
exec不能返回的!
execl函数 int execl(const char *path,const char *arg,...)
:加载一个进程(可执行程序),用于加载普通和系统的可执行程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <unistd.h> int execl (const char *path,const char *arg,...) ; path:可执行文件路径; arg:argv[0 ],所以跟path一样; ...:用来传入执行可执行文件的参数,可变参数; 失败:-1 ,errno;
execlp函数 int execlp(const char *file,const char *arg,...)
:借助PATH环境变量加载一个进程 ,用于加载系统可执行程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <unistd.h> int execlp (const char *file,const char *arg,...) ; file:可执行文件的文件名; arg:argv[0 ],所以还是传入可执行文件名; ...:用来传入执行可执行文件的参数,可变参数; 失败:-1 ,errno;
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <cstddef> #include <cstdio> #include <cstdlib> #include <sys/types.h> #include <unistd.h> #include <iostream> int main (int argc, char *argv[]) { pid_t pid=fork(); if (pid==-1 ){ perror ("fork error" ); exit (1 ); }else if (pid==0 ) { execlp ("ls" , "ls" ,"-l" ,"-h" ,NULL ); perror ("exec error" ); exit (1 ); }else if (pid>0 ) { sleep (1 ); std::cout<<"I'm parent:" <<getpid ()<<std::endl; } return 0 ; }
execvp函数 int execvp(const char *file,const char *argv[])
:使用自定义环境变量env加载一个进程
1 2 3 4 5 6 7 8 9 10 11 12 #include <unistd.h> int execvp (const char *file,const char *argv[]) ; file:可执行文件的文件名; argv[]:可变参数,类似于main函数中的argv[]; 失败:-1 ,errno;
exec函数族特点
回收子进程 引言 孤儿进程 :父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程
僵尸进程 :进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程
注意:
僵尸进程是不能使用kill命令清除掉的,因为kill命令只是用来终止进程的,而僵尸进程已经终止
需要使用kill命令将父进程kill掉,然后由init接收僵尸进程并回收
概念 :
一个进程在终止时 会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在按其中保存了一些信息,如果是正常退出则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个
这个进程的父进程可以调用wait或waitpid获取这些信息,彻底清除掉这个进程
wait、waitpid都只能够一次回收一个进程
wait函数 函数原型 :
pid_t wait(int *status)
:父进程调用wait函数可以回收子进程终止信息,有三个功能:
阻塞等待子进程退出(死亡)
回收子进程残留资源
获取子进程结束状态(退出原因)
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <sys/wait.h> pid_t wait (int *status) ; status:传出参数,子进程退出状态; 成功:返回被回收子进程的进程ID; 失败:-1 ;
子进程退出信息 :
要想进一步获取退出状态和原因需要借助一些宏函数
WIFEXITED(status)
:判断子进程是否正常终止,为真则正常终止WEXITSTATUS(status)
:只有当子进程是正常终止才调用查看子进程返回值
WIFSIGNALED(status)
:判断子进程是否是被信号终止的,为真为是
WTERMSIG(status)
:只有当子进程是被信号终止时,用来查看子进程是被哪个信号终止
WIFSTOPPED(status)
:判断子进程是否被信号阻塞,为真则是
WSTOPSIG(status)
:只有当子进程是被信号阻塞时,用来查看子进程被哪个信号阻塞的
WIFCONTINUED(status)
:查看子进程是否被恢复
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <cstdio> #include <cstdlib> #include <ostream> #include <sys/types.h> #include <unistd.h> #include <iostream> #include <sys/wait.h> int main (int argc, char *argv[]) { pid_t pid,wpid; pid=fork(); int status; if (pid==0 ){ std::cout<<"---child,my id=" <<getpid ()<<",going to sleep 10s\n" ; sleep (10 ); std::cout<<"--------child die--------" <<std::endl; return 73 ; }else if (pid>0 ) { wpid=wait (&status); if (wpid==-1 ){ perror ("wait error" ); exit (1 ); } if (WIFEXITED (status)){ std::cout<<"child exit with " <<WEXITSTATUS (status)<<std::endl; } if (WIFSIGNALED (status)){ std::cout<<"child kill with signal " <<WTERMSIG (status)<<std::endl; } std::cout<<"I am parent,pid=" <<getgid ()<<",myson=" <<pid<<std::endl; std::cout<<"被回收的进程ID=" <<wpid<<std::endl; sleep (1 ); }else { perror ("fork error" ); return 1 ; } return 0 ; }
waitpid函数 函数原型 :
pid_t waitpid(pid_t pid,int *status,int options)
:作用同wait一样 (父进程调用wait函数可以回收子进程终止信息),但可指定pid进程清理,可以不阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <sys/wait.h> pid_t waitpid (pid_t pid,int *status,int options) ; pid:要被回收的子进程ID; > 0 :回收指定ID的子进程;-1 :回收任意子进程(相当于wait);0 :回收和当前调用waitpid一个组的所有子进程; < -1 :回收指定进程组内的任意子进程; status:传出参数,子进程退出状态; options:函数行为标志位参数;0 :阻塞; WNOHANG:不阻塞; 返回值>0 :表示成功回收的子进程pid; 返回值=0 :函数调用时,参数三指定WNOHANG,并且没有子进程结束; 失败:-1 ,errno;
waitpid回收多个进程 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <cstdio> #include <cstdlib> #include <ostream> #include <sys/types.h> #include <unistd.h> #include <iostream> #include <sys/wait.h> int main (int argc, char *argv[]) { int i; pid_t pid,wpid; pid=fork(); for (i=0 ;i<5 ;++i){ pid=fork(); if (pid==0 ) break ; } if (5 ==i){ while ((wpid=waitpid (-1 ,NULL ,WNOHANG))!=-1 ){ if (wpid>0 ){ std::cout<<"wait child " <<wpid<<std::endl; }else if (wpid==0 ) { sleep (1 ); continue ; } } }else { sleep (i); std::cout<<"I'm " <<i+1 <<"th child,pid=" <<getpid ()<<std::endl; } return 0 ; }
进程间通信IPC 概念 :
进程间通信(IPC)
:也就是进程与进程之间进行通信、共享和数据传递 ,叫作进程间通信
进程间通信的实质
就是在两个进程间放置一个缓冲区,让两个进程在缓冲区里进行读和写来进行通信。
进程间通信方式 :
管道(使用最简单)
信号(开销最小)
mmap共享映射区(无血缘关系)
本地套接字socket(最稳定)
管道 概念 概念 :
管道
是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。
管道的本质是一个伪文件(实为内核缓冲区)
由两个文件描述符引用的,一个表示读端,一个表写端
管道是内核使用环形队列机制,借助内核缓冲区实现的
局限性 :
数据不能自己写,自己读
管道中数据不可反复读取 ,一旦读走,管道中的相应内容不再存在
采用半双工通信方式 ,数据只能在单方向上流动
特点 :
**它是半双工的(即数据只能在一个方向上流动)**,具有固定的读端和写端
它只能用于具有亲缘关系的进程之间的通信
(父子进程或兄弟进程之间)
它可以看成是一种特殊的文件(伪文件),对于它的 读写也可以使用普通的read、write等函数 。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中
pipe函数 函数原型 :
int pipe(int pipefd[2])
:创建并打开管道
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <unistd.h> int pipe (int pipefd[2 ]) ; pipefd[0 ]:读端的文件描述符; pipefd[1 ]:写端的文件描述符; 成功:0 ; 失败:-1 ,errno;
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <cstdio> #include <cstdlib> #include <cstring> #include <unistd.h> int main (int argc, char *argv[]) { int ret,fd[2 ]; pid_t pid; char *str="hello pipe\n" ; char buf[1024 ]; ret=pipe (fd); if (ret==-1 ){ perror ("pipe error" ); exit (1 ); } pid=fork(); if (pid>0 ){ close (fd[0 ]); write (fd[1 ],str,strlen (str)); sleep (1 ); close (fd[1 ]); }else if (pid==0 ) { close (fd[1 ]); ret=read (fd[0 ],buf,sizeof (buf)); write (STDOUT_FILENO, buf, ret); close (fd[0 ]); } return 0 ; }
管道的读写行为
读管道
:
管道中有数据,read返回实际读到的字节数.
管道中无数据:
管道写端被全部关闭,read返回0
写端没有全部关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
写管道
:
管道读端全部被关闭,进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
管道读端没有全部关闭:
管道已满,write阻塞
管道未满,write将数据写入,并返回实际写入的字节数
兄弟进程通信 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <cstdio> #include <cstdlib> #include <cstring> #include <unistd.h> #include <sys/wait.h> int main (int argc, char *argv[]) { int ret,fd[2 ],i; pid_t pid; ret=pipe (fd); if (ret==-1 ){ perror ("pipe error" ); exit (1 ); } for (i=0 ;i<2 ;++i){ pid=fork(); if (pid==-1 ){ perror ("fork error" ); exit (1 ); } if (pid==0 ) break ; } if (i==2 ){ close (fd[0 ]); close (fd[1 ]); wait (NULL ); wait (NULL ); }else if (i==0 ) { close (fd[0 ]); dup2 (fd[1 ],STDOUT_FILENO); execlp ("ls" ,"ls" ,NULL ); perror ("execlp ls error" ); exit (1 ); }else if (i==1 ) { close (fd[1 ]); dup2 (fd[0 ],STDIN_FILENO); execlp ("wc" ,"wc" ,"-l" ,NULL ); perror ("execlp ls error" ); exit (1 ); } return 0 ; }
FIFO 概念 概念 :
FIFO(命名管道)
:是Linux基础文件类型中的一种 ,各进程可以打开这个文件进行read/write ,实际上是在读写内核通道,这样就是实现了进程间通信
FIFO文件在磁盘上没有数据块,仅仅用来标识内核中的一条通道 。
在Linux终端中可以使用mkfifo命令,进行创建FIFO文件
特点 :
FIFO可以在无血缘关系的进程之间交换数据
,与pipe不同。
它以一种特殊设备文件形式存在于文件系统中,所以我们应该用对文件的操作对待它
mkfifo函数 函数原型 :
int mkfifo(const char *pathname,mode_t mode)
:用来创建FIFO文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <sys/stat.h> int mkfifo (const char *pathname, mode_t mode) ; pathname:fifo文件路径名; mode:为创建新文件设置权限参数,权限受到umask影响(默认文件操作权限); 成功:0 ; 失败:-1 ,errno;
FIFO进程通信 先创建一个myfifo的fifo文件
写端:test.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main (int argc, char *argv[]) { int fd,i; char buf[BUFSIZ]; if (argc<2 ){ std::cout<<"Enter like this: ./a.out fifoname\n" ; return -1 ; } fd=open (argv[1 ],O_WRONLY); if (fd<0 ){ perror ("open error" ); exit (1 ); } i=0 ; while (1 ) { sprintf (buf,"hello itcast %d\n" ,i++); write (fd,buf,strlen (buf)); sleep (1 ); } close (fd); return 0 ; }
读端:testr.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <cstdio> #include <cstdlib> #include <iostream> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main (int argc, char *argv[]) { int fd,len; char buf[BUFSIZ]; if (argc<2 ){ std::cout<<"./a.out fifoname\n" ; return -1 ; } fd=open (argv[1 ],O_RDONLY); if (fd<0 ){ perror ("open error" ); exit (1 ); } while (1 ) { len=read (fd,buf,sizeof (buf)); write (STDOUT_FILENO,buf,len); sleep (3 ); } close (fd); return 0 ; }
共享存储映射 存储映射I/O 概念 :
存储映射I/O
使一个磁盘文件与存储空间中的一个缓冲区相映射 ,当从缓冲区取数据相当于读文件中相应字节,将数据存入缓冲区,则相应的字节就自动写入文件。
存储映射可以 在不适用read和write函数的情况下,使用地址(指针)完成I/O操作
使用注意事项 :
用于创建映射区的文件大小为0,实际指定非0大小创建映射区,出”总线错误(SIGBUS)”
用于创建映射区的文件大小为0,实际指定0大小创建映射区,出”无效参数”
用于创建映射区文件的读写属性为只读,映射区属性为读写,出”无效参数”
创建映射区需要读权限,当访问权限指定为MAP_SHARED时,mmap的读写权限应该<=文件的open权限
文件描述符fd,在mmap创建映射区完成即可关闭,后续访问文件,用地址访问
offset必须是4096的整数倍(MMU映射的最小单位为4k)
对申请的映射区内存,不能越界访问。
munmap用于释放的地址,必须是mmap申请返回的地址
映射区访问权限为MAP_PRIVATE,对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上
映射区访问权限为MAP_PRIVATE,只需要open文件时,有读权限用于创建映射区即可
mmap函数 函数原型 :
void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset)
:创建共享内侧映射区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <sys/mman.h> void *mmap (void *addr, size_t len, int prot, int flags,int fd, off_t off) ; addr:建立映射区的首地址,由Linux内核指定,使用时,直接传入NULL ; length:欲创建映射区的大小(小于等于文件实际大小); prot:映射区权限; PROT_READ:读; PROT_WRITE:写; PROT_READ|PROT_WRITE:读写; PROT_NONE:没有访问权限; ...:更多的看man手册第二卷的mmap函数; flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区); MAP_SHARED:会将映射区所做的操作反映到物理设备(磁盘)上; MAP_PRIVATE:映射区所做的修改不会反映到物理设备; ...:更多的看man手册第二卷的mmap函数; fd:用于创建共享内存映射区的那个文件的文件描述符; offset:默认0 ,表示映射文件全部。偏移位置,取值必须是4096 的整数倍; 成功:共享内存映射区的首地址; 失败:返回MAP_FAILED ((void *)-1 ),errno;
mmap函数保险调用方式 :
open(pathname,O_RDWR,0644);
mmap(NULL,有效文件大小,PROT_READ|PROT_WRITE,fd,0);
munmap函数 函数原型 :
int munmap(void *addr,size_t length)
:释放共享内存映射区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <sys/mman.h> int munmap (void *addr,size_t length) ; addr:要释放的映射区的首地址,也是mmap函数的返回值; length:映射区大小; 成功:0 ; 失败:-1 ,errno;
使用示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <cstdio> #include <cstdlib> #include <iostream> #include <sys/mman.h> #include <unistd.h> #include <fcntl.h> #include <string.h> int main (int argc, char *argv[]) { char *p=nullptr ; int fd; fd=open ("testmap" ,O_RDWR|O_CREAT|O_TRUNC,0644 ); if (fd==-1 ){ perror ("open error" ); exit (1 ); } ftruncate (fd,20 ); int len=lseek (fd,0 ,SEEK_END); p=(char *)mmap (NULL ,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 ); if (p==MAP_FAILED){ perror ("mmap error" ); exit (1 ); } strcpy (p,"hello mmap" ); std::cout<<"-------" <<p<<std::endl; int ret=munmap (p,len); if (ret==-1 ){ perror ("munmap error" ); exit (1 ); } close (fd); return 0 ; }
mmap父子进程通信 概念 :
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <fcntl.h> #include <iostream> #include <sys/mman.h> #include <sys/wait.h> #include <cstdio> #include <cstdlib> #include <unistd.h> int var=100 ;int main (int argc, char *argv[]) { int *p; pid_t pid; int fd; fd=open ("testmap" ,O_RDWR|O_CREAT|O_TRUNC,0644 ); if (fd<0 ){ perror ("open error" ); exit (1 ); } ftruncate (fd,4 ); p=(int *)mmap (NULL ,4 ,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 ); if (p==MAP_FAILED){ perror ("mmap error" ); exit (1 ); } close (fd); pid=fork(); if (pid==0 ){ *p=2000 ; var=1000 ; std::cout<<"child,*p=" <<*p<<",var=" <<var<<std::endl; }else if (pid>0 ){ sleep (1 ); std::cout<<"parent,*p=" <<*p<<",var=" <<var<<std::endl; wait (NULL ); int ret=munmap (p, 4 ); if (ret==-1 ){ perror ("munmap error" ); exit (1 ); } }else { perror ("fork error" ); exit (1 ); } return 0 ; }
mmap无血缘关系进程通信 示例:
写端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <fcntl.h> #include <string.h> #include <sys/mman.h> #include <sys/wait.h> #include <cstdio> #include <cstdlib> #include <unistd.h> struct student { int id; char name[256 ]; int age; };int main (int argc, char *argv[]) { int fd; student stu={10 ,"小明" ,18 },*p; fd=open ("testmap" ,O_RDWR|O_CREAT|O_TRUNC,0644 ); if (fd==-1 ){ perror ("open error" ); exit (1 ); } ftruncate (fd,sizeof (stu)); p=(struct student*)mmap (NULL ,sizeof (stu),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 ); if (p==MAP_FAILED){ perror ("mmap error" ); exit (1 ); } close (fd); while (1 ) { memcpy (p,&stu,sizeof (stu)); stu.id++; sleep (1 ); } int ret=munmap (p,sizeof (stu)); if (ret==-1 ){ perror ("munmap error" ); exit (1 ); } return 0 ; }
读端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <fcntl.h> #include <string.h> #include <sys/mman.h> #include <sys/wait.h> #include <cstdio> #include <cstdlib> #include <unistd.h> struct student { int id; char name[256 ]; int age; };int main (int argc, char *argv[]) { int fd; student stu,*p; fd=open ("testmap" ,O_RDONLY); if (fd==-1 ){ perror ("open error" ); exit (1 ); } p=(struct student*)mmap (NULL ,sizeof (stu),PROT_READ,MAP_SHARED,fd,0 ); if (p==MAP_FAILED){ perror ("mmap error" ); exit (1 ); } close (fd); while (1 ) { printf ("id=%d,name=%s,age=%d\n" ,p->id,p->name,p->age); sleep (1 ); } int ret=munmap (p,sizeof (stu)); if (ret==-1 ){ perror ("munmap error" ); exit (1 ); } return 0 ; }
匿名映射 概念 :
匿名映射
:可以不需要创建文件来创建共享内存映射区 ,在mmap函数的flags标志位参数添加上MAP_ANON或MAP_ANONYMOUS来实现,-1代替fd
之前使用mmap映射时,我们都是使用创建一个文件来创建共享内存映射区,但是我们也可以使用匿名映射,来避免需要创建文件来创建共享内存映射区
只能用于血缘关系进程间通信
也可以使用open(“/dev/zero”)来当作映射区,因为/dev/zero可以提供很大的空间,你想要多少他就给多少,这样就不需要扩展文件,直接创建映射区(虽然不是匿名映射)
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <fcntl.h> #include <string.h> #include <sys/mman.h> #include <sys/wait.h> #include <cstdio> #include <cstdlib> #include <unistd.h> int var=100 ;int main (int argc, char *argv[]) { int *p; p=(int *)mmap (NULL ,40 ,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1 ,0 ); if (p==MAP_FAILED){ perror ("mmap error" ); exit (1 ); } pid_t pid=fork(); if (pid==0 ){ *p=7000 ; var=1000 ; printf ("child,*p=%d,var=%d\n" ,*p,var); }else { sleep (1 ); printf ("parent,*p=%d,var=%d\n" ,*p,var); wait (NULL ); } int ret=munmap (p,4 ); if (ret==-1 ){ perror ("munmap error" ); exit (1 ); } return 0 ; }
信号 概念 概念 :
信号的机制 :
A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行
由于信号是通过软件方法实现的,其实现手段导致信号有很强的延时性。但相对于用户来说,这个延迟时间非常短,不易察觉
每个进程收到的所有信号,都是由内核负责发送的,内核处理 。
与信号相关的事件和状态 :
信号四要素 :
信号编号
信号名称
信号对应事件
信号默认处理动作
注:信号使用之前,应先确定4要素,而后使用
默认处理动作 :
Term :终止进程
Ign :忽略信号(默认及时对该种信号忽略操作)
Core :停止进程,生成Core文件。(查验进程死亡原因,用于gdb调试)
Stop :停止(暂停)进程
Cont :继续运行进程
常规信号一览表 :
SIGHUP :本信号在用户终端结束时发出,通常是在终端的控制进程结束时,通知同一会话期内的各个作业,这时他们与控制终端不在关联。比如,登录Linux时,系统会自动分配给登录用户一个控制终端,在这个终端运行的所有程序,包括前台和后台进程组,一般都属于同一个会话。当用户退出时,所有进程组都将收到该信号,这个信号的默认操作是终止进程。此外对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
SIGINT :程序终止信号。当用户按下CRTL+C时通知前台进程组终止进程。
SIGQUIT :Ctrl+\控制,进程收到该信号退出时会产生core文件,类似于程序错误信号。
SIGILL:执行了非法指令。通常是因为可执行文件本身出现错误,或者数据段、堆栈溢出时也有可能产生这个信号。
SIGTRAP:由断点指令或其他陷进指令产生,由调试器使用。
SIGABRT:调用abort函数产生,将会使程序非正常结束。
SIGBUS :非法地址。包括内存地址对齐出错。比如访问一个4个字长的整数,但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法地址的非法访问触发。
SIGFPE :发生致命的算术运算错误。
SIGKILL
:用来立即结束程序的运行。
SIGUSR1 :留给用户使用,用户可自定义。
SIGSEGV :访问未分配给用户的内存区。或操作没有权限的区域。
SIGUSR2 :留给用户使用,用户可自定义。
SIGPIPE :管道破裂信号。当对一个读进程已经运行结束的管道执行写操作时产生。
SIGALRM :时钟定时信号。由alarm函数设定的时间终止时产生。
SIGTERM :程序结束信号。shell使用kill产生该信号,当结束不了该进程,尝试使用SIGKILL信号。
SIGSTKFLT:堆栈错误。
SIGCHLD :子进程结束,父进程会收到。如果子进程结束时父进程不等待或不处理该信号,子进程会变成僵尸进程。
SIGCONT:让一个停止的进程继续执行。
SIGSTOP
:停止进程执行。暂停执行。
SIGTSTP:停止运行,可以被忽略。Ctrl+z。
SIGTTIN:当后台进程需要从终端接收数据时,所有进程会收到该信号,暂停执行。
SIGTTOU:与SIGTTIN类似,但在写终端时产生。
SIGURG:套接字上出现紧急情况时产生。
SIGXCPU:超过CPU时间资源限制时产生的信号。
SIGXFSZ:当进程企图扩大文件以至于超过文件大小资源限制时产生。
SIGVTALRM:虚拟使用信号。计算的是进程占用CPU调用的时间。
SIGPROF:包括进程使用CPU的时间以及系统调用的时间。
SIGWINCH:窗口大小改变时。
SIGIO:文件描述符准备就绪,表示可以进行输入输出操作。
SIGPWR:电源失效信号。
SIGSYS:非法的系统调用。
注:9号和19号信号不允许忽略和捕捉,只能执行默认动作,甚至不能将其设置为阻塞
信号产生 kill函数/命令 kill命令产生信号 :
1 2 kill -SIGKILL pid; //pid为要kill的进程ID
kill函数 :
int kill(pid_t pid,int sig)
:发送信号给指定进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <signal.h> int kill (pid_t pid, int sig) ; pid:进程ID; pid> 0 :发送信号给指定的进程; pid=0 :发送信号给与调用kill函数进程属于同一进程的所有进程; pid< -1 :取|pid|(绝对值)发给对应进程组; pid=-1 :发送给进程有权限发送的系统中所有进程; sig:信号; 成功:0 ; 失败:-1 ,errno;
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <cstdio> #include <signal.h> #include <sys/types.h> #include <unistd.h> int main (int argc, char *argv[]) { pid_t pid=fork(); if (pid>0 ){ printf ("parent,pid=%d\n" ,getpid ()); while (1 ); }else if (pid==0 ) { printf ("child pid=%d,ppid=%d\n" ,getpid (),getppid ()); sleep (2 ); kill (getppid (), SIGKILL); } return 0 ; }
其他几个发信号函数 :
int raise(int sig)
;
void abort(void)
;
alarm函数 概念 :
alarm函数可以设置定时器,在指定seconds后,内核会给当前进程发送SIGALRM信号,进程收到该信号,默认动作终止
每个进程都有且只有唯一个定时器
函数原型 :
unsigned int alarm(unsigned int seconds)
:设置定时器,在指定seconds后,内核会给当前进程发送SIGALRM信号,进程收到该信号,默认动作终止 ,使用自然计时法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <unistd.h> unsigned int alarm (unsigned int seconds) ; seconds:闹钟时长(秒); 成功:返回0 或上次定时剩余的秒数; 返回0 就是没有用alarm定时; 无失败;
time命令 :查看程序执行时间,实际时间=用户时间+内核时间+等待时间
setitimer函数 函数原型 :
int setitimer(int which,const struct itimerval *new_value,struct itimerval *old_value)
:设置定时器,精度可达微秒,可以实现周期定时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <sys/time.h> int setitimer (int which,const struct itimerval *new_value,struct itimerval *old_value) ; which:指定定时形式; ITIMER_REAL:自然时间计时法(计算自然时间,也就是在这之间程序终止,时间一到依然发送信号); ->发送SIGALRM信号ITIMER_VIRTUAL (用户空间):虚拟时间计时法(只计算进程占用cpu的时间); ->发送SIGTVALRM信号ITIMER_PROF (用户+内核):运行时间计时法(计算占用cpu及执行系统调用的时间); ->发送SIGPROF信号 new_value:定时秒数; old_value:传出参数,上次定时剩余时间;struct itimerval { struct timeval it_interval; struct timeval it_value; }; it_interval:用来设定两次定时任务之间间隔的时间; it_value:定时的时长; 两个参数都设置为0 ,则清0 操作也就是取消闹钟;struct timeval { time_t tv_sec; suseconds_t tv_usec; }; 成功:0 ; 失败:-1 ,errno;
信号集操作函数 概念 :
在Linux中阻塞信号集和未决信号集都是存在于PCB中,并且Linux不允许我们直接在上面操作,但是我们又想对阻塞信号集或未决信号集操作就得需要以下函数
我们可以通过自定义的信号集来跟阻塞信号集进行位运算来影响阻塞信号集的各个信号位的值,阻塞信号集受到影响也将影响未决信号集
信号集设定函数
int sigemptyset(sigset_t *set)
:将某个信号集清0
int sigfillset(sigset_t *set)
:将某个信号集全部置为1
int sigaddset(sigset_t *set,int signum)
:把某一个信号(signum)加入到某个自定义的信号集(set)
int sigdelset(sigset_t *set,int signum)
:把某一个信号(signum)从某个自定义信号集(set)中移出
int sigismember(const sigset_t *set,int signum)
:查看某一个信号(signum)是否在某个自定义信号集(set)中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <signal.h> typedef unsigned long sigset_t ;int sigemptyset (sigset_t *set) ;int sigfillset (sigset_t *set) ;int sigaddset (sigset_t *set, int signum) ;int sigdelset (sigset_t *set, int signum) ;int sigismember (const sigset_t *set, int signum) ; set:自定义信号集; signum:信号; 成功:0 ; sigismember返回值:0 :set没有signum;1 :set有signum;-1 ,errno:失败; 失败:-1 ,errno;
sigprocmask函数 函数原型 :
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset)
:用来屏蔽信号和解除屏蔽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <signal.h> int sigprocmask (int how,const sigset_t *set,sigset_t *oldset) ; how:函数行为设置位,设置函数执行所需要做的行为; SIG_BLOCK:当how设置为此值,set表示需要屏蔽信号.相当于mask=mask|set; SIG_UNBLOCK:当how设置为此值,set表示需要解除屏蔽的信号.相当于mask=mask&~set; SIG_SETMASK:当how设置为此值,set表示用于替代原始屏蔽集的新屏蔽集.相当于mask=set,若调用sigprocmask解除了对当前某个若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达; set:传入参数,自定义信号集,是一个位图,set中哪位置为1 ,就表示当前进程屏蔽哪个信号; oldset:传出参数,保存旧的信号屏蔽集,不需要可以传NULL ; 成功:0 ; 失败:-1 ,errno;
注:屏蔽信号,只是将信号处理延后执行(延至解除屏蔽),而忽略表示将信号丢弃处理
sigpending函数 函数原型 :
int sigpending(sigset_t *set)
:读取当前进程的未决信号集
1 2 3 4 5 6 7 8 9 10 11 12 #include <signal.h> int sigpending (sigset_t *set) ; set:传出参数,当前进程的未决信号集; 成功:0 ; 失败:-1 ,errno;
信号捕捉 概念 :
信号捕捉使是提供给开发者使用信号捕捉的函数在程序中捕捉到递达到该进程下的信号,然后自定义其的处理方式或者其他操作
signal函数 函数原型 :
sighandler_t signal(int signum,sighandler_t handler)
:注册一个信号捕捉函数 ,设定指定信号需要被捕捉,注册完后,由内核捕捉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <signal.h> typedef void (*sighandler_t ) (int ) ;sighandler_t signal (int signum, sighandler_t handler) ; signum:信号; handler:捕捉后行为参数,是一个函数指针也就是sighandler_t 定义的函数; 成功:返回指向前一个此信号的处理(回调)函数的指针; 失败:返回SIG_ERR;
注:该函数由ANSI定义,在不同版本的Linux中可能有不同的行为,因此我们推荐使用sigaction函数
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <csignal> #include <cstdio> void sig_cath (int signo) { printf ("catch you! %d\n" ,signo); return ; }int main (int argc, char *argv[]) { signal (SIGINT,sig_cath); while (1 ) { } return 0 ; } ./test ^Ccatch you! 2 ^Ccatch you! 2 ^Ccatch you! 2 ^Ccatch you! 2 ^Ccatch you! 2 ^Ccatch you! 2 ^\zsh: quit (core dumped) ./test
验证signal返回值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <stdio.h> #include <signal.h> using namespace std;void fun1 (int ) { cout<<"fun1" <<endl; }void fun2 (int ) { cout<<"fun2" <<endl; }int main () { void (*res)(int ); if ( (res=signal (SIGINT,fun1)) == SIG_ERR ){ perror ("error!" ); return -1 ; } raise (SIGINT); if ( (res=signal (SIGINT,fun2)) == SIG_ERR ){ perror ("error!" ); return -1 ; } raise (SIGINT); res (SIGINT); return 0 ; } > ./test fun1 fun2 fun1
sigaction函数 函数原型 :
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)
:用来注册一个信号的捕捉函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <signal.h> struct sigaction { void (*sa_handler)(int ); void (*sa_sigaction)(int , siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void ); };int sigaction (int signum,const struct sigaction *act,struct sigaction *oldact) ; signum:要捕捉的信号编号; act:传入参数,指定新的信号处理方式; oldact:传出参数,输出上一次的信号处理方式(不为0 的话),不需要可以传入NULL ; 成功:0 ; 失败:-1 ,errno;
特性 :
进程正常运行时,默认PCB中有一个信号屏蔽字mask,它决定了进程自动屏蔽哪些信号,当注册了某个信号捕捉函数,捕捉到该信号以后,要调用信号捕捉函数,而信号捕捉函数有可能执行很长时间,在这期间所屏蔽的信号不由mask来指定,而是用act中的sa_mask来指定,调用完信号处理函数后,再恢复为mask
当sa_flag=0时,xxx信号捕捉函数执行期间,xxx信号会自动被该进程屏蔽
阻塞的常规信号不支持排队,多次产生只记录一次(也就是只处理一次)。(后32个实时信号支持排队)
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <csignal> #include <cstdio> #include <cstdlib> void sig_catch (int signo) { if (signo==SIGINT) printf ("catch you!! %d\n" ,signo); else if (signo==SIGQUIT) printf ("----catch you!! %d\n" ,signo); return ; }int main (int argc, char *argv[]) { struct sigaction act,oldact; act.sa_handler=sig_catch; sigemptyset (&act.sa_mask); act.sa_flags=0 ; int ret=sigaction (SIGINT,&act,&oldact); if (ret==-1 ){ perror ("sigaction error" ); exit (1 ); } ret=sigaction (SIGQUIT,&act,&oldact); if (ret==-1 ){ perror ("sigaction error" ); exit (1 ); } while (1 ); return 0 ; }
验证返回值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <stdio.h> #include <csignal> using namespace std;void fun1 (int signo) { cout<<"fun1" <<endl; }void fun2 (int signo) { cout<<"fun2" <<endl; }int main () { struct sigaction act,oldact; act.sa_handler=fun1; sigemptyset (&act.sa_mask); act.sa_flags=0 ; int ret=sigaction (SIGINT,&act,&oldact); if (ret==-1 ){ perror ("sigaction error!" ); exit (1 ); } raise (SIGINT); act.sa_handler=fun2; sigemptyset (&act.sa_mask); ret=sigaction (SIGINT,&act,&oldact); if (ret==-1 ){ perror ("error!" ); return -1 ; } raise (SIGINT); oldact.sa_handler (SIGINT); return 0 ; } > ./test fun1 fun2 fun1
内核实现信号捕捉过程
子进程回收 SIGCHLD信号 产生条件
当子进程状态发生变化,就会产生SIGCHLD信号 (例如子进程终止)
借助SIGCHLD信号回收子进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <bits/types/sigset_t.h> #include <cstdlib> #include <unistd.h> #include <cstdio> #include <sys/wait.h> #include <signal.h> void catch_child (int signo) { pid_t wpid; int status; while ((wpid=waitpid (-1 ,&status,0 ))!=-1 ){ if (WIFEXITED (status)){ printf ("----------catch child id %d,ret=%d\n" ,wpid,WEXITSTATUS (status)); } } return ; }int main (int argc, char *argv[]) { pid_t pid; sigset_t mask; sigemptyset (&mask); sigaddset (&mask,SIGCHLD); sigprocmask (SIG_BLOCK,&mask,NULL ); int i; for (i=0 ;i<5 ;++i){ if ((pid=fork())==0 ) { break ; } } if (5 ==i){ struct sigaction act; act.sa_handler=catch_child; sigemptyset (&act.sa_mask); act.sa_flags=0 ; sigaction (SIGCHLD,&act,NULL ); sigprocmask (SIG_UNBLOCK,&mask,NULL ); printf ("I'm parent,pid=%d\n" ,getpid ()); }else { printf ("I'm child pid=%d\n" ,getpid ()); } return 0 ; }
中断系统调用 概念 :
系统调用分为两类:慢速系统调用和其他系统调用
慢速系统调用
:可能会使进程永远阻塞的一类系统调用 ,如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行 。这一类系统调用有read、write、pause、wait…
慢速系统调用中断问题 :
慢速系统调用被信号中断后将不会再被执行,而我们可以利用 注册信号捕捉函数的sa_flags参数来设置被信号中断后系统调用是否重启。
不重启(默认):SA_INTERRURT
, 重启 :SA_RESTART
扩展 :
sa_flags还有很多可选参数,如:捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号,可以将sa_flags设置为SA_NODEFER,除非sa_mask中包含该信号
如果想要信号传递复杂信息(例如结构体),需要 将sa_flags设置为SA_SIGINFO ,然后使用void (*sa_sigaction)(int, siginfo_t *, void *); 这个函数来进行传递
进程组和会话 进程组 概念 :
进程组
,也称之为作业,代表一个或多个进程的集合,每个进程都有属于的一个进程组 ,是为了简化对多个进程的管理
当父进程创建子进程的时候,默认子进程与父进程属于同一进程组 。**进程组ID(PGID)==第一个进程ID(组长进程)**。所以组长进程标识,其进程组ID==其进程ID
可以使用kill -SIGKILL -进程组ID(取负值)来将整个进程组内的进程全部杀死
一个进程可以为自己或子进程设置进程组ID
会话 概念 :
在Linux中,会话(Session)是指用户与操作系统交互的一段时间
我们常见的Linux session一般是指shell session 。Shell session 是终端中当前的状态,在终端中只能有一个 session。当我们打开一个新的终端时,总会创建一个新的 shell session。这表明会话是我们和shell交互的一个过程。
tty:文字终端,只能输入命令无图形化
pts:虚拟终端,有图形化界面
创建会话注意事项 :
调用进程不能是进程组组长 ,该进程变成新会话首进程
该进程成为一个新进程组的组长进程
需要root权限(ubuntu不需要)
新会话丢弃原有的控制终端,该会话没有控制终端
该调用进程是组长进程,则出错返回
建立新会话时,先调用fork,父进程终止,子进程调用setsid()-创建新会话
getsid函数 函数原型 :
pid_t getsid(pid_t pid)
:获取进程所属的会话ID
1 2 3 4 5 6 7 8 9 10 11 12 #include <unistd.h> pid_t getsid (pid_t pid) ; pid:进程ID; 成功:返回调用进程的会话ID; 失败:-1. errno;
setsid函数 函数原型 :
pid_t setsid(void)
:创建一个会话,并以自己的进程ID设置进程组ID,同时也是新会话的ID
1 2 3 4 5 6 7 8 #include <unistd.h> pid_t setsid (void ) ; 成功:返回调用进程的会话ID; 失败:-1 ,errno;
注:
调用了setsid函数的进程,既是新的会长,也是新的组长
调用进程不能是组长进程
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <unistd.h> #include <cstdio> #include <cstdlib> int main (int argc, char *argv[]) { pid_t pid; if ((pid=fork())<0 ) { perror ("fork error" ); exit (1 ); }else if (pid==0 ) { printf ("child process PID is %d\n" ,getpid ()); printf ("Group ID of child is %d\n" ,getpgid (0 )); printf ("Session ID of child is %d\n" ,getsid (0 )); sleep (10 ); setsid (); printf ("Changed:\n" ); printf ("child process PID is %d\n" ,getpid ()); printf ("Group ID of child is %d\n" ,getpgid (0 )); printf ("Session ID of child is %d\n" ,getsid (0 )); sleep (20 ); exit (0 ); } return 0 ; } ./test child process PID is 18261 Group ID of child is 18260 Session ID of child is 16952 ~/test > Changed: child process PID is 18261 Group ID of child is 18261 Session ID of child is 18261
守护进程 概念 :
守护进程
:叫做Daemon(精灵)进程 ,是Linux中的后台服务进程,通常独立于(脱离)控制终端并且周期性地执行某种人物或等待处理某些发生的事情 ,一般采用以d结尾的名字
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互,不受用户登陆、注销的影响 ,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现、ftp服务器、nfs服务器等
创建守护进程,最关键一步是调用setsid函数创建一个新的Session,并成为Session Leader
创建守护进程模型 :
创建子进程,父进程退出 。所有工作在子进程中进行形式上脱离了控制终端
在子进程中创建新会话,使用setsid()函数 ,使子进程完全独立出来,脱离控制
**根据需要,改变守护进程工作目录,使用chdir()函数,防止占用可卸载的文件系统(例如U盘)**,防止放在可卸载的文件系统,使得守护进程服务无法执行
重设文件权限掩码,使用umask()函数,防止继承的文件创建屏蔽字拒绝某些权限 ,增加守护进程灵活性
**关闭或重定向文件描述符,继承的打开文件不会用到(针对012默认文件描述符)**,重定向的话将012重定向到/dev/null这个空洞文件下,浪费系统资源,无法卸载
开始执行守护进程核心工作守护进程退出处理程序模型
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <cstdlib> #include <unistd.h> #include <cstdio> #include <sys/stat.h> #include <fcntl.h> int main (int argc, char *argv[]) { pid_t pid; pid=fork(); if (pid>0 ) { exit (0 ); } pid=setsid (); if (pid==-1 ){ perror ("setsid error" ); exit (1 ); } int ret=chdir ("/home/moon/test" ); if (ret==-1 ){ perror ("chdir error" ); exit (1 ); } umask (0022 ); close (STDIN_FILENO); int fd=open ("/dev/null" ,O_RDWR); if (fd==-1 ){ perror ("open error" ); exit (1 ); } dup2 (fd, STDOUT_FILENO); dup2 (fd, STDERR_FILENO); while (1 ) { } return 0 ; } ./test kill -9 test的进程ID
线程控制 线程概念 概念 :
线程
:可以看作是轻量级进程(LWP),有独立的PCB,没有独立的进程地址空间(共享)
在Linux下:
线程:最小的执行单位
进程:最小分配资源单位,可看成是只有一个线程的进程
ps -Lf 进程ID
:**可以查看线程号(LWP)**,CPU执行的最小单位
轻量级进程(LWP),也有PCB,创建线程使用的底层函数和进程一样,都是克隆clone
从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的
进程可以蜕变成线程
线程可看作寄存器和栈的集合
注:线程ID和线程号是两个东西
线程ID:用来标识线程的
线程号:标识线程身份交给CPU使用分配执行时间
线程共享资源 :
文件描述符表
每种信号的处理方式
当前工作目录
用户ID和组ID
内存地址空间(.text/.data/.bss/heap/共享库)
线程非共享资源 :
线程ID
处理器现场(寄存器值)和栈指针(内核栈)
独立的栈空间(用户空间栈)
errno变量
信号屏蔽字
调度优先级
线程优缺点 :
优点:
提高程序并发性
开销小
数据通信、共享数据方便
缺点:
库函数,不稳定
调试、编写困难、gdb不支持
对信号支持不好
注:优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大
线程控制原语 注 :
在Linux环境中,所有线程特点,失败均直接返回错误号!
所以得到的ret就是错误号,需要通过strerror来对错误号进行解读,说明错误原因
pthread_self函数 函数原型 :
pthread_t pthread_self(void)
:获取线程ID ,其作用对应进程中getpid()函数
1 2 3 4 5 6 7 8 9 #include <pthread.h> typedef unsigned long int pthread_t ;pthread_t pthread_self (void ) ; 成功:获取当前线程ID;
pthread_create函数 函数原型 :
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)
:创建一个新线程 ,其作用对应进程中fork()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <pthread.h> typedef unsigned long int pthread_t ;typedef struct { int detachstate; int schedpolicy; struct sched_param schedparam; int inheritsched; int scope; size_t guardsize; int stackaddr_set; void * stackaddr; size_t stacksize; }pthread_attr_t ;int pthread_create (pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg) ; thread:传出参数,新创建的子线程ID; attr:线程属性参数,通常传NULL ,表示使用线程默认属性; start_routine:函数指针,子线程回调函数。创建成功,pthread_create函数返回时,该函数会被自动调用; arg:线程主函数执行期间所使用的参数(参数3 函数的参数,没有传NULL ); 成功:0 ; 失败:错误号;
设置线程属性
1 2 3 4 5 6 7 8 9 10 11 typedef struct { int detachstate; int schedpolicy; struct sched_param schedparam; int inheritsched; int scope; size_t guardsize; int stackaddr_set; void * stackaddr; size_t stacksize; }pthread_attr_t ;
int pthread_attr_init(pthread_attr_t *attr)
:初始化线程属性结构体
1 2 3 4 5 6 7 8 9 10 11 #include <pthread.h> int pthread_attr_init (pthread_attr_t *attr) ; attr:传出参数,要初始化的线程属性结构体; 成功:0 ; 失败:错误号;
int pthread_attr_destroy(pthread_attr_t *attr)
:销毁线程属性所占用的资源
1 2 3 4 5 6 7 8 9 10 11 12 #include <pthread.h> int pthread_attr_destroy (pthread_attr_t *attr) ; attr:要销毁的线程属性结构体; 成功:0 ; 失败:错误号;
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate)
:设置线程属性是分离还是非分离状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <pthread.h> int pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate) ; attr:线程属性结构体; detachstate:分离状态; PTHREAD_CREATE_DETACHED:分离状态; PTHREAD_CREATE_JOINABLE:非分离状态; 成功:0 ; 失败:错误号;
pthread_create()示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <cstdlib> #include <pthread.h> #include <cstdio> #include <unistd.h> #include <string.h> void *tfn (void *arg) { printf ("thread:pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); return NULL ; }int main (int argc, char *argv[]) { pthread_t tid; printf ("main:pid=%d,tid=%lu\n" ,getpid (),tid); int ret=pthread_create (&tid,NULL ,tfn,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } sleep (1 ); return 0 ; }
注:因为线程是不共享errno变量,所以直接返回的就是errno,所以得到的ret就是errno编号,需要通过strerror来对errno说明错误原因
创建多个子线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <cstdlib> #include <pthread.h> #include <cstdio> #include <unistd.h> #include <string.h> void *tfn (void *arg) { int i=*(int *)arg; sleep (i); printf ("--I'm %dth thread:pid=%d,tid=%lu\n" ,i+1 ,getpid (),pthread_self ()); return NULL ; }int main (int argc, char *argv[]) { int i,ret; pthread_t tid; for (i=0 ;i<5 ;++i){ int *arg=new int (i); ret=pthread_create (&tid, NULL ,tfn,arg); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } } sleep (i); printf ("main:I'm Main,pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <cstdlib> #include <pthread.h> #include <cstdio> #include <unistd.h> #include <string.h> void *tfn (void *arg) { int i=*(int *)arg; printf ("--I'm %dth thread:pid=%d,tid=%lu\n" ,i+1 ,getpid (),pthread_self ()); return NULL ; }int main (int argc, char *argv[]) { int i,ret; pthread_t tid; for (i=0 ;i<5 ;++i){ ret=pthread_create (&tid, NULL ,tfn,(void *)&i); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } sleep (i); } sleep (1 ); printf ("main:I'm Main,pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); return 0 ; }
设置线程属性示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <cstddef> #include <cstdlib> #include <cstring> #include <pthread.h> #include <cstdio> #include <unistd.h> void *tfn (void *arg) { printf ("thread:pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); return NULL ; }int main (int argc, char *argv[]) { pthread_t tid; pthread_attr_t attr; int ret=pthread_attr_init (&attr); if (ret!=0 ){ fprintf (stderr,"pthread_attr_init error:%s\n" ,strerror (ret)); exit (1 ); } pthread_attr_setdetachstate (&attr,PTHREAD_CREATE_DETACHED); if (ret!=0 ){ fprintf (stderr,"pthread_attr_setdetachstate error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_create (&tid,&attr,tfn,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_attr_destroy (&attr); if (ret!=0 ){ fprintf (stderr,"pthread_attr_destroy error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_join (tid,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_join error:%s\n" ,strerror (ret)); exit (1 ); } printf ("main:pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); pthread_exit ((void *)0 ); }
pthread_exit函数 函数原型 :
void pthread_exit(void *retval)
:退出当前线程
1 2 3 4 5 6 #include <pthread.h> void pthread_exit (void *retval) ; retval:表示线程退出状态(退出值),无退出值,传NULL ;
注:
exit函数是退出进程的函数,所以不能使用exit退出线程 ,要不然会将这个进程杀死导致其他线程也被删除!
return是返回到调用者,也不是退出的意思,但是你也可以返回
pthread_exit是将调用该函数的线程退出
pthread_join函数 函数原型 :
int pthread_join(pthread_t thread,void **retval)
:阻塞等待线程退出(回收指定线程),获取线程退出状态 。其作用对应进程中waitpid()函数,线程不回收跟进程一样会产生僵尸
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <pthread.h> int pthread_join (pthread_t thread, void **retval) ; thread:线程ID; retval:传出参数,存储线程结束状态。因为线程结束回调函数返回值为void *,所以接受返回值就需要void **; 成功:0 ; 失败:错误号;
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <cstdlib> #include <cstring> #include <pthread.h> #include <cstdio> struct thrd { int var; char str[256 ]; };void *tfn (void *arg) { struct thrd *tval; tval=(thrd *)malloc (sizeof (struct thrd)); tval->var=100 ; strcpy (tval->str,"hello thread" ); return (void *)tval; }int main (int argc, char *argv[]) { pthread_t tid; struct thrd *retval; int ret=pthread_create (&tid,NULL ,tfn,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_join (tid,(void **)&retval); if (ret!=0 ){ fprintf (stderr,"pthread_join error:%s\n" ,strerror (ret)); exit (1 ); } printf ("child thread exit with var=%d,str=%s\n" ,retval->var,retval->str); pthread_exit (NULL ); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <cstdlib> #include <cstring> #include <pthread.h> #include <cstdio> struct thrd { int var; char str[256 ]; };void *tfn (void *arg) { struct thrd * tval=(thrd *)arg; tval->var=100 ; strcpy (tval->str,"hello thread" ); return (void *)tval; }int main (int argc, char *argv[]) { pthread_t tid; struct thrd arg; struct thrd *retval; int ret=pthread_create (&tid,NULL ,tfn,(void *)&arg); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_join (tid,(void **)&retval); if (ret!=0 ){ fprintf (stderr,"pthread_join error:%s\n" ,strerror (ret)); exit (1 ); } printf ("child thread exit with var=%d,str=%s\n" ,retval->var,retval->str); printf ("child thread exit with var=%d,str=%s\n" ,arg.var,arg.str); pthread_exit (NULL ); return 0 ; }
pthread_detach函数 函数原型 :
int pthread_detach(pthread_t thread)
:实现线程分离,可以将指定线程分离出线程组自立门户,线程结束后自动释放资源,不会残留资源在内核中
1 2 3 4 5 6 7 8 9 10 11 12 #include <pthread.h> int pthread_detach (pthread_t thread) ; thread:待分离线程ID; 成功:0 ; 失败:错误号;
注:
线程分离状态
:指定该状态,线程主动与主控线程断开关系,线程结束后,其退出状态不由其他线程获取,而直接自己自动释放 。网络、多线程服务器常用!
进程若有该分离机制,将不会产生僵尸进程。
也可以使用pthread_create函数参2(线程属性)来设置线程分离。
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <cstdlib> #include <pthread.h> #include <cstdio> #include <unistd.h> #include <string.h> void *tfn (void *arg) { printf ("thread:pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); return NULL ; }int main (int argc, char *argv[]) { int i,ret; pthread_t tid; ret=pthread_create (&tid, NULL ,tfn,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_detach (tid); if (ret!=0 ){ fprintf (stderr,"pthread_detach error:%s\n" ,strerror (ret)); exit (1 ); } sleep (1 ); ret=pthread_join (tid,NULL ); printf ("join ret=%d\n" ,ret); if (ret!=0 ){ fprintf (stderr,"pthread_join error:%s\n" ,strerror (ret)); exit (1 ); } printf ("main:I'm Main,pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); pthread_exit ((void *)0 ); } > ./test thread:pid=12954 ,tid=139659550131904 join ret=22 pthread_join error:Invalid argument
pthread_cancel函数 函数原型 :
int pthread_cancel(pthread_t thread)
:杀死(取消)线程 ,对应进程中的kill()函数
1 2 3 4 5 6 7 8 9 10 11 12 #include <pthread.h> int pthread_cancel (pthread_t thread) ; thread:要杀死(取消)的线程ID; 成功:0 ; 失败:错误号;
注:
杀死(取消)一个线程,并不是立即杀死(取消),而是得等到到达了一个取消点,再进行取消
取消点
:是线程检查是否被取消,并按请求进行动作的一个位置,通常是一些系统调用的位置 ,可以用man 7 pthreads 查看具备这些取消点的系统调用列表
被pthread_cancel()杀死的线程,将会返回-1,使用pthread_join()函数回收
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <cstdlib> #include <cstring> #include <pthread.h> #include <cstdio> #include <unistd.h> void *tfn (void *arg) { while (1 ) { printf ("thread:pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); sleep (1 ); } return NULL ; }int main (int argc, char *argv[]) { pthread_t tid; int ret=pthread_create (&tid,NULL ,tfn,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } printf ("main:pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); sleep (5 ); ret=pthread_cancel (tid); if (ret!=0 ){ fprintf (stderr,"pthread_cancel error:%s\n" ,strerror (ret)); exit (1 ); } while (1 ); pthread_exit ((void *)0 ); } > ./test main:pid=17152 ,tid=133482618086656 thread:pid=17152 ,tid=133482611214016 thread:pid=17152 ,tid=133482611214016 thread:pid=17152 ,tid=133482611214016 thread:pid=17152 ,tid=133482611214016 thread:pid=17152 ,tid=133482611214016
pthread_testcancel函数 函数原型 :
void pthread_testcancel(void)
:为pthread_cancel函数自定义添加取消点
1 2 3 #include <pthread.h> void pthread_testcancel (void ) ;
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <cstdlib> #include <cstring> #include <pthread.h> #include <cstdio> #include <unistd.h> void *tfn (void *arg) { while (1 ) { pthread_testcancel (); } return NULL ; }int main (int argc, char *argv[]) { pthread_t tid; int ret=pthread_create (&tid,NULL ,tfn,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } printf ("main:pid=%d,tid=%lu\n" ,getpid (),pthread_self ()); ret=pthread_cancel (tid); if (ret!=0 ){ fprintf (stderr,"pthread_cancel error:%s\n" ,strerror (ret)); exit (1 ); } pthread_exit ((void *)0 ); }
线程使用注意事项
malloc和mmap申请的内存可以被其他线程释放(因为线程共享内存)
应避免在多线程模型中调用fork,除非马上exec ,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit
信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制
线程同步 概念 概念 :
同步
:即协同步调,按预定的先后次序运行
线程同步
:指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其他线程为保证数据一致性,不能调用该功能
同步目的:为了避免数据混乱,解决与时间有关的错误 。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制
所有”多个控制流,共同操作一个共享资源”的情况,都需要同步
数据混乱原因 :
资源共享
调度随机(意味着数据访问会出现竞争)
线程间缺乏必要的同步机制
互斥量(锁)mutex 函数原型 :
pthread_mutex_t lock
:定义锁(创建锁)
注:pthread_mutex_t类型 ,其本质是一个结构体,为简化理解,应用时可忽略其实现细节,简单当成整数看待
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr)
:初始化互斥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <pthread.h> int pthread_mutex_init (pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr) ; mutex:要初始化的互斥量(锁); mutexattr:锁属性参数,通常传入NULL ,表示默认属性;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 成功:0 ; 失败:错误号;
int pthread_mutex_lock(pthread_mutex_t *mutex)
:加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_mutex_lock (pthread_mutex_t *mutex) ; mutex:互斥量(锁); 成功:0 ; 失败:错误号;
int pthread_mutex_unlock(pthread_mutex_t *mutex)
:解锁
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_mutex_unlock (pthread_mutex_t *mutex) ; mutex:互斥量(锁); 成功:0 ; 失败:错误号;
int pthread_mutex_trylock(pthread_mutex_t *mutex)
:尝试加锁(非阻塞),成功就加锁,失败则返回 。
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_mutex_trylock (pthread_mutex_t *mutex) ; mutex:互斥量(锁); 成功:0 ; 失败:错误号 如:EBUSY;
int pthread_mutex_destroy(pthread_mutex_t *mutex)
:销毁互斥锁
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_mutex_destroy (pthread_mutex_t *mutex) ; mutex:互斥量(锁); 成功:0 ; 失败:错误号;
注:
互斥锁mutex变量的取值为0或1
尽量保证锁的粒度,越小越好**(访问贡献数据前加锁,访问结束立即解锁)**
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <cstddef> #include <cstdlib> #include <cstring> #include <ctime> #include <pthread.h> #include <cstdio> #include <unistd.h> pthread_mutex_t mutex;void *tfn (void *arg) { srand (time (NULL )); while (1 ) { pthread_mutex_lock (&mutex); printf ("hello " ); sleep (rand ()%3 ); printf ("world\n" ); pthread_mutex_unlock (&mutex); sleep (rand ()%3 ); } return NULL ; }int main (int argc, char *argv[]) { pthread_t tid; srand (time (NULL )); int ret=pthread_mutex_init (&mutex,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_mutex_init error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_create (&tid,NULL ,tfn,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } while (1 ) { pthread_mutex_lock (&mutex); printf ("HELLO " ); sleep (rand ()%3 ); printf ("WORLD\n" ); pthread_mutex_unlock (&mutex); sleep (rand ()%3 ); } ret=pthread_join (tid, NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_join error:%s\n" ,strerror (ret)); exit (1 ); } ret=pthread_mutex_destroy (&mutex); if (ret!=0 ){ fprintf (stderr,"pthread_mutex_destroy error:%s\n" ,strerror (ret)); exit (1 ); } return 0 ; }
死锁 概念 :
产生情况 :
对一个锁反复lock
两个线程,线程1持有A锁,请求B锁,线程2持有B锁,请求A锁,导致死循环
读写锁rwlock 概念 :
与互斥量类似,但读写锁 允许更高的并行性。其特性为:写独占,读共享
以读方式给数据加锁 称之为读锁
以写方式给数据加锁 称之为写锁
特性 :
读写锁使用特性 :
读写锁是”写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞
读写锁是”读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式对其加锁会阻塞
读写锁是”读模式加锁”时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程,那么读写锁会阻塞随后的读模式加锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
。
读写锁非常适合对于数据结构读的次数大于写的情况
函数原型 :
int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t attr)
:初始化读写锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <pthread.h> int pthread_rwlock_init (pthread_rwlock_t *rwlock,const pthread_rwlockattr_t attr) ; rwlock:读写锁; attr:读写锁属性参数,默认属性传NULL ;pthread_cond_t cond = PTHREAD_RWLOCK_INITIALIZER; 成功:0 ; 失败:错误号;
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
:读模式加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock) ; rwlock:读写锁; 成功:0 ; 失败:错误号;
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
:尝试以读模式加锁,成功就加锁,失败则返回
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock) ; rwlock:读写锁; 成功:0 ; 失败:错误号;
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
:以写模式加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock) ; rwlock:读写锁; 成功:0 ; 失败:错误号;
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
:尝试以写模式加锁,成功就加锁,失败则返回
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock) ; rwlock:读写锁; 成功:0 ; 失败:错误号;
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
:解锁
1 2 3 4 5 6 7 8 9 10 11 12 #include <pthread.h> int pthread_rwlock_unlock (pthread_rwlock_t *rwlock) ; rwlock:读写锁; 成功:0 ; 失败:错误号;
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
:销毁读写锁属性所占用资源
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_rwlock_destroy (pthread_rwlock_t *rwlock) ; rwlock:读写锁; 成功:0 ; 失败:错误号;
示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include <cstddef> #include <cstdlib> #include <cstring> #include <ctime> #include <pthread.h> #include <cstdio> #include <unistd.h> int counter;pthread_rwlock_t rwlock;void *th_write (void *arg) { int t; int i=*(int *)arg; while (1 ) { pthread_rwlock_wrlock (&rwlock); t=counter; usleep (1000 ); printf ("======write %d: %lu:counter=%d ++counter=%d\n" ,i,pthread_self (),t,++counter); pthread_rwlock_unlock (&rwlock); usleep (10000 ); } return NULL ; }void *th_read (void *arg) { int i=*(int *)arg; while (1 ) { pthread_rwlock_rdlock (&rwlock); printf ("----------read %d: %lu: %d\n" ,i,pthread_self (),counter); pthread_rwlock_unlock (&rwlock); usleep (2000 ); } return NULL ; }int main (int argc, char *argv[]) { pthread_t tid[8 ]; int i; int ret=pthread_rwlock_init (&rwlock,NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_rwlock_init error:%s\n" ,strerror (ret)); exit (1 ); } for (i=0 ;i<3 ;++i){ ret=pthread_create (&tid[i],NULL ,th_write,(void *)&i); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } } for (i=0 ;i<5 ;++i){ ret=pthread_create (&tid[i+3 ],NULL ,th_read,(void *)&i); if (ret!=0 ){ fprintf (stderr,"pthread_create error:%s\n" ,strerror (ret)); exit (1 ); } } for (i=0 ;i<8 ;++i){ ret=pthread_join (tid[i], NULL ); if (ret!=0 ){ fprintf (stderr,"pthread_join error:%s\n" ,strerror (ret)); exit (1 ); } } ret=pthread_rwlock_destroy (&rwlock); if (ret!=0 ){ fprintf (stderr,"pthread_rwlock_destroy error:%s\n" ,strerror (ret)); exit (1 ); } return 0 ; }
条件变量cond 概念
函数原型
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr)
:初始化条件变量 ,一般是动态初始化,在函数体用到再初始化,但是也可以使用静态初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <pthread.h> int pthread_cond_init (pthread_cond_t *cond,const pthread_condattr_t *attr) ; cond:条件变量; attr:条件变量属性参数,默认属性传NULL ;pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 成功:0 ; 失败:错误号;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <pthread.h> int pthread_cond_wait (pthread_cond_t *cond,pthread_mutex_t *mutex) ; cond:条件变量; mutex:已掌握的互斥锁; 成功:0 ; 失败:错误号;
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_cond_mutex_t *mutex,const struct timespec *abstime)
:定时等待一个条件满足
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <pthread.h> int pthread_cond_timedwait (pthread_cond_t *cond,pthread_cond_mutex_t *mutex,const struct timespec *abstime) ;struct timespec { time_t tv_sec; long tv_nsec; }; cond:条件变量; mutex:互斥锁; abstime:定时时长,需要传绝对时间; 绝对时间:这里的绝对时间是从unix元年也就是1970.1 .1 开始,假设abs_timeout=3 就是1970.1 .1 日的0 点0 分3 秒;time_t cur=time (NULL ); struct timespec t; t.tv_sec=cur+1 ; t.tv_nsec=t.tv_sec+100 ;sem_timedwait (&sem,&t); 成功:0 ; 失败:错误号;
int pthread_cond_signal(pthread_cond_t *cond)
:通知函数,一次通知至少一个线程 ,也可以唤醒多个线程,但是通常用于通知一个线程
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_cond_signal (pthread_cond_t *cond) ; cond:条件变量; 成功:0 ; 失败:错误号;
pthread_cond_broadcast()
:广播函数,一次通知多个线程
1 2 3 4 5 6 7 8 9 10 11 12 #include <pthread.h> int pthread_cond_broadcast (pthread_cond_t *cond) ; cond:条件变量; 成功:0 ; 失败:错误号;
int pthread_cond_destroy(pthread_cond_t *cond)
:销毁条件变量属性所占用资源
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <pthread.h> int pthread_cond_destroy (pthread_cond_t *cond) ; cond:条件变量; 成功:0 ; 失败:错误号;
生产者消费者模型 概念 :
生产者消费者模型
:由生产者进行生产数据,生产者生产的数据将会放置到一个公共区域,由消费者从公共区域拿取消费
生产者模型工作流程 :
生产数据
加锁 pthread_mutex_lock
将数据放置到公共区域
解锁 pthread_mutex_unlock
通知阻塞在条件变量上的线程 pthread_cond_signal或pthread_cond_broadcast
循环生产后续数据
消费者模型工作流程 :
创建锁 mutex
初始化 pthread_mutex_init
加锁 pthread_mutex_lock
等待条件满足:pthread_cond_wait
阻塞等条件变量
解锁 unlock
加锁 lock
访问共享数据
解锁、释放条件变量,释放锁
实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <cstdlib> #include <cstring> #include <ctime> #include <pthread.h> #include <cstdio> #include <string> #include <unistd.h> void err_thread (int ret,std::string str) { if (ret!=0 ){ fprintf (stderr, "%s:%s\n" ,str.c_str (),strerror (ret)); pthread_exit (NULL ); } }struct msg { int num; struct msg *next; };struct msg *head;pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;pthread_cond_t has_data=PTHREAD_COND_INITIALIZER;void *produser (void *arg) { while (1 ){ struct msg *mp=(msg *)malloc (sizeof (struct msg)); mp->num=rand ()%1000 +1 ; printf ("--produce:%d\n" ,mp->num); int ret=pthread_mutex_lock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_lock error" ); mp->next=head; head=mp; ret=pthread_mutex_unlock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_unlock error" ); ret=pthread_cond_signal (&has_data); if (ret!=0 ) err_thread (ret,"pthread_cond_signal error" ); sleep (rand ()%3 ); } return NULL ; }void *consumer (void *arg) { while (1 ) { int ret=pthread_mutex_lock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_lock error" ); if (head==NULL ){ ret=pthread_cond_wait (&has_data,&mutex); if (ret!=0 ) err_thread (ret,"pthread_cond_wait error" ); } struct msg *mp; mp=head; head=mp->next; printf ("-----consumer:%d\n" ,mp->num); pthread_mutex_unlock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_unlock error" ); free (mp); sleep (rand ()%3 ); } return NULL ; }int main (int argc, char *argv[]) { pthread_t pid,cid; int ret; srand (time (NULL )); ret=pthread_create (&pid,NULL ,produser,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create produser error" ); ret=pthread_create (&cid,NULL ,consumer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create consumer error" ); ret=pthread_join (pid, NULL ); if (ret!=0 ) err_thread (ret,"pthread_join produser error" ); ret=pthread_join (cid,NULL ); if (ret!=0 ) err_thread (ret,"pthread_join consumer error" ); return 0 ; }
多个消费者示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <cstdlib> #include <cstring> #include <ctime> #include <pthread.h> #include <cstdio> #include <string> #include <unistd.h> void err_thread (int ret,std::string str) { if (ret!=0 ){ fprintf (stderr, "%s:%s\n" ,str.c_str (),strerror (ret)); pthread_exit (NULL ); } }struct msg { int num; struct msg *next; };struct msg *head;pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;pthread_cond_t has_data=PTHREAD_COND_INITIALIZER;void *produser (void *arg) { while (1 ){ struct msg *mp=(msg *)malloc (sizeof (struct msg)); mp->num=rand ()%1000 +1 ; printf ("--produce:%d\n" ,mp->num); int ret=pthread_mutex_lock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_lock error" ); mp->next=head; head=mp; ret=pthread_mutex_unlock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_unlock error" ); ret=pthread_cond_signal (&has_data); if (ret!=0 ) err_thread (ret,"pthread_cond_signal error" ); sleep (rand ()%3 ); } return NULL ; }void *consumer (void *arg) { while (1 ) { int ret=pthread_mutex_lock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_lock error" ); while (head==NULL ){ ret=pthread_cond_wait (&has_data,&mutex); if (ret!=0 ) err_thread (ret,"pthread_cond_wait error" ); } struct msg *mp; mp=head; head=mp->next; pthread_mutex_unlock (&mutex); if (ret!=0 ) err_thread (ret,"pthread_mutex_unlock error" ); printf ("-----consumer id:%lu :%d\n" ,pthread_self (),mp->num); free (mp); sleep (rand ()%3 ); } return NULL ; }int main (int argc, char *argv[]) { pthread_t pid,cid; int ret; srand (time (NULL )); ret=pthread_create (&pid,NULL ,produser,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create produser error" ); ret=pthread_create (&cid,NULL ,consumer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create consumer error" ); ret=pthread_create (&cid,NULL ,consumer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create consumer error" ); ret=pthread_create (&cid,NULL ,consumer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create consumer error" ); ret=pthread_create (&cid,NULL ,consumer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create consumer error" ); ret=pthread_join (pid, NULL ); if (ret!=0 ) err_thread (ret,"pthread_join produser error" ); pthread_join (cid,NULL ); if (ret!=0 ) err_thread (ret,"pthread_join consumer error" ); return 0 ; }
注:
虚假唤醒
:因为pthread_cond_wait会进行解锁操作,多个消费者同时阻塞在一个锁上,容易导致一个线程消费数据解锁后,另一线程拿到锁,但是公共区域没有数据所导致的虚假唤醒
信号量 概念
信号量
:是一个既能保证同步、数据不混乱,又能提高线程并发,可以用来实现多线程间对共享资源进行共享,相当于初始化为n的互斥量,n值表示可以同时访问共享数据区的线程数
可以应用于线程、进程间同步
函数原型
int sem_init(sem_t *sem,int pshared,unsigned int value)
:初始化信号量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <semaphore.h> int sem_init (sem_t *sem, int pshared, unsigned int value) ; sem:信号量变量; pshared:指定是否在线程、进程间共享;0 :表示线程间同步;1 (非0 ):表示进程间同步; value:N值,指定同时访问的线程数; 成功:0 ; 失败:-1 ,errno;
int sem_wait(sem_t *sem)
:相当于加锁,信号量值大于0,则信号量值-1;信号量等于0,则造成阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <semaphore.h> int sem_wait (sem_t *sem) ; sem:信号量; 成功:0 ; 失败:-1 ,errno;
int sem_trywait(sem_t *sem)
:尝试加锁,成功则信号量-1,失败则返回,非阻塞的sem_wait
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <semaphore.h> int sem_trywait (sem_t *sem) ; sem:信号量; 成功:0 ; 失败:-1 ,errno;
int sem_timedwait(sem_t *sem,const struct timespec *abs_timeout)
:设置定时加锁,定时的sem_wait
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <semaphore.h> int sem_timedwait (sem_t *sem,const struct timespec *abs_timeout) ;struct timespec { time_t tv_sec; long tv_nsec; }; sem:信号量; abs_timeout:定时时长,需要传绝对时间; 绝对时间:这里的绝对时间是从unix元年也就是1970.1 .1 开始,假设abs_timeout=3 就是1970.1 .1 日的0 点0 分3 秒;time_t cur=time (NULL ); struct timespec t; t.tv_sec=cur+1 ; t.tv_nsec=t.tv_sec+100 ;sem_timedwait (&sem,&t); 成功:0 ; 失败:-1 ,errno;
int sem_post(sem_t *sem)
:相当于解锁,当信号量<N,则信号量++;当信号量==N时,则造成阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <semaphore.h> int sem_post (sem_t *sem) ; sem:信号量; 成功:0 ; 失败:-1 ,errno;
int sem_destroy(sem_t *sem)
:释放信号量属性所占用资源
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <semaphore.h可以应用于线程、进程间同步> int sem_destroy (sem_t *sem) ; sem:信号量; 成功:0 ; 失败:-1 ,errno;
生产者消费者模型 实现示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include <cstdlib> #include <cstring> #include <ctime> #include <pthread.h> #include <cstdio> #include <string> #include <unistd.h> #include <semaphore.h> #define NUM 5 void err_thread (int ret,std::string str) { if (ret!=0 ){ fprintf (stderr, "%s:%s\n" ,str.c_str (),strerror (ret)); pthread_exit (NULL ); } }int queue[NUM]; sem_t blank_num,prod_num; void *produser (void *arg) { int i=0 ; while (1 ){ int ret=sem_wait (&blank_num); if (ret!=0 ) err_thread (ret,"sem_wait error" ); queue[i]=rand ()%1000 +1 ; printf ("--produce:%d\n" ,queue[i]); sem_post (&prod_num); if (ret!=0 ) err_thread (ret,"sem_post error" ); i=(i+1 )%NUM; sleep (rand ()%1 ); } return NULL ; }void *consumer (void *arg) { int i=0 ; while (1 ) { int ret=sem_wait (&prod_num); if (ret!=0 ) err_thread (ret,"sem_wait error" ); printf ("-----consumer id:%lu :%d\n" ,pthread_self (),queue[i]); queue[i]=0 ; ret=sem_post (&blank_num); if (ret!=0 ) err_thread (ret,"sem_post error" ); i=(i+1 )%NUM; sleep (rand ()%3 ); } return NULL ; }int main (int argc, char *argv[]) { pthread_t pid,cid; int ret; srand (time (NULL )); ret=sem_init (&blank_num,0 ,NUM); if (ret!=0 ) err_thread (ret,"sem_init blank_num error" ); sem_init (&prod_num,0 ,0 ); if (ret!=0 ) err_thread (ret,"sem_init prod_num error" ); ret=pthread_create (&pid,NULL ,produser,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create produser error" ); ret=pthread_create (&cid,NULL ,consumer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create consumer error" ); ret=pthread_join (pid, NULL ); if (ret!=0 ) err_thread (ret,"pthread_join produser error" ); ret=pthread_join (cid,NULL ); if (ret!=0 ) err_thread (ret,"pthread_join consumer error" ); return 0 ; }
多个消费者单个生产者示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <cstdlib> #include <cstring> #include <ctime> #include <pthread.h> #include <cstdio> #include <string> #include <unistd.h> #include <semaphore.h> #define NUM 5 #define NUM_CONSUMERS 3 void err_thread (int ret,std::string str) { if (ret!=0 ){ fprintf (stderr, "%s:%s\n" ,str.c_str (),strerror (ret)); pthread_exit (NULL ); } }int queue[NUM]; sem_t blank_num,prod_num; void *producer (void *arg) { int i=0 ; while (1 ){ int ret=sem_wait (&blank_num); if (ret!=0 ) err_thread (ret,"sem_wait error" ); queue[i]=rand ()%1000 +1 ; printf ("--producer:%d\n" ,queue[i]); sem_post (&prod_num); if (ret!=0 ) err_thread (ret,"sem_post error" ); i=(i+1 )%NUM; sleep (rand ()%1 ); } return NULL ; }void *consumer (void *arg) { int i=0 ; while (1 ) { int ret=sem_wait (&prod_num); if (ret!=0 ) err_thread (ret,"sem_wait error" ); while (queue[i] == 0 ) { ret=sem_post (&prod_num); if (ret!=0 ) err_thread (ret,"sem_post error" ); usleep (100 ); ret=sem_wait (&prod_num); if (ret!=0 ) err_thread (ret,"sem_wait error" ); } printf ("-----consumer id:%lu :%d\n" ,pthread_self (),queue[i]); queue[i]=0 ; ret=sem_post (&blank_num); if (ret!=0 ) err_thread (ret,"sem_post error" ); i=(i+1 )%NUM; sleep (rand ()%3 ); } return NULL ; }int main (int argc, char *argv[]) { pthread_t producer_tid; pthread_t consumer_tids[NUM_CONSUMERS]; int ret; srand (time (NULL )); ret=sem_init (&blank_num,0 ,NUM); if (ret!=0 ) err_thread (ret,"sem_init blank_num error" ); sem_init (&prod_num,0 ,0 ); if (ret!=0 ) err_thread (ret,"sem_init prod_num error" ); ret=pthread_create (&producer_tid,NULL ,producer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create producer error" ); for (int i = 0 ; i < NUM_CONSUMERS; ++i) { ret=pthread_create (&consumer_tids[i],NULL ,consumer,NULL ); if (ret!=0 ) err_thread (ret,"pthread_create consumer error" ); } ret=pthread_join (producer_tid, NULL ); if (ret!=0 ) err_thread (ret,"pthread_join producer error" ); for (int i = 0 ; i < NUM_CONSUMERS; ++i) { ret=pthread_join (consumer_tids[i],NULL ); if (ret!=0 ) err_thread (ret,"pthread_join consumer error" ); } return 0 ; }