进程的创建与回收
1 进程概念
1.程序和进程
程序是静态的,存放在磁盘上静态的文件
进程是动态的,执行一个程序所要申请的资源
2.进程包含内容
进程包括:
代码段:存放代码
数据段:初始化的全局变量区
BBS段:未初始化的全局变量
堆:malloc分配
栈:局部变量、函数入参、函数返回值
进程控制块:
进程控制块(pcb)
进程标识PID
进程用户
进程状态、优先级
文件描述符表
3进程状态
运行态 等待态 停止态 死亡态
2 进程常用命令
ps 查看进程信息(静态查看)
ps 命令详细参数:
-e:显示所有进程
-l:长格式显示更加详细的信息
-f 全部列出,通常和其他选项联用
ps -elf | grep
F 进程标志,说明进程的权限,常见的标志有两个:
1:进程可以被复制,但是不能被执行;
4:进程使用超级用户权限;
S 进程状态。进程状态。常见的状态有以下几种:
1.-D:不可被唤醒的睡眠状态,通常用于 I/O 情况。
2.-R:该进程正在运行。
3.-S:该进程处于睡眠状态,可被唤醒。
4.-T:停止状态,可能是在后台暂停或进程处于除错状态。
5.-W:内存交互状态(从 2.6 内核开始无效)。
6.-X:死掉的进程(应该不会出现)。
7.-Z:僵尸进程。进程已经中止,但是部分程序还在内存当中。
8.-<:高优先级(以下状态在 BSD 格式中出现)。
9.-N:低优先级。
10.-L:被锁入内存。
11.-s:包含子进程。
12.-l:多线程(小写 L)。
13.-+:位于后台。
UID 运行此进程的用户的 ID;
PID 进程的 ID;
PPID 父进程的 ID;
C 该进程的 CPU 使用率,单位是百分比;
PRI 进程的优先级,数值越小,该进程的优先级越高,越早被 CPU 执行;
NI 进程的优先级,数值越小,该进程越早被执行;
ADDR 该进程在内存的哪个位置;
SZ 该进程占用多大内存;
WCHAN 该进程是否运行。"-"代表正在运行;
TTY 该进程由哪个终端产生;
TIME 该进程占用 CPU 的运算时间,注意不是系统时间;
CMD 产生此进程的命令名;
top命令 动态查看
top | grep
nice 指定优先级启动进程
nice [-n] 进程名
renice [优先级] PID 改变正在运行状态进程的优先级
jobs查看后台进程
bg [进程号] 挂起进程在后台运行
fg [进程号] 后台程序放到前台
创建子进程
创建:
#include<unistd.h>
pid_t fok(void);
1.fork创建子进程,子进程只是复制父进程代码,但是只执行fork之后的代码。
可以根据pid来使得父子进程执行不同代码。
2.父子进程执行
父子进程关系:
父进程和子进程有相互独立空间,互不影响
如果父进程先结束,则子进程被init进程收养,转为后台进程
如果子进程结束,父进程没有及时回收,子进程变为僵尸进程
创建多个子进程:
如果不退出for循环,子进程和父进程都会执行for循环
//创建的时候,子进程和父进程都会进入for循环,因此判断pid,父进程就继续for循环,如果是子进程就直接break不在进行for循环了
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 pid_t pid;
6 for(int i=0;i<4;i++){
7 pid=fork();
8 if(pid<0){
9 perror("");
10 }else if(pid==0){//子进程就直接退出
11 printf("child\n");
12 break;
13 }else{
14 printf("father\n");
15 }
16
17
18
19 }
20
21 }
进程退出:
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区
return 和exit的区别
main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。
进程的回收
#include <unistd.h>
pid_t wait(int *status);
成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值
子进程通过exit / _exit / return 返回某个值(0-255)
父进程调用wait(&status) 回收
WIFEXITED(status) 判断子进程是否正常结束
WEXITSTATUS(status) 获取子进程返回值
WIFSIGNALED(status) 判断子进程是否被信号结束
WTERMSIG(status) 获取结束子进程的信号类型
#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);
参数:
pid
pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
options
options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用
WNOHANG :若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0
WUNTRACED: 返回终止子进程信息和因信号停止的子进程信息
wait(wait_stat) 等价于waitpid(-1,wait_stat,0)
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 int main(){
7 pid_t pid;
8 int status;
9 pid=fork();
10 if(pid<0){
11 perror("");
12 }else if(pid==0){
13 printf("child exit\n");
14 sleep(1);
15 exit(2);
16 }else{
17 printf("father\n");
18 wait(&status);
19 printf("child status=%d\n",WEXITSTATUS(status));
20 }
21 return 0;
22 }
exec函数族和守护进程
背景:fork创建进程之后,子进程和父进程执行相同的代码,但是在实际开发当中,我们希望父子进程执行不同的代码。
作用:执行指定的程序
#include <unistd.h>
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
成功时执行指定的程序;失败时返回EOF
path 执行的程序名称,包含路径
arg… 传递给执行的程序的参数列表
file 执行的程序的名称,在PATH中查找
**注意:**两个函数区别execlp不需要写文件名全路径,在PATH查找最后一个参数必须用空指针(NULL)作结束
进程当前内容被指定的程序替换,但进程号不变
第0个参数必须要写,虽然它没有使用
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 pid_t pid;
6
7 printf("before execl\n");
8 pid=fork();
9 if(pid==0){
10 if(execl("/bin/ls","ls","-a","-l",NULL)<0){
11 perror("execl");
12 }
13 }
14 printf("after execl\n");
15
16
17 }
创建守护进程
概念:
守护进程又叫精灵进程(Daemon Process),它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
特点:
始终在后台运行,独立于任何终端,周期性的执行某种任务或等待处理特定事件。
它是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。
举例:
http 服务的守护进程叫 httpd,mysql 服务的守护进程叫 mysqld。
更简便地创建守护进程: nohup 命令
nohup xxxx &
setsid函数:
pid_t setsid(void);
成功:返回调用进程的会话ID;失败:-1,设置errno。
调用了setsid函数的进程,既是新的会长,也是新的组长
getsid函数
pid_t getsid(pid_t pid)
成功:返回调用进程的会话ID;失败:-1,设置errno
1.pid为0表示察看当前进程session ID
2.ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
3.组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
getpid:pid_t getpid(void); 获取进程id
getpgid:pid_t getpgid(pid_t pid); 获取进程组id
#include<unistd.h>
3 #include<stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include<time.h>
7
8 int main(){
9 pid_t pid;
10 pid =fork();
11 //1创建子进程,退出父进程
12 if(pid<0){
13 perror("fork");
14 return 0;
15 }else if(pid>0){
16 printf("father exit\n");
17 exit(0);
18 }
19
20 printf("sid=%d pid=%d ppid=%d\n",getsid(getpid()),getpid(),getpgid(getpid()));
//2.子进程创建新会话(成为新组长)
21 if(setsid()<0){
22 perror("setid");
23 exit(0);
24 }
25
26 printf("sid=%d pid=%d ppid=%d\n",getsid(getpid()),getpid(),getpgid(getpid()));
//3.更改当前工作目录
27 chdir("/");
28 //4重设文件权限源码
29 if(umask(0)<0){
30 exit(0);
31 }
32 //5关闭文件描述符
33 for(int i=0;i<3;i++){
34 close(i);
35 }
36
37 printf("after close\n");
38 FILE* fp=NULL;
39 fp=fopen("time.log","a+");
40 if(fp==NULL){
41 perror("fopen");
42 return -1;
43 }
44
45 time_t t;
46 t=time(NULL);
47 struct tm* ctime;
48 ctime=localtime(&t);
49 fseek(fp,0,SEEK_END);
50
51 while(1){
52
53 fprintf(fp,"%d-%d-%d %d:%d:%d\n",ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,
54 ctime->tm_hour,ctime->tm_min,ctime->tm_sec);
55 fflush(fp);
56 sleep(1);
57 }
58 fclose(fp);
59 return 0;
60
61 }
GDB调试多进程程序
set follow-fork-mode child 设置GDB调试子进程
set follow-fork-mode parent 设置GDB调试父进程
set detach-on-fork on/off 设置GDB跟踪调试单个进程或多个
on: 只调试父进程或子进程的其中一个,(根据follow-fork-mode来决定),这是默认的模式
off:父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定),另一个进程会被设置为暂停状态。
info inferiors 显示GDB调试的进程
inferiors 进程序号(1,2,3…) 切换GDB调试的进程
线程
创建线程,回收
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<pthread.h>
5
6 int* threadtest(char *arg){
7
8 printf("this is create thread\n");
9 pthread_exit(NULL);
10 }
11
12
13 int main(){
14 pthread_t tid;
15 int ret;
16 ret=pthread_create(&tid,NULL,(void*)threadtest,NULL);//强制类型转换,不然会有warming
17 printf("this is main thread\n");
18 sleep(1);
19 return 0;
20
21 }
linux下编译需要加上-lpthread动态库,不然会报错
线程参数传递
1.地址传递,不会产生warming,如果主线程没有延迟,子线程可能执行滞后
2.值传递,可能存在warming,但是传递不超过地址范围不会造成数据丢失,效率更高
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<pthread.h>
5
6 int* threadtest(void *arg){
7
8 //printf("this is %d thread,tid=%lu\n",(int*)arg,pthread_self());值传递
9 printf("this is %d thread,tid=%lu\n",*(int*)arg,pthread_self());//地址传递
10 pthread_exit(NULL);
11
12 }
13
14
15 int main(){
16 pthread_t tid[5];
17 int ret;
18 int i;
19 for(int i=0;i<5;i++){
20 //ret=pthread_create(&tid[i],NULL,(void*)threadtest,(void*)i);
21 ret=pthread_create(&tid[i],NULL,(void*)threadtest,(void*)&i);
22 printf("this is main thread\n");
23 }
24 sleep(1);
25 return 0;
26
27 }
线程的回收与分离
回收
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5
6 void* fun(void* agrv){
7
8 printf("this is child\n");
9 // pthread_exit(NULL);
10 sleep(5);
11 }
12
13 int main(){
14 pthread_t tid[5];
15 int i=0;
16 void*retv;
17 for(int i=0;i<5;i++){
18 pthread_create(&tid[i],NULL,fun,NULL);
19 }
20 for(i=0;i<5;i++){
21
22 pthread_join(tid[i],&retv);//阻塞函数,可能会造成第一个线程未结束,后面几个线程结束了只能等待,成为僵尸进程
23 printf("child return\n");
24 }
25 while(1){
26
27 sleep(1);
28 }
29
30
31
32
33 }
~
分离:将子线程与主线程分离,那么子线程结束之后就自动回收,不会成为僵尸线程
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5
6 void* fun(void* agrv){
7 pthread_detach(pthread_self());
8 printf("this is child\n");
9 // pthread_exit(NULL);
10 sleep(5);
11 }
12
13 int main(){
14 pthread_t tid[5];
15 int i=0;
16 void*retv;
17 for(int i=0;i<5;i++){
18 pthread_create(&tid[i],NULL,fun,NULL);
19 }
20 for(i=0;i<5;i++){
21
22 // pthread_join(tid[i],&retv);
23 //pthread_detach(tid[i]);
24 printf("child return\n");
25 }
26 while(1){
27
28 sleep(1);
29 }
30
31
32
33
34 }
设置线程属性为分离
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
线程的取消和互斥
意义:随时杀掉一个线程
int pthread_cancel(pthread_t thread);
注意:线程的取消要有取消点才可以,不是说取消就取消,线程的取消点主要是阻塞的系统调用
运行段错误调试:
可以使用gdb调试
使用gdb 运行代码,gdb ./youapp
(gdb) run
等待出现Thread 1 “pcancel” received signal SIGSEGV, Segmentation fault.
输入命令bt(打印调用栈)
(gdb) bt
#0 0x00007ffff783ecd0 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x00007ffff78458a9 in printf () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x00000000004007f9 in main () at pcancel.c:21
确定段错误位置是pcancel.c 21行
如果没有取消点,手动设置一个
void pthread_testcancel(void);
设置取消使能或禁止
int pthread_setcancelstate(int state, int *oldstate);
PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE
设置取消类型
int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED 等到取消点才取消
PTHREAD_CANCEL_ASYNCHRONOUS 目标线程会立即取消
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5 void* func(void* argv){
6
7 printf("this is child\n");
8 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//禁止取消
9 //while(1){
10 sleep(5);//要有取消点才能取消
11 pthread_testcancel();//没有取消点自己设置一个
12 // }
13 // pthread_exit("return exit");
14 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//可以取消
15 }
16
17
18 int main(){
19
20 pthread_t tid;
21 void* retv;
22 pthread_create(&tid,NULL,func,NULL);
23 sleep(5);
24 pthread_cancel(tid);
25 pthread_join(tid,&retv);
26 // printf("thread ret=%s\n",(char*)retv);
27 printf("exit\n");
28 while(1){
29
30 sleep(1);
31
32 }
33
34
35 }
线程的清理
必要性:适用于线程结束了,但是分配的内存还没清理情况
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
routine 函数被执行的条件:
1.被pthread_cancel取消掉。
2.执行pthread_exit
3.非0参数执行pthread_cleanup_pop()
注意:
1.必须成对使用,即使pthread_cleanup_pop不会被执行到也必须写上,否则编译错误。2.pthread_cleanup_pop()被执行且参数为0,pthread_cleanup_push回调函数routine不会被执行.
3 pthread_cleanup_push 和pthread_cleanup_pop可以写多对,routine执行顺序正好相反
4.线程内的return 可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4
5
6 void cleanup(void* arg){
7
8 printf("clean up,rag=%s\n",(char*)arg);
9 }
10
11 void* func(void* argv){
12 printf("this is child\n");
13
14 pthread_cleanup_push(cleanup,"1111");
15 //while(1)
16 {
17 sleep(1);
18 }
19
20 // pthread_testcancel();
21 // pthread_exit("exit");
22 pthread_cleanup_pop(1);
23 pthread_exit("exit");
24
25 }
26
27
28 int main(){
29 pthread_t tid;
30 void* retv;
31
32 pthread_create(&tid,NULL,func,NULL);
33 sleep(2);
34 //pthread_cancel(tid);
35 pthread_join(tid,&retv);
36
37 // printf("ret=%s\n",(char*)retv);
38 while(1){
39
40 sleep(1);
41 }
42
43 }
互斥锁
互斥锁的创建和销毁
两种方法创建互斥锁,静态方式和动态方式
动态方式:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
其中mutexattr用于指定互斥锁属性,如果为NULL则使用缺省属性。
静态方式:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
锁的销毁:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
互斥锁的使用:
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
vim 设置代码全文格式化:gg=G
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<string.h>
4 #include<string.h>
5 #include<unistd.h>
6
7 pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
8
9 FILE* fp;
10 void* func1(void* arg){
11 pthread_detach(pthread_self());
12 char str[]="this is func1 write\n";
13 char c;
14 int i=0;
15 while(1){
16 pthread_mutex_lock(&mutex);
17 for( i=0;i<strlen(str);i++){
18 c=str[i];
19 fputc(c,fp);
20 }
21 pthread_mutex_unlock(&mutex);
22 usleep(1);
23 i=0;
24 }
25
26 }
27
28 void* func2(void* arg){
29
30 pthread_detach(pthread_self());
31 char str[]="that is func2 read\n";
32 char c;
33 int i=0;
34 while(1){
35 pthread_mutex_lock(&mutex)
36 for( i=0;i<strlen(str);i++){
37 c=str[i];
38 fputc(c,fp);
39 }
40 pthread_mutex_unlock(&mutex);
41 i=0;
42 usleep(1);
43 }
44
45 }
46
47
48 int main(){
49 pthread_t tid1,tid2;
50 fp=fopen("1.txt","a+");
51 if(fp==NULL){
52 perror("fopen");
53 return -1;
54 }
55 pthread_create(&tid1,NULL,func1,NULL);
56 pthread_create(&tid2,NULL,func2,NULL);
57
58 pthread_join(tid1,NULL);
59 pthread_join(tid2,NULL);
60
61 while(1){
62
63 sleep(1);
64 }
65 fclose(fp);
66 return 0;
67
68 }
读写锁
必要性:提高线程执行效率
特性:
写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。
读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。
注意:
同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。
读写锁出于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。
读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁
初始化一个读写锁 pthread_rwlock_init
读锁定读写锁 pthread_rwlock_rdlock
非阻塞读锁定 pthread_rwlock_tryrdlock
写锁定读写锁 pthread_rwlock_wrlock
非阻塞写锁定 pthread_rwlock_trywrlock
解锁读写锁 pthread_rwlock_unlock
释放读写锁 pthread_rwlock_destroy
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<string.h>
4 #include<string.h>
5 #include<unistd.h>
6 #include<stdlib.h>
7 pthread_rwlock_t rwlock;
8 FILE* fp;
9
10 void* read_fun(void* arg){
11 pthread_detach(pthread_self());
12 char data[32]={0};
13 while(1){
14 // rewind(fp);
15 pthread_rwlock_rdlock(&rwlock);
16 while(fgets(data,32,fp)!=NULL){
17 printf("%d ret=%s\n",(int)arg,data);
18 usleep(1000);
19 }
20 pthread_rwlock_unlock(&rwlock);
21 memset(data,0,sizeof(data));
22
23 }
24
25 }
26 void* func1(void* arg){
27 pthread_detach(pthread_self());
28 char str[]="this is func1 write\n";
29 char c;
30 int i=0;
31 while(1){
32 pthread_rwlock_wrlock(&rwlock);
33 for( i=0;i<strlen(str);i++){
34 c=str[i];
35 fputc(c,fp);
36 }
37 pthread_rwlock_unlock(&rwlock);
38 usleep(1);
39 }
40 pthread_exit("f1 exit");
41
42 }
43
44 void* func2(void* arg){
45
46 pthread_detach(pthread_self());
47 char str[]="that is func2 read\n";
48 char c;
49 int i=0;
50 while(1){
51 pthread_rwlock_wrlock(&rwlock);
52 for( i=0;i<strlen(str);i++){
53 c=str[i];
54 fputc(c,fp);
55
56 }
57 pthread_rwlock_unlock(&rwlock);
58 usleep(1);
59 }
60 pthread_exit("f2 exit");
61 }
62
63
64 int main(){
65 pthread_t tid1,tid2,tid3,tid4;
66 void* retv;
67 fp=fopen("1.txt","a+");
68 if(fp==NULL){
69 perror("fopen");
70 return -1;
71 }
72
73 pthread_rwlock_init(&rwlock,NULL);
74
75
76 pthread_create(&tid3,NULL,read_fun,3);
77 pthread_create(&tid4,NULL,read_fun,4);
78
79
80 pthread_create(&tid1,NULL,func1,NULL);
81 pthread_create(&tid2,NULL,func2,NULL);
82
83 pthread_join(tid1,&retv);
84 pthread_join(tid2,&retv);
85
86 while(1){
87
88 sleep(1);
89 }
90 fclose(fp);
91 return 0;
92
93 }
死锁
概念:
避免方法:
1.锁越少越好,最好使用一把锁
2.调整好锁的顺序
3.两个线程获得锁的时间顺序调整
条件变量和线程池
条件变量
应用场景:生产者消费者问题,是线程同步的一种手段。
必要性:为了实现等待某个资源,让线程休眠。提高运行效率
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
使用步骤:
初始化:
静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //初始化互斥量
或使用动态初始化
pthread_cond_init(&cond);
生产资源线程:
pthread_mutex_lock(&mutex);
开始产生资源
pthread_cond_sigal(&cond); //通知一个消费线程
或者
pthread_cond_broadcast(&cond); //广播通知多个消费线程
pthread_mutex_unlock(&mutex);
消费者线程:
pthread_mutex_lock(&mutex);
while (如果没有资源){ //防止惊群效应
pthread_cond_wait(&cond, &mutex);
}
有资源了,消费资源
pthread_mutex_unlock(&mutex);
注意:
1 pthread_cond_wait(&cond, &mutex),在没有资源等待是是先unlock 休眠,等资源到了,再lock
所以pthread_cond_wait 和 pthread_mutex_lock 必须配对使用。
2 如果pthread_cond_signal或者pthread_cond_broadcast 早于 pthread_cond_wait ,则有可能会丢失信号。
3 pthead_cond_broadcast 信号会被多个线程收到,这叫线程的惊群效应。所以需要加上判断条件while循环。
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5
6 pthread_cond_t hastax=PTHREAD_COND_INITIALIZER;
7 pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
8 struct taxi{
9 int num;
10 struct taxi* next;
11 };
12 typedef struct taxi* tax;
13 struct taxi* head=NULL;
14
15
16 void*taketax(void* argv){
17 pthread_detach(pthread_self());
18 printf("taxi take thread\n");
19 tax tx;
20 while(1){
21 pthread_mutex_lock(&mutex);
22 while(head==NULL)
23 {
24 pthread_cond_wait(&hastax,&mutex);//阻塞式函数,只有接收到信号才会往下执行,否则一直等待
25 }
26 tx=head;
27 head=tx->next;
28 printf("%d take taxi %d\n",(int)argv,tx->num);
29 free(tx);
30 pthread_mutex_unlock(&mutex);
31
32 }
33 pthread_exit(0);
34
35
36 }
37
38 void* taxarv(void* argv){
39 printf("taxi arrive thread\n");
40 pthread_detach(pthread_self());
41 tax tx;
42 int i=1;
43 while(1){
44 pthread_mutex_lock(&mutex);
45 tx=(tax)malloc(sizeof(struct taxi));
46 tx->num=i++;
47 tx->next=head;
48 head=tx;
49 printf("taxi %d is coiming\n",tx->num);
50 //pthread_cond_signal(&hastax);只发送一个信号,哪个线程抢到信号就执行
51 pthread_cond_broadcast(&hastax);//向所有线程发送信号,注意惊群效应
52 pthread_mutex_unlock(&mutex);
53 sleep(1);
54 }
55 pthread_exit(0);
56
57 }
58
59
60 int main(){
61
62 pthread_t tid1,tid2,tid3;
63 pthread_create(&tid1,NULL,taxarv,NULL);
64 sleep(3);
65
66 pthread_create(&tid2,NULL,taketax,(int)2);
67 pthread_create(&tid3,NULL,taketax,(int)3);
68
69 while(1){
70
71 sleep(1);
72 }
73
74
75 }
线程池
线程池的实现:
1创建线程池的基本结构:
任务队列链表
typedef struct Task;
线程池结构体
typedef struct ThreadPool;
2.线程池的初始化:
pool_init()
{
创建一个线程池结构
实现任务队列互斥锁和条件变量的初始化
创建n个工作线程
}
3.线程池添加任务
pool_add_task
{
判断是否有空闲的工作线程
给任务队列添加一个节点
给工作线程发送信号newtask
}
4.实现工作线程
workThread
{
while(1){
等待newtask任务信号
从任务队列中删除节点
执行任务
}
}
5.线程池的销毁
pool_destory
{
删除任务队列链表所有节点,释放空间
删除所有的互斥锁条件变量
删除线程池,释放空间
}
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4 #include<stdlib.h>
5
6 //任务队列
7 typedef struct Task{
8 void* (*func)(void* argv);
9 void* argv;
10 struct Task* next;
11 }Task;
12
13 #define POOL_NUM 10
//线程池结构体
14 typedef struct ThreadPool{
15 pthread_mutex_t mutex;
16 pthread_cond_t newTask;
17 pthread_t tid[POOL_NUM];
18 Task* task_head;
19 int busy_num;
20 }ThreadPool;
21
22 ThreadPool* pool;
23 void*realwork(void* argv){
24
25 printf("finshed %d\n",(int)argv);
26 }
27 void add_to_task(void* argv){
28 pthread_mutex_lock(&pool->mutex);
29 while(pool->busy_num>=POOL_NUM){
30 pthread_mutex_unlock(&pool->mutex);
31 usleep(1000);
32 pthread_mutex_lock(&pool->mutex);
33 }
34 pthread_mutex_unlock(&pool->mutex);
35 //create new task
36 Task* newTask=(Task*)malloc(sizeof(Task));
37 newTask->func=realwork;
38 newTask->argv=argv;
39 newTask->next=NULL;
40 //lock and newTask insert to queue tail
41 pthread_mutex_lock(&pool->mutex);
42 Task* member;
43 if(pool->task_head==NULL){
44 pool->task_head=newTask;
45 }else{
46 member=pool->task_head;
47 while(member->next){
48 member=member->next;
49 }
50 member->next=newTask;
51 }
52 pool->busy_num++;
53 pthread_cond_signal(&pool->newTask);
54 pthread_mutex_unlock(&pool->mutex);
55 }
56
57 void*workfunc(void* argv){
58
59 while(1){
60 pthread_mutex_lock(&pool->mutex);
61 pthread_cond_wait(&pool->newTask,&pool->mutex);
62 Task *newWork=pool->task_head;
63 pool->task_head=pool->task_head->next;
64
65 pthread_mutex_unlock(&pool->mutex);
66 newWork->func(newWork->argv);
67 pool->busy_num--;
68
69 }
70
71 }
72 int pool_init(void){
73 pool=(ThreadPool*)malloc(sizeof(struct ThreadPool));
74 if(pool==NULL){
75 return -1;
76 }
77 pthread_mutex_init(&pool->mutex,NULL);
78 pthread_cond_init(&pool->newTask,NULL);
79 for(int i=0;i<POOL_NUM;i++){
80
81 pthread_create(&pool->tid[i],NULL,workfunc,NULL);
82 }
83 pool->busy_num=0;
84 pool->task_head=NULL;
85 return 0;
86 }
87 void pool_destroy(void){
88 Task* head;
89 while(pool->task_head){
90 head=pool->task_head;
91 pool->task_head=pool->task_head->next;
92 free(head);
93 }
94 pthread_mutex_destroy(&pool->mutex);
95 pthread_cond_destroy(&pool->newTask);
96
97 free(pool);
98 }
99
100 int main(){
101 pool_init();
102 sleep(3);
103 for(int i=0;i<20;i++){
104 add_to_task(i);
105 }
106 sleep(3);
107
108 pool_destroy();
109 printf("thread pool is destroyed\n");
110 return 0;
111 }
线程的GDB调试:
显示线程
info thread
切换线程
thread id
GDB为特定线程设置断点
break location thread id
GDB设置线程锁,
set scheduler-locking on/off
on:其他线程会暂停。可以单独调试一个线程
进程间通信
有名/无名管道
信号
共享内存
套接字
有名管道
1.只能用于亲缘关系的进程(父子进程,兄弟进程)
2.创建两个文件描述符,pfd[2],pfd[0]用于读,pfd[1]用于写
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4 int main(){
5 int pfd[2];
6 int ret=pipe(pfd);
7 char wbuf[20]={0};
8 char rbuf[20]={0};
9 if(ret<0){
10 perror("pipe");
11 }
12 pid_t pid=fork();
13 if(pid<0){
14
15 perror("fork");
16 }else if(pid==0){
17 while(1){
18 strcpy(wbuf,"hello wrold");
19 write(pfd[1],wbuf,strlen(wbuf));
20 sleep(1);
21 }
22
23 }else{
24 while(1){
25
26 read(pfd[0],rbuf,20);
27 printf("read pipe=%s\n",rbuf);
28 sleep(1);
29 }
30
31 }
32
33
34
35 }
有名管道的读写特性:
①读管道:
-
管道中有数据,read返回实际读到的字节数。
-
管道中无数据:
(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
②写管道:
-
管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
-
管道读端没有全部关闭:
(1) 管道已满,write阻塞。(管道大小64K)
(2)管道未满,write将数据写入,并返回实际写入的字节数。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4 #include<stdlib.h>
5 int main(){
6 int pfd[2];
7 pid_t pid;
8 char buf[40]={0};
9 int ret=pipe(pfd);
10 if(ret<0){
11 return 0;
12 }
13 printf("%d %d",pfd[0],pfd[1]);
14 int i=0;
15 for(i=0;i<2;i++){
16 pid=fork();
17
18 if(pid<0){
19 perror("fork");
20 }else if(pid>0){
21
22 }else{
23
24 break;
25 }
26
27 }
28 //father pid
29 if(i==2){
30 close(pfd[1]);
31 while(1){
32 memset(buf,0,sizeof(buf));
33 int ret=read(pfd[0],buf,40);
34 printf("father read %d ,is %s\n",ret,buf);
35 sleep(1);
36 }
37 return 0;
38 }
39 //seconed pid
40 if(i==1){
41 close(pfd[0]);
42 while(1){
43 memset(buf,0,sizeof(buf));
44 strcpy(buf,"this is 2 process");
45 write(pfd[1],buf,strlen(buf));
46 sleep(2);
47 }
48 return 0;
49 }
50 //first pid
51 if(i==0){
52 close(pfd[0]);
53 while(1){
54 memset(buf,0,sizeof(buf));
55 strcpy(buf,"this is 1 process");
56 write(pfd[1],buf,strlen(buf));
57 sleep(3);
58 }
59 return 0;
60 }
61
62
63
64
65
66
67 }
无名管道
创建管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
open(const char *path, O_RDONLY);//1
open(const char *path, O_RDONLY | O_NONBLOCK);//2
open(const char *path, O_WRONLY);//3
open(const char *path, O_WRONLY | O_NONBLOCK);//4
**概述:**无名管道适用于没有亲缘关系的进程,以文件作为两个进程相互通信的基础,对文件进行读写,但是和文件io不同的是,文件打开的方式只能是只读或者只写,否则会产生错误。
特点:
1有名管道可以使非亲缘的两个进程互相通信
2通过路径名来操作,在文件系统中可见,但内容存放在内存中
3 文件IO来操作有名管道
4 遵循先进先出规则
5 不支持leek操作
6 单工读写
注意事项:
1 就是程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程可以读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递
2 第二个参数中的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的
3 对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。
4.数据完整性,如果有多个进程写同一个管道,使用O_WRONLY方式打开管道,如果写入的数据长度小于等于PIPE_BUF(4K),那么或者写入全部字节,或者一个字节都不写入,系统就可以确保数据决不会交错在一起。
fifor.c
#include <string.h>
int main(){
int re;
int fd;
char buf[32];
fd = open("/myfifo",O_RDONLY);
if(fd<0){
perror("open");
return 0;
}
printf("after open\n");
while(1){
re=read(fd,buf,32);
if(re>0){
printf("read fifo=%s\n",buf);
}else if(re==0){
exit(0);
}
}
}
fifow.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
int main(){
int re;
int fd;
char buf[32];
re = mkfifo("/myfifo",0666);
if(re<0){
perror("mkfifo");
//return 0;
}
fd = open("/myfifo",O_WRONLY|O_NONBLOCK);
if(fd<0){
perror("open");
return 0;
}
printf("after open\n");
while(1){
fgets(buf,32,stdin);
write(fd,buf,strlen(buf));
}
}
共享内存
概念:
使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。
mmap()的优点:
实现了用户空间和内核空间的高效交互方式
函数定义:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:创建共享内存映射
函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED( ((void *) -1) ),设置errno值
参数说明:
addr:指定要 映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。
length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。
prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。
flags:由以下几个常值指定:MAP_SHARED(共享的) MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)
fd:表示要映射的文件句柄。如果匿名映射写-1。
offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。
注意事项:
(1) 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。
(2) 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误(mmap: Permission denied)。
当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。
(3) 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。
(4) 用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)
(5) 文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误).
(6)映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误 ,超出映射的内存大小报段错误
(7)mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
mmap()映射的种类:
1基于文件的映射
2 匿名映射
适用于具有亲缘关系的进程之间,
释放内存映射
munmap函数
int munmap(void *addr, size_t length);
返回值:成功返回0,失败返回-1,并设置errno值。
函数参数:
addr:调用mmap函数成功返回的映射区首地址
length:映射区大小(即:mmap函数的第二个参数)
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>
7 #include<string.h>
8
9
10 int main(){
11 int fd;
12 void* addr;
13 fd=open("test",O_RDWR);
14 if(fd<0){
15 perror("open");
16 return -1;
17 }
18 addr=mmap(NULL,2048,PROT_WRITE,MAP_SHARED,fd,0);
19 if(addr==MAP_FAILED){
20 perror("mmap");
21 return -1;
22 }
23 int i=0
24 while(i<2048){
25 memcpy((addr+i),"a",1);
26 sleep(1);
27 }
28 /*
29 while(1){
30 printf("read=%s\n",addr);
31 sleep(1)
32 }
33 */
34
35 }
System V共享内存
ftok函数
key_t ftok(const char *path, int id);
其中参数path是指定的文件名,这个文件必须是存在的而且可以访问的。id是子序号,它是一个8bit的整数。即范围是0~255。当函数执行成功,则会返回key_t键值,否则返回-1。在一般的UNIX中,通常是将文件的索引节点取出,然后在前面加上子序号就得到key_t的值
system V 共享内存使用步骤:
1创建/打开共享内存
2映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
3 读写共享内存
4 撤销共享内存映射
5 删除共享内存对象
查看共享内存命令ipcs
共享内存创建 – shmget
int shmget(key_t key, int size, int shmflg);
共享内存映射
void *shmat(int shmid, const void *shmaddr, int shmflg);
第二个参数一般写NULL,表示自动映射
第三参数一般写0 ,表示可读写
共享内存撤销
int shmdt(void *shmaddr);
撤销后,内存地址不可再访问。
共享内存控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid, IPC_RMID, NULL);删除共享
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key<0){
perror("ftok");
return 0;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 0;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return 0;
}
strcpy(buf,"hello world");
}
信号机制
信号机制
概念:信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
所有信号的产生及处理全部都是由内核完成的
信号的产生:
1 按键产生
2 系统调用函数产生(比如raise, kill)
3 硬件异常
4 命令行产生 (kill)
5 软件条件(比如被0除,访问非法内存等)
信号处理方式:
1 缺省方式
2 忽略信号
3 捕捉信号
常用信号:
信号名 含义 默认操作
SIGHUP 该信号在用户终端关闭时产生,通常是发给和该终端关联的会话内的所有进程 终止
SIGINT 该信号在用户键入INTR字符(Ctrl-C)时产生,内核发送此信号送到当前终端的所有前台进程 终止
SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来产生 终止
SIGILL 该信号在一个进程企图执行一条非法指令时产生 终止
SIGSEV 该信号在非法访问内存时产生,如野指针、缓冲区溢出 终止
SIGPIPE 当进程往一个没有读端的管道中写入时产生,代表“管道断裂” 终止
信号名 含义 默认操作
SIGKILL 该信号用来结束进程,并且不能被捕捉和忽略 终止
SIGSTOP 该信号用于暂停进程,并且不能被捕捉和忽略 暂停进程
SIGTSTP 该信号用于暂停进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号 暂停进程
SIGCONT 该信号让进程进入运行态 继续运行
SIGALRM 该信号用于通知进程定时器时间已到 终止
SIGUSR1/2 该信号保留给用户程序使用 终止
SIGCHLD 是子进程状态改变发给父进程的。 忽略
信号命令:kill不能单纯的理解为杀死命令,而应该是发送信号的函数
kill [-signal] pid
killall [-u user | prog]
信号的函数:
int kill(pid_t pid, int signum)
功能:发送信号
参数:
pid: > 0:发送信号给指定进程
= 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。
< -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员。
= -1:发送信号给,有权限发送的所有进程。
signum:待发送的信号
int raise(int sig);
给自己发信号,等价于kill(getpid(), signo);
定时器函数
unsigned int alarm(unsigned int seconds);
功能:定时发送SIGALRM给当前进程
参数: seconds:定时秒数
返回值:上次定时剩余时间。
ualarm (循环发送)
useconds_t ualarm(useconds_t usecs, useconds_t interval);
以useconds为单位,第一个参数为第一次产生时间,第二个参数为间隔产生
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
功能:定时的发送alarm信号
参数:
which:
ITIMER_REAL:以逝去时间递减。发送SIGALRM信号
ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送SIGVTALRM信号
ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时间。 发送SIGPROF信号
new_value: 负责设定 timout 时间
old_value: 存放旧的timeout值,一般指定为NULL
struct itimerval {
struct timeval it_interval; // 闹钟触发周期
struct timeval it_value; // 闹钟触发时间
};
struct timeval {
time_t tv_sec; /* seconds /
suseconds_t tv_usec; / microseconds */
};
信号的捕捉
信号捕捉过程:
1.定义新的信号的执行函数handle。
2.使用signal/sigaction 函数,把自定义的handle和指定的信号相关联。
signal函数:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:捕捉信号执行自定义函数
返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR
参数:
signo 要设置的信号类型
handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include <signal.h>
6 typedef void (*sighandler_t)(int);
7 sighandler_t oldact;
8
9 void newact(int sig){
10 printf("I catch SIGQUIT\n");
11 signal(SIGQUIT,oldact);
12
13 }
14 int main(){
15
16 oldact= signal(SIGQUIT,newact);
17 while(1){
18
19 sleep(1);
20 }
21
22 }
~
系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样
sigaction函数:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
参数:
signum:处理的信号
act,oldact: 处理信号的新行为和旧的行为,是一个sigaction结构体。
sigaction结构体成员定义如下:
sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似
sa_sigaction: 另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
re_restorer:是一个已经废弃的数据域
定时器的实现
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include<signal.h>
6 #include <sys/time.h>
7
8 typedef void (*sighandler_t)(int);
9 sighandler_t oldact;
10
11 void newact(int sig){
12 if(sig==SIGQUIT){
13 printf("I catch SIGQUIT\n");
14 signal(SIGQUIT,oldact);\
15 }else if(sig==SIGALRM){
16 printf("sned timer\n");
17 //alarm(1);
18 }
19
20 }
21 int main(){
22 struct sigaction act;
23 act.sa_handler=newact;
24 act.sa_flags=0;
25 sigemptyset(&act.sa_mask);
26 sigaction(SIGQUIT,&act,NULL);
27 //alarm(1);
28 struct itimerval newtime;
29
30 newtime.it_interval.tv_sec=1;
31 newtime.it_interval.tv_usec=0;
32
33 newtime.it_value.tv_sec=1;
34 newtime.it_value.tv_usec=0;
35
36 setitimer(ITIMER_REAL,&newtime,NULL);
37 sigaction(SIGALRM,&act,NULL);
38
39 while(1){
40
41 // sleep(1);
42 }
43
44 }
信号回收子进程
用信号来回收进程(wait函数回收进程是一个阻塞函数,会组织主线程的运行)
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include<signal.h>
6 #include <sys/time.h>
7 #include <sys/wait.h>
8
9 void waitwork(int sig){
10 wait(NULL);
11 printf("get sig %d\n",sig);
12
13 }
14
15 int main(){
16 pid_t pid;
17 pid=fork();
18
19 struct sigaction act;
20 act.sa_handler=waitwork;
21 act.sa_flags=0;
22 sigemptyset(&act.sa_mask);
23
24
25
26 if(pid>0){
27 sigaction(SIGCHLD,&act,NULL);
28 while(1){
29
30 printf("this is father\n");
31 sleep(1);
32 }
33
34 }else if(pid==0){
35
36 sleep(5);
37 exit(0);
38 }
39
40 }
信号集和信号阻塞
有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。
信号的阻塞概念:信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
信号的状态:
**信号递达(Delivery ):**实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)
**信号未决(Pending):**从产生到递达之间的状态
信号集操作函数
sigset_t set; 自定义信号集。 是一个32bit 64bit 128bit的数组。
sigemptyset(sigset_t *set); 清空信号集
sigfillset(sigset_t *set); 全部置1
sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中
sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除
sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。
设定对信号集内的信号的处理方式(阻塞或不阻塞)
#include <signal.h>
int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );
返回值:若成功则返回0,若出错则返回-1
首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)
SIG_BLOCK : 把参数set中的信号添加到信号屏蔽字中
SIG_UNBLOCK: 从信号屏蔽字中删除参数set中的信号
SIG_SETMASK: 把信号屏蔽字设置为参数set中的信号
2 #include<stdio.h>
3 #include<unistd.h>
4 #include<stdio.h>
5 #include<stdlib.h>
6 #include<signal.h>
7 #include <sys/time.h>
8
9 void handle(int sig){
10 printf("I get sig%d\n",sig);
11
12 }
13 int main(){
14
15
16 struct sigaction act;
17 act.sa_handler=handle;
18 act.sa_flags=0;
19 sigemptyset(&act.sa_mask);
20 sigset_t set;
21 sigemptyset(&set);
22 sigaddset(&set,SIGQUIT);
23 sigaction(SIGQUIT,&act,NULL);
24
25 sigprocmask(SIG_BLOCK,&set,NULL);
26 sleep(5);
27 sigprocmask(SIG_UNBLOCK,&set,NULL);
28
29 while(1){
30
31 sleep(1);
32 }
33
34
35
36 }
需求:通过信号来驱动程序的模型
int pause(void);可以简单将pause理解为:只有接收到信号才能继续往下运行,否则会一直阻塞
进程一直阻塞,直到被信号中断,返回值:-1 并设置errno为EINTR
函数行为:
1如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。
2如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回
3 如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。
4 pause收到的信号如果被屏蔽,那么pause就不能被唤醒
int sigsuspend(const sigset_t *sigmask);和sigprocmask(UNBLOCK)和pause()同时进行,pause就会接收到没有屏蔽的信号,开始新一轮循环
功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后挂起进程的执行
参数:
sigmask:希望屏蔽的信号
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include<signal.h>
6 #include <sys/time.h>
7
8
9 void work(int sig){
10 printf("I get sig %d\n",sig);
11
12 }
13 void mytask(){
14 printf("task start\n");
15 sleep(3);
16 printf("task end\n");
17
18 }
19 int main(){
20 struct sigaction act;
21 act.sa_flags=0;
22 act.sa_handler=work;
23 sigemptyset(&act.sa_mask);
24 sigset_t set,set2;
25 sigemptyset(&set2);
26 sigemptyset(&set);
27 sigaddset(&set,SIGQUIT);
28 sigaction(SIGQUIT,&act,NULL);
29
30 pause();
31 while(1){
32
33 sigprocmask(SIG_BLOCK,&set,NULL);
34 mytask();
35 // sigprocmask(SIG_UNBLOCK,&set,NULL);
36 // pause();
37 sigsuspend(&set2);
38 // sleep(1);
39 }
40
41
42 }
消息队列
创建相关函数:
ftok 创建key
msgget 创建队列
msgsnd/msgrcv 消息发送和接受
msgctl 队列控制(消除)
msgsnd_t.c
1 #include<stdio.h>
2 #include<sys/ipc.h>
3 #include<sys/msg.h>
4 #include<string.h>
5
6 typedef struct{
7 long msg_type;
8 char buf[1024];
9
10 }msgT;
11 #define MSGLEN (sizeof(msgT)-sizeof(long))
12 int main(){
13 int msgid;
14 key_t key;
15 int ret;
16 msgT msg;
17 key=ftok(".",100);
18 if(key<0){
19 perror("ftok");
20 }
21
22 msgid=msgget(key,IPC_CREAT|0666);
23 if(msgid<0){
24 perror("msgget");
25 }
26 for(int i=0;i<5;i++){
27 msg.msg_type=i+1;
28 strcpy(msg.buf,"this is send msg");
29 ret=msgsnd(msgid,&msg,MSGLEN,0);
30 if(ret<0){
31 perror("msgsnd");
32 }
33 }
34 }
msgrcv_t.c
1 #include<stdio.h>
2 #include<sys/ipc.h>
3 #include<sys/msg.h>
4 #include<string.h>
5
6
7 typedef struct msgtype{
8 long msg_typde;
9 char buf[128];
10 }msgT;
11 #define MSGLEN (sizeof(msgT)-sizeof(long))
12 int main(){
13 key_t key;
14 int msgid;
15 int ret;
16 msgT msg;
17 key=ftok(".",100);
18
19 if(key<0){
20 perror("ftok");
21 }
22 msgid=msgget(key,IPC_CREAT|0666);
23 if(msgid<0){
24 perror("msgget");
25 }
26 int cnt=0;
27 while(1){
28 ret=msgrcv(msgid,&msg,MSGLEN,0,0);
29 if(ret<0){
30 perror("msgrcv");
31 }
32 printf("recv msg type=%d,msg=%s\n",(int)msg.msg_typde,msg.buf);
33 cnt++;
34 if(cnt>3){
35 break;
36 }
37 }
38 msgctl(msgid,IPC_RMID,NULL);
39
40 }
信号灯
前驱知识:
P操作:获取/分配资源
V操作:释放资源
一、创建共享内存
1.key
2.shmget
3.shmat
二、创建读信号量和写信号量
sem_open创建(有名信号灯)
sem_wait 获取信号量
sen_psot 释放信号量
三 捕获信号量,解决程序一次使用问题
sem_unlink
sem_r.c
1 #include<stdio.h>
2 #include<sys/ipc.h>
3 #include<sys/msg.h>
4 #include <sys/ipc.h>
5 #include <sys/shm.h>
6 #include <fcntl.h> /* For O_* constants */
7 #include <sys/stat.h> /* For mode constants */
8 #include <semaphore.h>
9 #include<signal.h>
10 void delfile(int sig){
11 sem_unlink("mysem_r");
12 exit(0);
13
14 }
15 int main(){
16 key_t key;
17 key=ftok(".",100);
18 int shmid;
19 char* shmaddr;
20 sem_t *sem_r,*sem_w;
21 struct sigaction act;
22 act.sa_flags=0
23 act.sa_handler=delfile;
24 sigemptyset(&act.sa_mask);
25 if(key<0){
26 perror("ftok");
27 }
28 shmid=shmget(key,500,0666|IPC_CREAT);
29 if(shmid<0){
30 perror("shmget");
31 }
32 shmaddr=shmat(shmid,NULL,0);
33 sem_r=sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
34 sem_w=sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
35
36
37 while(1){
38 sem_wait(sem_r);
39 // printf(">");
40 // fgets(shmaddr,500,stdin);
41 printf("%s",shmaddr);
42 sem_post(sem_w);
43
44 }
45
46 }
sem_w.c
1 #include<stdio.h>
2 #include<sys/ipc.h>
3 #include<sys/msg.h>
4 #include <sys/ipc.h>
5 #include <sys/shm.h>
6 #include <fcntl.h> /* For O_* constants */
7 #include <sys/stat.h> /* For mode constants */
8 #include <semaphore.h>
9 #include<signal.h>
10
11 void delfile(int sig){
12 sem_unlink("mysem_w");
13 exit(0);
14
15 }
16 int main(){
17 key_t key;
18 key=ftok(".",100);
19 int shmid;
20 char* shmaddr;
21 sem_t *sem_r,*sem_w;
22 struct sigaction act;
23 act.sa_handler=delfile;
24 act.sa_flags=0;
25 sigemptyset(act.sa_mask);
26 if(key<0){
27 perror("ftok");
28 }
29 shmid=shmget(key,500,0666|IPC_CREAT);
30 if(shmid<0){
31 perror("shmget");
32 }
33 sigaction(SIGINT,&act,NULL);
34 shmaddr=shmat(shmid,NULL,0);
35 sem_r=sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
36 sem_w=sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
37
38
39 while(1){
40 sem_wait(sem_w);
41 printf(">");
42 fgets(shmaddr,500,stdin);
43 sem_post(sem_r);
44
45 }
46
47 }
无名信号灯的创建和有名信号灯的创建使用是差不多的,唯一不同的是有名信号量可以再进程和线程当中使用,而无名信号灯只能在线程中使用。
无名信号初始化:sem_init();
无名信号销毁:sem_destroy();
systemV信号灯
一、创建共享内存
1.key
2.shmget
3.shmat
二、创建读信号量和写信号量
semget创建(有名信号灯)
semop 信号量p-v操作
semctl 信号灯控制(初始化、删除)
syssem.c
1 #include<stdio.h>
2 #include<sys/ipc.h>
3 #include<sys/msg.h>
4 #include<string.h>
5 #include<stdlib.h>
6 #include <sys/shm.h>
7 #include<unistd.h>
8 #include <sys/types.h>
9 #include <sys/sem.h>
10
11 union semun{
12 int val;
13 };
14
15 #define SEM_READ 0
16 #define SEM_WRITE 1
17
18 void poperation(int semid,int semindex){
19 struct sembuf sbuf;
20 sbuf.sem_num=semindex;
21 sbuf.sem_op=-1;
22 sbuf.sem_flg=0;
23
24 semop(semid,&sbuf,1);
25
26 }
27 void voperation(int semid,int semindex){
28 struct sembuf sbuf;
29 sbuf.sem_num=semindex;
30 sbuf.sem_op=1;
31 sbuf.sem_flg=0;
32
33 semop(semid,&sbuf,1);
34
35
36
37 }
38 int main(){
39 key_t key;
40 int shmid,semid;
41 char* shmaddr;
42 key=ftok(".",100);
43 if(key<0){
44 perror("ftok");
45 }
46 shmid=shmget(key,500,IPC_CREAT|0666);
47 if(shmid<0){
48 perror("shmget");
49 }
50 semid=semget(key,2,IPC_CREAT|0666);
51 if(semid<0){
52 perror("semget");
53 }
54 shmaddr=shmat(shmid,NULL,0);
55 if(shmat<0){
56 perror("shmat");
57 }
58
59 pid_t pid;
60
61 union semun mysem;
62 mysem.val=0;
63 semctl(shmid,SEM_READ,SETVAL,mysem);
64 mysem.val=1;
65 semctl(shmid,SEM_WRITE,SETVAL,mysem);
66
67 pid=fork();
68 if(pid<0){
69 semctl(semid,IPC_RMID,IPC_RMID);
70 shmctl(shmid,IPC_RMID,NULL);
71
72 }else if(pid==0){
73 while(1){
74 poperation(shmid,SEM_READ);
75 printf("%s\n",shmaddr);
76 voperation(shmid,SEM_WRITE);
77
78 }
79
80 }else{
81 poperation(shmid,SEM_WRITE);
82 printf(">");
83 fgets(shmaddr,32,stdin);
84 voperation(shmid,SEM_READ);
85 }
86
87
88 }