最近在学习 APUE,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习APUE的童鞋们提供一份参考。
本系列博文均根据学习《UNIX环境高级编程》一书总结而来;
运行环境:
- 操作系统: ubutnu 16.04
- 编译器:QtCreator CLion 2020.3
守护进程
守护进程简介
Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字,如vsftpd
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。
守护进程特点:
- Linux后台服务进程
- 独立于控制终端
- 周期性执行
- 不受用户登录和注销影响
进程组和会话
进程组
进程组是一个或者多个进程的集合,每个进程都属于一个进程组,引入进程组是为了简化对进程的管理。当父进程创建子进程的时候,默认子进程与父进程属于同一个进程组。
进程组ID==第一个进程ID(组长进程)。如父进程创建了多个子进程,父进程和多个子进程同属于一个组,而由于父进程是进程组里的第一个进程,所以父进程就是这个组的组长, 组长ID==父进程ID。
会话
创建了会话,这个进程就脱离了控制终端的影响
创建守护进程模型
第1步:fork子进程,父进程退出
- 子进程继承了父进程的进程组ID, 但具有一个新的进程ID,这样就保证了子进程不是一个进程组的组长ID,这对于下面要做的setsid函数的调用是必要的前提条件
第2步:子进程调用setsid函数创建新会话
调用这个函数以后
该进程成为新会话的首进程,是会话的会长
成为一个新进程组的组长进程,是进程组组长
不受控制终端的影响
第3步:改变当前工作目录chdir
- 如:a.out在U盘上,启动这个程序,这个程序的当前的工作目录就是这个u盘,如果u盘拔掉后进程的当前工作目录将消失,a.out将不能正常工作。
第4步:重设文件掩码 mode & ~umask
子进程会继承父进程的掩码
增加子进程程序操作的灵活性
umask(0000);
第5步:关闭文件描述符
第6步:执行核心工作
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
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h>
void MyFunc(int signo) { int fd = open("time.log",O_RDWR | O_CREAT | O_APPEND,0666); if(fd < 0) { return; } time_t t; time(&t); char *p = ctime(&t); write(fd,p,strlen(p)); close(fd); return ; }
int main() { pid_t pid = fork(); if(pid < 0 || pid > 0) { exit(1); }
setsid();
chdir("/root/log/");
umask(002);
close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO);
struct sigaction act; act.sa_handler = MyFunc; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGALRM,&act,NULL);
struct itimerval st; st.it_interval.tv_sec = 2; st.it_interval.tv_usec = 0; st.it_value.tv_sec = 3; st.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &st, NULL);
printf("Hello,sYstemk1t\n"); while (1) { sleep(1); } }
|
线程
线程简介
轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。
进程:拥有独立的地址空间,拥有PCB,相当于独居。
线程:有PCB,但没有独立的地址空间,多个线程共享进程空间,相当于合租。
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数 clone。
如果复制对方的地址空间,那么就产出一个“进程”;
如果共享对方的地址空间,就产生一个“线程”。
Linux内核是不区分进程和线程的, 只在用户层面上进行区分。
线程共享资源
线程非共享资源
线程id
处理器现场和栈指针(内核栈)
独立的栈空间(用户空间栈)
errno变量
信号屏蔽字
调度优先级
pthread_create
创建一个新线程
1 2 3
| #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
|
pthread_t:传出参数,保存系统为我们分配好的线程ID
- 当前Linux中可理解为:typedef unsigned long int pthread_t。
attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
arg:线程主函数执行期间所使用的参数。
由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用strerror()把错误码转换成错误信息再打印。
如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待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
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
void* ThreadProc(void *arg) { printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); }
int main() { pthread_t thread; int nRet = pthread_create(&thread,NULL,ThreadProc,NULL); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; } printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); sleep(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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
struct Test { int data; char name[64]; };
void* ThreadProc(void *arg) { struct Test *t = (struct Test *)arg; printf("data = %d\n",t->data); printf("name = %s\n",t->name); printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); }
int main() { struct Test test; memset(&test,0x00,sizeof(test)); test.data = 20; strcpy(test.name,"sYstemk1t"); pthread_t thread; int nRet = pthread_create(&thread,NULL,ThreadProc,&test); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; } printf("main Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); sleep(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 44 45 46 47 48 49 50 51 52
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
void* ThreadProc(void *arg) { int i = *(int *)arg; printf("[%d] child Thread, pid == [%d] id == [%ld]\n",i,getpid(),pthread_self()); }
int main() { int nCount = 5; pthread_t thread[5]; int nRet; int nArray[5]; for (int i = 0; i < nCount; ++i) { nArray[i] = i; nRet = pthread_create(&thread[i],NULL,ThreadProc,&nArray[i]); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; }
} printf("main Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); sleep(1); return 0; }
|
在创建子线程的时候使用循环因子作为参数传递给子线程,这样主线程和多个子线程就会共享变量i(变量i在main函数中定义,在整个进程都一直有效)所以在子线程看来变量i是合法的栈内存空间。
pthread_exit
在线程中禁止调用exit函数,否则会导致整个进程退出,取而代之的是调用pthread_exit函数,这个函数是使一个线程退出,如果主线程调用pthread_exit函数也不会使整个进程退出,不影响其他线程的执行。
1
| void pthread_exit(void *retval);
|
pthread_join
阻塞等待线程退出,获取线程退出状态。其作用,对应进程中的waitpid() 函数。
1 2
| int pthread_join(pthread_t thread, void **retval);
|
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
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h> int g_var = 9;
struct Test { int data; char name[64]; }; struct Test test;
void* ThreadProc(void *arg) { printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self());
memset(&test,0x00,sizeof(test)); test.data = 99; strcpy(test.name,"sYstemk1t"); printf("test == %p\n",&test); pthread_exit(&test); }
int main() { pthread_t thread; int nRet = pthread_create(&thread,NULL,ThreadProc,NULL); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; } printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); void *p = NULL; pthread_join(thread,&p); struct Test *pt = (struct Test *)p; printf("child exit status = %d, [%s] ,[%p]\n",pt->data,pt->name,p); return 0; }
|
pthread_detach
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。
也可使用 pthread_create函数参2(线程属性)来设置线程分离。pthread_detach函数是在创建线程之后调用的。
1
| int pthread_detach(pthread_t thread);
|
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用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 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
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
int g_var = 9;
struct Test { int data; char name[64]; }; struct Test test;
void* ThreadProc(void *arg) { printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self());
memset(&test,0x00,sizeof(test)); test.data = 99; strcpy(test.name,"sYstemk1t"); printf("test == %p\n",&test); pthread_exit(&test); }
int main() { pthread_t thread; int nRet = pthread_create(&thread,NULL,ThreadProc,NULL); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; } printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); pthread_detach(thread);
void *p = NULL; nRet = pthread_join(thread,NULL); if(nRet != 0) { printf("pthread_join error : %s\n",strerror(nRet)); } sleep(1); return 0; }
|
pthread_cancel
杀死(取消)线程。其作用,对应进程中 kill() 函数。
1
| int pthread_cancel(pthread_t thread);
|
线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。
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 <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
int g_var = 9;
struct Test { int data; char name[64]; }; struct Test test;
void* ThreadProc(void *arg) {
while (1) { int a; int b; int c; printf("--------\n"); pthread_testcancel(); } }
int main() { pthread_t thread; int nRet = pthread_create(&thread,NULL,ThreadProc,NULL); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; } printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); pthread_cancel(thread); pthread_join(thread,NULL); return 0; }
|
pthread_equal
比较两个线程ID是否相等。
1
| int pthread_equal(pthread_t t1, pthread_t t2);
|
比较两个线程是否相等
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
|
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
struct Test { int data; char name[64]; };
void* ThreadProc(void *arg) { printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); }
int main() { pthread_t thread; struct Test test; memset(&test,0,sizeof(test)); test.data = 100; strcpy(test.name,"sYstemk1t"); int nRet = pthread_create(&thread,NULL,ThreadProc,NULL); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; } printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self());
if(pthread_equal(thread,pthread_self()) != 0) { printf("two thread is equal\n"); } else { printf("two thread is no equal\n"); } sleep(1); return 0; }
|
设置线程属性分离
第1步:定义线程属性类型类型的变量
第2步:对线程属性变量进行初始化
- int pthread_attr_init (pthread_attr_t* attr);
第3步:设置线程为分离属性
1 2 3 4 5
| 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 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 <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/time.h> #include <time.h> #include <fcntl.h> #include <sys/stat.h> #include <signal.h> #include <pthread.h>
void* ThreadProc(void *arg) { printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self()); }
int main() { pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_t thread; int nRet = pthread_create(&thread,&attr,ThreadProc,NULL); if(nRet != 0) { printf("pthread_create error, [%s]\n",strerror(nRet)); return -1; } printf("child Thread, pid == [%d] id == [%ld]\n",getpid(),pthread_self());
nRet = pthread_join(thread,NULL); if(nRet != 0) { printf("pthread_join errror : %s\n",strerror(nRet)); } pthread_attr_destroy(&attr); sleep(1); return 0; }
|