0%

unix环境高级编程 - 守护进程与线程

最近在学习 APUE,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习APUE的童鞋们提供一份参考。

本系列博文均根据学习《UNIX环境高级编程》一书总结而来;

运行环境:

  • 操作系统: ubutnu 16.04
  • 编译器:QtCreator CLion 2020.3

守护进程

守护进程简介

Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字,如vsftpd

Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。

守护进程特点:

  • Linux后台服务进程
  • 独立于控制终端
  • 周期性执行
  • 不受用户登录和注销影响

进程组和会话

进程组

进程组是一个或者多个进程的集合,每个进程都属于一个进程组,引入进程组是为了简化对进程的管理。当父进程创建子进程的时候,默认子进程与父进程属于同一个进程组。

进程组ID==第一个进程ID(组长进程)。如父进程创建了多个子进程,父进程和多个子进程同属于一个组,而由于父进程是进程组里的第一个进程,所以父进程就是这个组的组长, 组长ID==父进程ID。

  • 可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。

  • 只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。

  • 进程组生存期:从进程组创建到最后一个进程离开

会话

  • 一个会话是一个或多个进程组的集合。

  • 创建会话的进程不能是进程组组长

  • 创建会话的进程成为一个进程组的组长进程,同时也成为会话的会长。

  • 需要有root权限(ubuntu不需要)

  • 新创建的会话丢弃原有的控制终端

  • 建立新会话时,先调用fork, 父进程终止,子进程调用setsid函数

创建了会话,这个进程就脱离了控制终端的影响

创建守护进程模型

第1步:fork子进程,父进程退出

  • 子进程继承了父进程的进程组ID, 但具有一个新的进程ID,这样就保证了子进程不是一个进程组的组长ID,这对于下面要做的setsid函数的调用是必要的前提条件

第2步:子进程调用setsid函数创建新会话

  • 调用这个函数以后

    • 该进程成为新会话的首进程,是会话的会长

    • 成为一个新进程组的组长进程,是进程组组长

    • 不受控制终端的影响

第3步:改变当前工作目录chdir

  • 如:a.out在U盘上,启动这个程序,这个程序的当前的工作目录就是这个u盘,如果u盘拔掉后进程的当前工作目录将消失,a.out将不能正常工作。

第4步:重设文件掩码 mode & ~umask

  • 子进程会继承父进程的掩码

  • 增加子进程程序操作的灵活性

  • umask(0000);

第5步:关闭文件描述符

  • 守护进程不受控制终端的影响所以可以关闭,以释放资源

  • close(STDIN_FILENO);

    close(STDOUT_FILENO);

    close(STDERR_FILENO);

第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
/**
* @file 守护进程创建
*
* 示例程序 - 01_deamon.c
*
* @author Steve & sYstemk1t
*
*/


#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()
{
//父进程fork子进程,父进程退出
pid_t pid = fork();
if(pid < 0 || pid > 0)
{
exit(1);
}


//子进程调用setsid函数
setsid(); //创建会话


//改变当前工作目录
chdir("/root/log/");

//改变文件掩码
umask(002);


//关闭文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

//核心代码
//每2s获取一次系统时间,将时间写入文件
//注册信号处理函数
struct sigaction act;
act.sa_handler = MyFunc;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM,&act,NULL);

//int setitimer(int which, const struct itimerval *new_value,
// struct itimerval *old_value);

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和组ID

  • 内存地址空间 (.text/.data/.bss/heap/共享库)

线程非共享资源

  • 线程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
/**
* @file 创建线程
*
* 示例程序 - 02_pthread_create.c
*
* @author Steve & sYstemk1t
*
*/


#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
/**
* @file 创建线程传递参数
*
* 示例程序 - 03_pthread_create_arg.c
*
* @author Steve & sYstemk1t
*
*/


#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)
{
//int n = *(int *)arg;
//int *p = (int *)arg;
struct Test *t = (struct Test *)arg;
//printf("n == %d\n",n);
//printf("p == %d\n",*p);
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");
//int n = 99;
//int *p = (int *)malloc(sizeof(int));
//p = 100;
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
/**
* @file 循环创建子线程,打印是第几个子线程
*
* 示例程序 - 04_pthread_create_loop.c
*
* @author Steve & sYstemk1t
*
*/


#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
/**
* @file 线程退出函数
*
* 示例程序 - 05_pthread_exit.c
*
* @author Steve & sYstemk1t
*
*/


#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());
//printf("a == %p\n",&g_var);
//pthread_exit(&g_var); //获取退出状态

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);
//int n = *(int *)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
/**
* @file 线程退出函数
*
* 示例程序 - 05_pthread_exit.c
*
* @author Steve & sYstemk1t
*
*/


#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());
//printf("a == %p\n",&g_var);
//pthread_exit(&g_var); //获取退出状态

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);

//子线程设置分离属性,则pthread_join不在阻塞
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
/**
* @file 线程退出函数
*
* 示例程序 - 05_pthread_exit.c
*
* @author Steve & sYstemk1t
*
*/


#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
/**
* @file 创建线程
*
* 示例程序 - 02_pthread_create.c
*
* @author Steve & sYstemk1t
*
*/


#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步:定义线程属性类型类型的变量

  • pthread_attr_t attr;

第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);

² 参数:

  • attr: 线程属性

  • detachstate:

    • PTHREAD_CREATE_DETACHED(分离)

    • PTHREAD_CREATE_JOINABLE(非分离)

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
/**
* @file 创建线程
*
* 示例程序 - 02_pthread_create.c
*
* @author Steve & sYstemk1t
*
*/


#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;

//初始化attr变量
pthread_attr_init(&attr);

//设置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;
}