01 学习目标
1.守护进程的特点
2.熟练进行守护进程的创建
3.熟练掌握多线程的创建
4.熟练掌握线程的退出和资源回收
02 守护进程相关的概念
进程组:多个进程在同一个组,第一个进程默认是进程组的组长。
会话:进程组的更高一级,多个进程组对应一个会话。
创建会话需要注意以下5点注意事项:
- 调用进程不能是进程组组长,该进程变成新会话首进程(session header)
- 该进程成为一个新进程组的组长进程
- 新会话丢弃原有的控制终端,该会话没有控制终端。
- 该调用进程是组长进程,则出错返回
- 建立新会话时,先调用fork,父进程终止,子进程调用setsid
创建会话的时候,组长不可以创建,必须是组员创建
创建会话的步骤:创建子进程,父进程去死,子进程自当会长。
man setsid
守护进程:
Deamon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互,不受用户登录,注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务;nfs服务器等。
创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader.
守护进程的步骤:
-
创建子进程,父进程退出
所有工作在子进程中进行形式上脱离了终端控制 -
在子进程中创建新会话
setsid()函数
使子进程完全独立出来,脱离控制 -
改变当前目录为根目录
chdir()函数
防止占用可卸载的文件系统
也可以换成其它路径 -
设置文件权限掩码
umask()函数
防止继承的文件创建屏蔽字拒绝某些权限
增加守护进程灵活性 -
关闭文件描述符
继承的打开文件不会用到,浪费系统资源,无法卸载 -
开始执行守护进程核心工作
-
守护进程退出程序模型
简化版步骤:
- 创建子进程fork
- 父进程退出
- 子进程当会长setsid
- 切换工作目录$HOME
- 设置掩码umask
- 关闭文件描述符0,1,2,为了避免浪费资源
- 执行核心逻辑
- 退出
03 守护进程创建
创建一个守护进程:每分钟在$HOME/log/ 创建一个文件 程序名.时间戳
#include<stdio.h>
#include<unisstd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcnt1.h>
#include<string.h>
#include<stdlib.h>
#define _FILE_NAME_FORMAR_ "%s/log/mydaemon.%ld" //定义文件格式化
void touchfile(int num)
{
char *HomeDir = getenv("HOME");
char strFilename[256]={0};
sprintf(strFilename,_FILE_NAME_FORMAR_,HomeDir,time(NULL));
int fd=open(strFilename,O_RDWR|O_CREAT,0666);
if(fd<0)
{
perror("open err");
exit(1);
}
close(fd);
}
int mian()
{
//创建子进程,父进程退出
pid_t pid=fork();
if(pid>0)
exit(1);
//当会长
setsid()
//设置掩码
umask(0);
//切换目录
chdir(getenv("HOME"));//切换到家目录
//关闭文件描述符
//close(1),close(0),close(2);
//执行核心逻辑
struct itimerval myit={{60,0},{60,0}};
setitimer(ITIMER_REAL,&myit,NULL);
struct sigaction act;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
act.sa_handler=touchfile;
sigaction(SIGALRM,&act,NULL);
while(1)
{
//每隔1分钟在/home/itheima/log下创建文件
sleep(1);
//do sth
}
//退出
return 0;
}
关闭进程,此线程依旧在运行
04 守护进程拓展了解
通过nohup指令也可以达到守护进程创建的效果
nohup cmd[>1.log] &
- nohup 指令会让cmd收不到SIGHUP信号
- & 代表后台运行
#include<stdio.h>
#include<unisstd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcnt1.h>
#include<string.h>
#include<stdlib.h>
int main(int argc,char* argv[])
{
char strFileName[256]={0};
while(1)
{
memset(strFileName,0x00,sizeof(strFileName));
sprintf(strFilename,"%s/log/zhen2.%ld",getenv("HOME"),time(NULL));
int fd=open(strFilename,O_RDWR|O_CREAT,0664);
if(fd<0)
{
perror("open err");
exit(1);
}
close(fd);
sleep(5);
}
}
05 线程有关的概念
线程man page安装
sudo apt-get install manpages-posix-dev
线程的概念:轻量级的进程,一个进程内部可以有多个线程,默认情况下一个进程只有一个线程
线程是最小的执行单元,进程是最小的系统资源分配单元。
内核实现都是通过clone函数实现的。
线程也有PCB,但没有独立的地址空间(共享)。
Linux内核线程实现原理
类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念,因此在这类系统中,进程和线程关系密切。
- 轻量级进程(ligth-weight-process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
- 从内核里看进程和线程是一样的,都是各自不同的PCB
- 进程至少有一个线程
- 线程可看做寄存器和栈的合集
- 在linux下,线程是最小的执行单元,进程是最小的分配资源单元。
查看LWP号:ps -Lf pid 查看指定线程的lwp号。
06 线程的优点和缺点
线程共享资源
- 文件描述符表
- 每种信号的处理方式
- 当前工作目录
- 用户ID和组ID
- 内存地址空间(.text/.data/.bss/heap共享库)
线程非共享资源
- 线程id
- 处理器线程和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量
- 信号屏蔽字
- 调度优先级
errno变量,获得错误码对应的错误信息
char* strerror(int errnum);
线程的优点:
- 提供并发性
- 占用资源小
- 通信方便
线程的缺点:
- 调用困难
- 库函数,不稳定
- 对信号支持不好
07 pthread_create创建一个线程
man pthread_create
创建一个线程
int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void* (*start_routine)(void*),void* arg);
- thread 线程的id,传出参数
- attr 代表线程的属性
- 第三个参数 函数指针,void* fun(void*)
- arg 线程执行函数的参数
- 返回值
成功返回0
失败返回errno
编译的时候需要加pthread库
Compile and link with -pthread
gcc 03_pthread_create.c -lpthread
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void* thr(void *arg)
{
printf("I am a thread!pid=%d,tid=%lu\n",getpid(),pthread_self());
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thr,NULL);
printf("I am a main thread!pid=%d,tid=%lu\n",getpid(),pthread_self());
sleep(1);
return 0;
}
echomake
多一个makefile
alias echomake
添加命令到makefile
在家目录的.bashrc增加
alias echomake='cat ~/bin/makefile.template>>makefile'
~/bin/makefile.template 是模板makefile
为了将模板makefile重定向到makefile文档,可以修改makefile文档,在重新执行makefile。
设置shell里vi 的快捷键
set -o vi
设置后vi的快捷键在shell里都能使用
08 pthread_exit 线程的退出
pthread_exit 线程退出函数
注意事项:
- 在线程中使用pthread_exit
- 在线程中使用return(主控线程return代表退出进程)
- exit代表退出整个进程
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
void* thr(void *arg)
{
printf("I am a thread!pid=%d,tid=%lu\n",getpid(),pthread_self());
//return NULL;
pthread_exit(NULL);
exit(1);
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thr,NULL);
printf("I am a main thread!pid=%d,tid=%lu\n",getpid(),pthread_self());
sleep(10);
printf("I will out\n");
pthread_exit(NULL);
return 0;
}
09 pthread_join线程的回收
pthread_join 线程回收函数
int pthread_join(pthread_t thread,void **retval);
- thread 创建的时候传出的第一个参数
- retval 代表的传出线程的退出信息
pthread_rtn.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void* thr(void *arg)
{
printf("I am a thread,tid=%lu\n",pthread_self());
return (void*)100;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thr,NULL);
void *ret;
pthread_join(tid,&ret);//线程回收
printf("ret exit with %d\n",(int)ret);
pthread_exit(NULL);
return 0;
}
10 pthread_cancel杀死线程
pthread_cancel 杀死线程
int pthread_cancel(pthread_t thread)
- 需要出入tid
- 返回值
失败返回errno
成功返回0
被pthread_cancel杀死的线程,退出状态为PTHREAD_CANCELED
#define PTHREAD_CANCELED((void *)-1)
pthread_cancel.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void* thr(void *arg)
{
while(1)
{
printf("I am a thread,very happy!tid=%lu\n,pthread_self());
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thr,NULL);
sleep(5);
pthread_cancel(tid);//杀死线程
void *ret;
pthread_join(tid,&ret);
printf("thread exit with %d\n",ret);
return 0;
}
但是循环中,无sleep,无法杀死
pthread_cancel.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void* thr(void *arg)
{
while(1)
{
//printf("I am a thread,very happy!tid=%lu\n,pthread_self());
//sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thr,NULL);
sleep(5);
pthread_cancel(tid);//杀死线程
void *ret;
pthread_join(tid,&ret);
printf("thread exit with %d\n",ret);
return 0;
}
遇上上述情况,可采用如下方法:
强制设置取消点
pthread_testcancel()
11 pthread_detach线程分离
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络,多线程服务器常用。
线程分离
int pthread_detach(pthread_t thread);
此时不需要pthread_join回收资源
pthread_detach.c:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void *thr(void *arg)
{
printf("I am a thread,self=%lu\n",pthread_self());
sleep(4);
printf("I am a thread,self=%lu\n",pthread_self());
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thr,NULL);
pthread_detach(tid);//线程分离
sleep(5);
int ret=0;
if((ret=pthread_join(tid,NULL))>0)
{
printf("join err:%d,%s\n",ret,strerror(ret));
}
return 0;
}
12 线程属性设置分离
pthread_equal 比较两个线程ID是否相等。
int pthread_equal(pthread_ t t1,pthread_t t2)
线程的特点:
- 线程共享全局变量
- 线程id在进程内部是唯一的,但不是全局唯一的!
进程的属性控制:
初始化线程属性
int pthread_attr_init(pthread_attr_t *attr)
销毁线程属性
int pthread_attr_destory(pthread_attr_t *attr)
设置属性分离态
int pthread_attr_setdetachstate(pthread_attr_t* attr,int detachstate)
- attr init初始化的属性
- detachstate
PTHREAD_CREATE_DETACHED 线程分离
PTHREAD_CREATE_JOINABLE 允许回收
pthread_attr.c:
#include<stdio.h>
#inlclude<unistd.h>
#include<pthread.h>
void *thr(void *arg)
{
printf("I am a thread\n");
return NULL;
}
int main()
{
pthread_attr_t attr;
pthread_attr_init(&attr);//初始化属性;
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置属性分离
pthread_t tid;
pthread_create(&tid,&attr,thr,NULL);
if((ret=pthread_join(tid,NULL))>0)
{
printf("join err:%d,%s\n",ret,strerror(ret));
}
pthread_attr_destroy(&attr);//摧毁属性
return 0;
}
13 线程注意事项
NPTL:
-
查看当前pthread库版本getconf GUN_LIBPTHREAD_VERSION
-
NPTL实现机制(POSIX),Native POSIX Thread Library
-
使用线程库时gcc指定 -lpthread
线程使用注意事项
- 主线程退出其他线程不退出,主线程应调用pthread_exit
- 避免僵尸线程
pthread_join
pthread_detach
pthread_create指定分离属性
被join线程可能在join函数返回前就释放完自己的所有内存资源,所有不应当返回被回收线程栈中的值。 - molloc和mmap申请的内存可以被其他线程释放
- 应避免在多线程模型中调用fork,除非,马上exec,子线程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit
- 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制。
创建多少个线程?
cpu核数*2+2
14 作业
1.实现一个守护进程,每分钟写入一次日志,要求日志文件保存在$HOME/log/下
- 命名规则:程序名.yyyymm
- 写入内容格式:mm:dd hh:mi:ss 程序名[进程号]:消息内容
#include<string.h>
#include<stdlib.h>
#include<sys/time.h>
#include<time.h>
#include<signal.h>
#define _PROGRAM_NAME_ "touchevety"
#define _LOG_FORMAT_ "%02d-%02d %02d:%02d:%02d %s [%06d]:%s\n" //mm-dd hh:mi:ss programname [pid]:msg
#define _LOG_FILE_ "%s/log/%s.%04f%02d" //$HOME/log/programname.yyyymm,如果$HOME/log不存在,需要创建
void catch_alarm(int num)
{
time_t nowtime=time(NULL);
struct tm* nowtm=localtime(&nowtime);
char strLogFile[100];
memset(strLogFile,0x00,sizeof(strLogFile));
sprintf(strLogFile,_LOG_FILE,getenv("HOME"),_PROGRAM_NAME_,nowtm->tm_year+1900,nowtm->tm_mon+1);
int fd =open(strLogFile,O_WRONLY|O_CREAT|O_APPEND,0666);
if(fd<0)
{
perror("open file err");
printf("file is %s\n",strLogFile);
exit(1);
}
char buf[2014]={0};
sprintf(buf,_LOG_FORMAT_ ,nowtm->tm_mon+1,nowtm->tm_mday,nowtm->tm_hour,nowtm->tm_min,nowtm->tm_sec,_PROGRAM_NAME_,getpid(),"I am alive!");
write(fd,buf,strlen(buf));
close(fd);
}
int main()
{
//初始化需要的环境变量
char *strHomeDir = getenv("HOME");
printf("homedir is %s\n",strHomeDir);
//守护进程创建
pid_t pid = fork();
if(pid>0)
{
exit(1);//父进程退出
}
setsid();//子进程当会长,此上2步必须
umask(0);//设置掩码
chdir(strHomeDir);
close(0);
//设置信号捕捉,捕捉ALARM信号
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
act.sa_handler=catch_alarm;
sigaction(SIGALRM,&act,NULL);
//设置时钟参数
struct itimerval myit={{60,0},{1,0}};//每隔60s来一次闹钟
setitimer(ITIMER_REAL,&myit,NULL);
//循环等待
while(1)
{
sleep(120);
}
return 0;
}
2.实现多线程拷贝
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcnt1.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#define _THR_CNT_ 5 //线程数
typedef struct _TaskInfo{
int num;
void *src;
void *des;
int size'
}TaskInfo;
void *thr_cp(void *arg)
{
TaskInfo *info=arg;
int num=info->num;
int cpsize=info->size/_THR_CNT_;
int mod=info->size%cpsize;
if(num=_THR_CNT_-1)
{
memcpy(info->des+num*size,info->src+num*cpsize,cpsize+mode);
}
else
{
memcpy(info->des+num*size,info->src+num*cpsize,cpsize);
}
return NULL;
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("./a.out srcfile desfile\n");
return -1;
}
int n=_THR_CNT_;//线程个数
struct stat sb;
if(stat(argv[1],&sb)<0)
{
perror(argv[1]);
exit(1);
}
long lfilesize=sb.size;
int fdsrc=open(argv[1].O_RDONLY);
int fddes=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0666);
ftruncate(fddes,lfilesize);
if(fdsrc<0||fddes<0)
{
printf("open file %s %s err\n",argv[1],argv[2]);
exit(1);
}
void *srcmem=mmap(NULL,lfilesize,PROT_READ,MAP_PRIVATE,fdsrc,0);
if(srcmem==MAP_FAILED)
{
perror("mmap srcfile err");
eixt(1);
}
void *desmem=mmap(NULL,lfilesize,PROT_READ|PROT_WRITE,MAP_SHARED,fddes,0);
if(desmem==MAP_FAILED)
{
perror("mmap desfile err");
eixt(1);
}
TaskInfo taskInfos[_THE_CNT_];
pthread_t tid[_THE_CNT_];
int i;
for(i=0;i<n;i++)
{
taskInfos[i].src=srcmem;
taskInfos[i].des=desmen;
taskInfos[i].num=i;
taskInfos[i].size=lfilesize;
pthread_create(&tid[i],NULL,thr_cp,&taskInfos[i]);
}
for(i=0;i<n;i++)
{
pthread_join(tid[i],NULL);
}
munmap(srcmem,lfilesize);
munmap(desmem,lfilesize);
return 0;
}