文件描述符概述
用户使用文件描述符(file descriptor)来访问文件。
利用open打开一个文件时,内核会返回一个文件描述符。
每个进程都有一张文件描述符的表,进程刚被创建时,标准输入、标准输出、标准错误输出
设备文件被打开,对应的文件描述符0、1、2 记录在表中
在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件
描述符记录在表中。
Linux中一个进程最多只能打开NR_OPEN_DEFAULT(即1024)个文件,故当文件不再使用时应及
时调用close函数关闭文件。
int dup(int oldfd);
功能:复制oldfd文件描述符,并分配一个新的文件描述符,新的文件描述符是调用进程文
件描述符表中最小可用的文件描述符。
例:
int main(void)
{
//通过dup函数复制一个文件描述符
int fd;
//dup执行后给返回值文件描述符分配的值是文件描述符表中最小可用的文件描述符
fd = dup(1);
printf("fd = %d\n", fd);
//由于通过dup函数将1这个文件描述符复制了一份为fd,所以fd现在就相当于1,所
以写数据就是想终端写入数据
write(fd, "nihao beijing\n", strlen("nihao beijing\n"));
return 0;
}
int dup2(int oldfd,int newfd);
功能:复制一份打开的文件描述符oldfd,并分配新的文件描述符newfd,newfd也标识oldfd所
标识的文件。
注意:
newfd是小于文件描述符最大允许值的非负整数,如果newfd是一个已经打开的文件描述符,
则首先关闭该文件,然后再复制。
例:
打开 1.txt ,其文件描述符为fd,fd2 = dup2(fd1,1); 当printf时,1.txt和终端都有
1、进程
ps ajx 在ubuntu中查看当前系统中所有的开启的进程
PPID 父
PID
PGID 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种
信号,关联的进程有一个进程组号(PGID)
/*************************************************************************************************/
1)、进程控制
获得进程号
pid_t getpid(void);
pid_t getppid(void);
pid_t getpgid(pid_t pid);
创建
pid_t fork(void);
pid_t > 0 :标记父进程代码区,为子进程号。
pid_t = 0 :标记子进程代码区
pid_t = ‐1 : 返回给父进程,子进程不会创建
子进程会继承父进程的一些公有的区域,如磁盘空间,内核空间
文件描述符的偏移量保存在内核空间中。
例:所以父进程改变偏移量,则子进程获取的偏移量是改变之后的
文件指针????
pid_t vfork(void);
fork和vfork函数的区别:
vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度运行。
vfork和fork一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程
中,因为子进程会立即调用exec(或exit),于是也就不访问该地址空间。
相反,在子进程中调用exec或exit之前,它在父进程的地址空间中运行,在exec之后子
进程会有自己的进程空间。
//使用vfork创建完子进程
//在子进程执行exit或者exec之前,父子进程共有同一块地址空间
挂起
unsigned int sleep(unsigned int seconds);
usleep
等待
pid_t wait(int *status);
等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
WIFEXITED(status)
如果子进程是正常终止的,取出的字段值非零。
WEXITSTATUS(status)
返回子进程的退出状态,退出状态保存在status变量的8~16位。
在用此宏前应先用宏WIFEXITED判断子进程是否正常退出,正常退出才可以使用此
exit();
使用exit退出当前进程并设置退出状态
pid_t waitpid(pid_t pid, int *status, int options);
参数:
pid:指定的进程或者进程组
pid>0:等待进程ID等于pid的子进程。
pid=0:等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid
不会等待它。
pid=‐1:等待任一子进程,此时waitpid和wait作用一样。
pid<‐1:等待指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值
status:保存子进程退出时的状态信息
options:选项
0:同wait,阻塞父进程,等待子进程退出。
WNOHANG:没有任何已经结束的子进程,则立即返回。
WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。
(跟踪调试,很少用到)
终止
void exit(int status);
void _exit(int status);
exit和_exit函数的区别:
exit为库函数,而_exit为系统调用
exit会刷新缓冲区,但是_exit不会刷新缓冲区
一般会使用exit
int atexit(void (*fun)(void));
替换
exec函数族(6个)
调用exec函数的进程并不创建新的进程,故调用exec前后,进程的进程号并不会改
变,其执行的程序完全由新的程序替换,而新程序则从其main函数开始执行。
exec函数族取代调用进程的数据段、代码段和堆栈段。
一个进程调用exec后,除了进程ID,进程还保留了下列特征不变:
父进程号
进程组号
控制终端
根目录
当前工作目录
进程信号屏蔽集
未处理信号
execl
execlp
execv
execvp
execle
execvpe
system函数
int system(const char * command);
功能:执行一个shell命令(shell命令、可执行文件、shell脚本)
system会调用fork函数产生子进程,
子进程调用exec启动/bin/sh ‐c string,
来执行参数string字符串所代表的命令,
此命令执行完后返回原调用进程。
返回值:
如果command为NULL,则system()函数返回非0,一般为1。
如果system()在调用/bin/sh时失败则返回127,其它失败原因返回‐1
僵尸进程
子进程未结束,父进程结束,子进程的资源未被回收
守护进程
创建子进程,终止父进程
在子进程创建新会话
pid_t setsid(void);
改变工作目录
chdir
重设文件创建掩码
umask(0)
关闭文件描述符
for(int i =0; i < NOFILE/MAXFILE, i++)
{
close(i);
}
系统日志文件
openlog
void openlog(char * ident, int option, int facility);
option:
LOG_CONS : 无法写入日志,就终端打印
LOG_PID : 将信息字符串加入产生该信息的PID
facility:
LOG_CRON : 由cron(计时程序) 或 at 程序产生信息
LOG_DAEMON : 由系统daemon产生信息
例:
openlog("该进程",LOG_PID,LOG_DAEMON);
syslog
void syslog(int priority, char * format,...);
priority:
LOG_IFNO : 提示相关信息
LOG_DEBUG :出错相关信息
closelog
void closelog(void);
/*************************************************************************************************/
2)、进程通信
进程间通信功能:
数据传输 :一个进程需要将它的数据发送给另一个进程。
资源共享 :多个进程之间共享同样的资源。
通知事件 :一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
进程控制 :有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程
希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。
系统只要创建一个进程,就会给当前进程分配4G的虚拟内存(32位操作系统)。
4G的虚拟内存分为3G的用户空间(0~3G)和1G(3~4G)的内核空间。
用户空间是进程所私有的,每一个进程的用户空间只能自己访问和使用,我们之前说的栈
区、堆区、数据区、代码区等都是用户空间的区域。
内核空间是所有进程所公有的,也就意味着绝大多数进程间通信方式,本质就是对内核空间
的操作。
特殊的进程间通信方式:
socket通信可以实现不同主机的进程间通信,其他六个只能在一台主机的多个进程间通信。
信号通信是唯一的一种异步通信机制。
共享内存是所有进程间通信方式中效率最高的,他是直接对物理内存进行操作。
/*************************************************************************************************/
(1)、信号
信号是软件中断,它是在软件层次上对中断机制的一种模拟。信号是一种异步通信方式。
在Linux下,要想查看这些信号和编码的对应关系,可使用命令:kill -l
当进程中产生了一个信号,就会让当前进程做出一定的反应,
默认处理进程的方式如下
1、终止进程:当信号产生后,当前进程就会立即结束
2、缺省处理:当信号产生后,当前进程不做任何处理
3、停止进程:当信号产生后,使得当前进程停止
4、让停止的进程回复运行:当信号产生后,停止的进程会回复执行(后台进程)
注意:每一个信号只有一个默认的处理方式
进程接收到信号后的处理方式
1、执行系统默认动作
对大多数信号来说,系统默认动作是用来终止该进程。
2、忽略此信号
接收到此信号后没有任何动作。
3、执行自定义信号处理函数
用 用户定义 的信号处理函数处理该信号。
注意:SIGKILL和SIGSTOP这两个信号只能以默认的处理方式执行,不能忽略也不能自定义
int kill(pid_t pid, int sig);
功能:给指定的进程或者进程组发送信号
参数:
pid:
pid>0: 将信号传送给进程ID为pid的进程。
pid=0: 将信号传送给当前进程所在进程组中的所有进程。
pid=‐1: 将信号传送给系统内所有的进程,除了init进程
pid<‐1: 将信号传给指定进程组的所有进程。这个进程组号等于pid的绝对值。
sig:指定的信号
例:kill(getppid(), SIGINT);
unsigned int alarm(unsigned int seconds);
功能:定时器,闹钟,当设定的时间到达时,会产生SIGALRM信号
返回值:
如果alarm函数之前没有alarm设置,则返回0
如果有,则返回上一个alarm剩余的时间
int raise(int sig);
功能:给调用进程本身发送信号
void abort(void);
功能:向进程发送一个SIGABRT信号,默认情况下进程会退出。
注意:
即使SIGABRT信号被加入阻塞集,一旦进程调用了abort函数,进程也还是会被终止,
且在终止前会刷新缓冲区,关闭文件描述符。
int pause(void);
功能:阻塞等待一个信号的产生
例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程的代码区
{
printf("This is parent peocess\n");
//使用pause阻塞等待捕捉信号
pause();
}
else //子进程的代码区
{
printf("This is son process\n");
sleep(3);
kill(getppid(), SIGINT);
}
return 0;
}
void (* signal(int sig, void (* func)(int sig)))(int);
简化:
typedef void (* sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:当进程中产生某一个信号时,对当前信号进行用户函数处理
参数:
sig:指定要处理的信号
handler:处理方式
SIG_IGN 当信号产生时,以缺省(忽略)的方式处理
SIG_DFL 当信号产生时,以当前信号默认的方式处理
void handler(int sig):
当信号产生时,通过信号处理函数自定义方式处理,函数名可以随便写,参数表示当前的信号
返回值:
成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址
失败:SIG_ERR
例:
1)、以默认的方式处理信号
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
2)、以忽略的方式来处理信号
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
注意:SIGKILL和SIGSTOP这两个信号只能以默认的方式处理,不能忽略或者捕捉
3)、以用户自定义方式处理信号
void handler(int sig)
{
...
}
signal(SIGINT, handler);
//该语句并不调用handler,而是等Ctrl+C等其他进程产生后产生了SIGINT后立即执行(与中断机制一样)
.
.
.
可重入函数(????)
可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误
可重入函数就是可以被中断的函数,当前函数可以在任何时刻中断它,并执行另一块代码,
当执行完毕后,回到原本的代码还可以正常继续运行
编写可重入函数:
1、不使用(返回)静态的数据、全局变量(除非用信号量互斥)。
2、不调用动态内存分配、释放的函数。
3、不调用任何不可重入的函数(如标准I/O函数)。
注意:
即使信号处理函数使用的都是可重入函数(常见的可重入函数),也要注意进入处理函
数时,首先要保存errno的值,结束时,再恢复原值。因为,信号处理过程中,errno值随
时可能被改变。
信号集
一个用户进程常常需要对多个信号做出处理。为了方便对多个信号进行处理,在Linux
系统中引入了信号集。
信号集是用来表示多个信号的数据类型。
信号集数据类型 :sigset_t
int sigemptyset(sigset_t * set);
初始化由set指向的信号集,清除其中所有的信号,即初始化一个空信号集。
int sigfillset(sigset_t *set);
初始化信号集合set, 将信号集合设置为所有信号的集合。
int sigaddset(sigset_t *set, int signum);
将信号signum加入到信号集合set之中。
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
返回值:在信号集中返回 1,不在信号集中返回 0。
信号阻塞集
有时既不希望进程在接受信号后立即中断进程的执行,也不希望其被忽视,而是延时一段时间
去调用信号处理函数,需阻塞信号。
所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集
中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:检查或修改信号阻塞集,根据how指定的方法对进程的阻塞集合进行修改,新的信号
阻塞集由set指定,而原先的信号阻塞集合由oldset保存。
how:信号阻塞集合的修改方法。
SIG_BLOCK:向信号阻塞集合中添加set信号集
SIG_UNBLOCK:从信号阻塞集合中删除set集合
SIG_SETMASK:将信号阻塞集合设为set集合
注意:若set为NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset中。
int main(int argc, char *argv[])
{
int i=0;
//创建信号集并在信号集中添加信号
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
while(1)
{
//将set信号集添加到信号阻塞集中**********************************
sigprocmask(SIG_BLOCK, &set, NULL);
//在该范围内产生SIGNAL信号不会退出当前进程
for(i=0; i<5; i++)
{
printf("SIGINT signal is blocked\n");
sleep(1);
}
//将set信号集从信号阻塞集中删除**********************************
sigprocmask(SIG_UNBLOCK, &set, NULL);
//如果在执行该条语句前有SIGNAL信号,执行该条语句后立即退出,否则,继续该进程
for(i=0; i<5; i++)
{
printf("SIGINT signal unblocked\n");
sleep(1);
}
}
return 0;
}
/*************************************************************************************************/
(2)、无名/有名管道
无名管道(pipe)
无名管道是一种特殊类型的文件,在应用层体现为两个打开的文件描述符。
任何一个进程在创建的时候,系统都会 给他分配4G的虚拟内存,分为3G的用户空间和1G
的内核空间,内核空间是所有进程公有的,无名管道就是创建在内核空间的,多个进程知道
同一个无名管道的空间,就可以利用他来进行通信
无名管道虽然是在内核空间创建的,但是会给当前用户进程两个文件描述符,一个负责执行
读操作,一个负责执行写操作
管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格
式,如多少字节算一个消息等。
管道不是普通的文件,不属于某个文件系统,其只存在于内存中,在应用层体现为两个打开的文件描述符。
管道没有名字,只能在具有公共祖先的进程之间使用
从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写
更多的数据。
int pipe(int pipfd[2]);
pipefd[0] : 读操作
pipefd[1] : 写操作
//如果管道中没有数据,则读操作会阻塞等待,直到有数据为止
ssize_t read(int fd, void *buf, size_t count);
//如果一直执行写操作,则无名管道对应的缓冲区会被写满,写满之后,write函数也会阻塞等待
//默认无名管道的缓冲区64K字节
ssize_t write(int fd, const void *buf, size_t count);
利用无名管道实现进程间的通信,都是父进程创建无名管道,然后再创建子进程,子进程继承
父进程的无名管道的文件描述符,然后父子进程通过读写无名管道实现通信。
SIGPIPE : 管道破裂信号
void handler(int sig)
{
printf("SIGPIPE信号产生了,管道破裂了\n");
}
signal(SIGPIPE, handler);
只有读端,没有写端
//关闭写文件描述符,只有读端
//如果原本管道中有数据,则读操作正常读取数据
//如果管道中没有数据,则read函数会返回0
只有写端,没有读端
//关闭写操作文件描述符,只有写端
//如果关闭读端,一旦执行写操作,就会产生一个信号SIGPIPE(管道破裂),
//这个信号的默认处理方式是退出进程
文件控制:
int fcntl(int fd, int cmd, ... /* arg */ );
设置为阻塞:
fcntl(fd, F_SETFL, 0);
如果是阻塞,管道中没有数据,read会一直等待,直到有数据才会继续运行,否则一直等待
设置为非阻塞:
fcntl(fd, F_SETFL, O_NONBLOCK);
如果是非阻塞,read函数运行时,会先看一下管道中是否有数据,如果有数据,则正
常运行读取数据,如果管道中没有数据,则read函数会立即返回,继续下面的代码运行
有名管道/命名管道(FIFO)
FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可以实现不相
关进程间通信,但FIFO中的内容却存放在内存中。
当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
FIFO有名字,不相关的进程可以通过打开命名管道进行通信。
int mkfifo(const char * pathname, mode_t mode);
mode:管道文件的权限,一般通过八进制数设置即可,例如0664。
如果命名管道中没有数据,则读操作会阻塞等待,直到有数据为止。
注意:由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道
进行操作,但是不能使用lseek修改管道文件的偏移量。有名管道创建的本地的文件只是起到标识
作用,真正有名管道实现进程间通信还是在内核空间开辟内存,所以本地产生的文件只是一个标
识,没有其他作用,对本地管道文件的操作实质就是对内核空间的操作。
有名管道可以实现进程间通信。
有名管道的读写规律(阻塞)
读写端都存在,只读不写
//读写端都存在,只读不写。
//如果原本管道中有数据,则正常读取。
//如果管道中没有数据,则read函数会阻塞等待。
读写端都存在,只写不读
//读写端都存在,只写不读。
//当有名管道的缓冲区写满后,write函数会发生阻塞。
//默认有名管道的缓冲区为64K字节。
在一个进程中,只有读端,没有写端
//在一个进程中,只有读端,没有写端。
//会在open函数的位置阻塞。
在一个进程中,只有写端,没有读端
//在一个进程中,只有写端,没有读端。
//会在open函数的位置阻塞。
一个进程只读,另一个进程只写
规律:
只要保证有名管道的读写端都存在,不管是几个进程,都不会再open这阻塞了。
如果一个进程只读,一个进程只写,都运行后,如果关闭写端,读端read会返回0。
如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生SIGPIPE
信号,默认的处理方式是退出进程
有名管道的读写规律(非阻塞)
//如果open标志位设置为非阻塞,并且以只读的方式打开管道文件
//open函数和read函数都不会阻塞
//如果open标志位设置为非阻塞,并且以只写的方式打开管道文件
//open函数会直接报错
//如果open设置为可读可写,那么跟阻塞是一样的效果
/*************************************************************************************************/
(3)、消息队列
IPC对象分类:消息队列、共享内存、信号量。
IPC对象也是在内核空间开辟区域,每一种IPC对象创建好之后都会将其设置为全局,并且会给其分
配一个编号,只要找到唯一的这个编号就可以进行通信,所以不相关的进程可以通过IPC对象通信。
ipcs 查看当前系统中所有创建的IPC对象
ipcs ‐q 查看创建的消息队列 ipcrm ‐q msqid 删除消息队列
ipcs ‐m 查看创建的共享内存 ipcrm ‐m shmid 删除共享内存
ipcs ‐s 查看信号量
消息队列的特点:
1、消息队列中的消息是有类型的。
2、消息队列中的消息是有格式的。
3、消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以
按消息的类型读取。
4、消息队列允许一个或多个进程向它写入或者读取消息。
5、与无名管道、命名管道一样,从消息队列中读出消息,消息队列中对应的数据都会被删除。
6、每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的。
7、只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,
消息队列会一直存在于系统中。
key_t ftok(const char * pathname, int proj_id);
功能:通过文件名和目标值共同创造一个键值并返回值。
proj_id:目标值,范围一般是0~127
只要保证ftok的第一个参数对应的文件和第二个参数值相同,则不管程序运行多少遍或者多少个
进程或者键值,键值一定都是唯一的。
int/msqid msgget(key_t key, int msgflg);
功能:创建一个消息队列,得到消息队列的id(msqid)。
唯一的键值确定唯一的消息队列。
msgflg:消息队列的访问权限,一般设置为 IPC_CREAT | IPC_EXCL | 0777
或者 IPC_CREAT | 0777。
int msgsnd(int msqid, const void * msgq, size_t msgsz, int msgflg);
msqid:消息队列的id。
msgp:要写入的数据,需要自己定义结构体。
struct struct_name
{
long mtype; // 消息的编号/message type,必须大于0
char mtext[128]; // 消息正文,可以定义多个成员
...
}MSG;
msgsz:消息正文的大小,不包括消息的编号长度 (sizeof(MSG) ‐ sizeof(long))* N。
msgflg:标志位 --> 0 阻塞 / IPC_NOWAIT 非阻塞。
ssize_t msgrcv(int msqid, void *msgq, size_t msgsz, long msgtyp, int msgflg);
msgtyp:设置要接收哪个消息。
0 按照写入消息队列的顺序依次读取
>0 只读取消息队列中消息编号为当前参数的第一个消息
<0 只读取消息队列中小于等于当前参数的绝对值内最小的第一个消息
int msgctl(int msqid, int cmd, struct msqid_ds * buf );
功能:设置或者获取消息队列的信息
cmd : IPC_SET 设置属性 、 IPC_STAT 获取属性 、 IPC_RMID 删除消息队列
msqid_ds:设置或者获取消息队列的属性,NULL
例:
msgctl(msgid, IPC_RMID, NULL);
/*************************************************************************************************/
(4)、共享内存
共享内存是进程间共享数据的一种最快的方法。
使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥。
int shmget(key_t key, int size, int shmflg);
shmflg:共享内存的访问权限, IPC_CREAT | 0777
void * shmat(int shmid, const void * shmaddr, int shmflg);
功能:映射共享内存(attach)。
shmflg:标志位 --> 0:共享内存具有可读可写权限 SHM_RDONLY:只读。
shmaddr:映射的地址,设置为NULL为系统自动分配。
成功:返回映射的地址
例:
struct struct_name
{
int a;
char b;
...
}SHM;
SHM * shm;
shm = (SHM *)shmat(shmdi, NULL,0);
void * mmap(void * start, size_t len, int prot, int flag, int fd, off_t offsize);//不用健值
功能:映射共享内存。
prot:映射区的保护方式
PROT_EXEC:映射区可执行
PROT_READ
PROT_WRITE
PROT_NONE:映射区不能存取
flag:MAP_SHARED(建议)
MAP_FIXED
成功:返回映射的地址
例:
//匿名内存映射
shm = (SHM *)mmap(NULL, sizeof(SHM), PROT_READ | PROT_WRITE, MAP_SHARED, -1, 0);
int shmdt(const void * shaddr);
功能:解除共享内存的映射(detach)。
shmaddr:映射的地址,shmat的返回值。
int shmctl(int shmid, int cmd, shmid_ds *buf);
例:
shmctl(shmid, IPC_RMID, NULL);通过shmctl函数删除共享内存。
int munmmap(void * start, size_t size);
例:
munmmap(shm, sizeof(SHM));
/*************************************************************************************************/
2、线程
线程依赖进程存在的,如果创建线程的进程结束了,线程也就结束了。
线程函数的程序在pthread库中,故链接时要加上参数-lpthread。
/*************************************************************************************************/
1)、线程创建
获取自己线程ID:
pthread_t pthread_self(void);
创建线程:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\
void *(*start_routine)(void *),void *arg);
thread : 线程id
attr : 线程的属性,设置为NULL表示以默认的属性创建
线程等待:
int pthread_join(pthread_t thread, void ** retval);
retval:保存子线程的退出状态值,如果不接受则设置为NULL
线程分离:
int pthread_detach(pthread_t thread);
功能:使调用线程与当前进程分离,使其成为一个独立的线程,该线程终止时,
系统将自动回收它的资源。
如果原本子线程是结合态,需要通过pthrad_join函数回收子线程退出的资源,
但是这个函数是一个阻塞函数,如果子线程不退出,就会导致当前进程(主控线程)
无法继续执行,大大的限制了代码的运行效率,如果子线程已经设置为分离态,
就不需要再使用pthread_join了。
主控线程exit,其也退出。
线程退出:
void pthread_exit(void * retval);
retval:当前线程的退出状态值,
一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并
不会释放。如果要释放资源,结合态需要通过pthread_join函数,分离态则自动释放。
线程取消:
int pthread_cancel(pthread_t thread);
pthread_cancel函数的实质是发信号给目标线程thread,使目标线程退出。此函数只
是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。
然而发送成功并不意味着目标线程一定就会终止,线程被取消时,线程的取消属性会
决定线程能否被取消以及何时被取消。
线程的取消状态
即线程能不能被取消
int pthread_setcancelstate(int state, int * oldstate);
state:新的状态
PTHREAD_CANCEL_DISABLE:不可以被取消
PTHREAD_CANCEL_ENABLE:可以被取消
线程取消点
即线程被取消的地方
void pthread_testcancel(void);
线程的取消类型
在线程能被取消的状态下,是立马被取消结束还是执行到取消点的时候被取消结束
int pthread_setcanceltype(int type, int * oldstate);
type:类型
PTHREAD_CANCEL_ASYNCHRONOUS:立即取消、asynchronous
PTHREAD_CANCEL_DEFERRED:不立即被取消
线程退出清理函数:
注意:线程可以建立多个清理处理程序。
处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反。
当线程执行以下动作时会调用清理函数:
1、调用pthread_exit退出线程。
2、响应其它线程的取消请求。
3、用非零execute调用pthread_cleanup_pop。
void pthread_clearup_push(void (* routine)(void *),void * arg);
功能:将清除函数压栈。即注册清理函数。
routine:例行程序
void pthread_clearup_pop(int execute);
execute:线程清理函数执行标志位。
非0,弹出清理函数,执行清理函数。
0,弹出清理函数,不执行清理函数。
/*注意push与pop必须配对使用,即使pop执行不到*/
/*************************************************************************************************/
2)、线程同步与互斥
互斥:
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共
资源。POSIX标准中进程和线程同步和互斥的方法,主要有信号量和互斥锁两种方式。
同步:
两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。同步就是
在互斥的基础上有顺序
互斥锁
mutex是一种简单的加锁的方法来控制对共享资源的访问,mutex只有两种状态,即上锁
(lock)和解锁(unlock)。如果mutex处于lock状态,则默认阻塞申请者。
动态分配互斥锁:
int pthread_mutex_init(pthread_mutex_t *mutex, \
const pthread_mutexattr_t * mutexattr);
mutexattr:互斥锁的属性,为NULL表示默认属。
互斥锁上锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:对互斥锁上锁,若已经上锁,则调用者一直阻塞到互斥锁解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:对互斥锁上锁,若已经上锁,则上锁失败,函数立即返回。
互斥锁解锁:
int pthread_mutex_unlock(pthread_mutex_t * mutex);
销毁互斥锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
条件变量
能弥补互斥锁的不足(只有 锁定 和 非锁定 ),常与互斥锁一起用。
使用时,条件变量用来阻塞一个线程,当条件不满足时,线程解开相应的互斥锁并等待条件满足,
一旦某个线程改变了条件变量,他将唤醒被一个或多个正被此条件变量阻塞的线程,这些线程将重
新互斥锁并重新测试条件是否满足。
可以用来线程间同步。
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr);
例:
pthread_cond_init(&cond, NULL);
销毁
int pthread_cond_destroy(pthread_cond_t * cond);
阻塞一个条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t * mutex);
使线程阻塞一段指定时间
int pthread_cond_timewait(pthread_cond_t * cond, pthread_mutex_t * mutex, \
const struct timeespec * abstime);
例:
struct timeval now;
struct timespec outtime;
gettimeofday(&now,NULL);
outtime.tv_sec = now.tv_sec + 5;
outtime.tv_nsec = now.tv_usec * 100;
pthread_cond_timewait(&cond, &mutex, &outtime);
根据调度策略唤醒线程
int pthread_cond_signal(pthread_cond_t * cond);
/*************************************************************************************************/
信号量
信号量广泛用于进程或线程间的同步和互斥。
信号量又称之为PV操作,PV原语是对信号量的操作,一次P操作使信号量sem减1,一次
V操作使信号量sem加1,对于P操作,如果信号量的sem值为小于等于0,则P操作就会阻塞,
如果信号量的值大于0,才可以执行P操作进行减1。
信号量主要用于进程或线程间的同步和互斥这两种典型情况。
1、若用于互斥,几个进程(或线程)往往只设置一个信号量。
2、若用于同步操作,往往会设置多个信号量,并且安排不同的初始值,来实现它们之间的
执行顺序。
int sem_init(sem_t * sem, int pshared, unsigned int value);
pshared:是否在线程间或者进程间共享。
0 线程间共享
1 进程间共享
value:信号量的初始值
信号量P操作:
int sem_wait(sem_t * sem);
功能:将信号量的值减1,若信号量的值小于等于0,此函数会引起调用者阻塞.
int sem_trywait(sem_t * sem);
功能:将信号量的值减1,若信号量的值小于0,则对信号量的操作失败,函数立即返回。
信号量V操作:
int sem_post(sem_t * sem);
功能:执行V操作,执行一次,信号量的值加1
获得信号量的计数值
int sem_getvalue(sem_t *sem, int *sval);
信号量的销毁
int sem_destroy(sem_t *sem);
06-24
5745