目录
一、 守护进程
1.1 进程组
- 多个进程的一个集合。
- 在linux所有的进程都属于某一个进程组。
- 当前进程组中的第一个进程, 就是组长
进程组ID和组长的进程ID相同
获取当前进程所在的进程组的组ID
pid_t getpgrp(void);
获取指定进程所在的进程组的组ID
pid_t getpgid(pid_t pid);
- pid: 指定的进程的PID
将一个进程送到另外的一个进程组,创建一个新的进程组
int setpgid(pid_t pid, pid_t pgid);
- pid: 要操作的进程的PID
- pgid: 进程组ID
1.2 会话
多个进程组的集合.
// 获取进程所属的会话ID
#include <unistd.h>
pid_t getsid(pid_t pid);
返回值:
成功: 会话ID, 失败: -1
#include <unistd.h>
// 创建一个会话
// 在哪个进程中调用该函数, 这个进程就会被提升为会话
// - 没有任何职务的进程才满足条件, 普通的进程
// - 这个普通的进程会脱离原来的操作终端
pid_t setsid(void);
- > - 不能是进程组长
- > - 创建会话的进程成为新进程组的组长
- > - 创建出的新会话会丢弃原有的控制终端
1.3 创建守护进程的步骤
红色为必须步骤
- 1. 父进程创建子进程, 杀死父进程 -> 必须
- 2. 将子进程提升为会话 -> 必须
setsid();
- 3. 修改进程的工作目录, 工作目录切换到不能被卸载的目录中: / /home -> 不是必须
目的: 防止有些不安全目录被卸载:
在U盘总启动一个进程, 把U盘拔了, 进程无法正常运行
chdir();
- 4. 修改umask掩码 -> 不是必须
umask();
- 5. 关闭/重定向文件描述符 -> 不是必须
- 标准输入 -> close(0)
- 标准输出 -> close(1)
- 标准错误 -> close(2)
- 重定向: 设备文件: /dev/null ->int fd = open("/dev/null", O_RDWR);
dup2(0, fd)
- 6. 核心操作流程 -> 必须
例子:写一个守护进程, 每隔2s获取一次系统时间, 将这个时间写入到磁盘文件.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
void working(int num)
{
// 得到当前系统时间
// 得到了秒数
time_t tm = time(NULL);
// 时间的转换
struct tm* loc = localtime(&tm);
//char buf[1024];
//sprintf(buf, "%d-%d-%d %d:%d:%d", loc->tm_year, loc->tm_mon, loc->tm_mday, loc->tm_hour, loc->tm_min, loc->tm_sec);
// 通过函数格式化时间
char* curTim = asctime(loc);
// 写入到文件中
int fd = open("time.log", O_WRONLY|O_CREAT|O_APPEND, 0664);
write(fd, curTim, strlen(curTim));
close(fd);
}
int main()
{
// 创建子进程, 杀死父进程
pid_t pid = fork();
if(pid > 0)
{
exit(0);
}
// 只剩下子进程 -> 提升为会话
// 脱离操作终端, 进程直接后台运行
setsid();
// 修改进程的工作目录
// 修改掩码
umask(022);
// 重定向fd 0, 1, 2 -> /dev/null
int fd = open("/dev/null", O_RDWR);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
// 核心操作
// 设置信号捕捉
struct sigaction newact;
newact.sa_flags = 0;
newact.sa_handler = working;
sigemptyset(&newact.sa_mask);
sigaction(SIGALRM, &newact, NULL);
// 设置定时器
struct itimerval myval;
// 第一次发信号倒计时
myval.it_value.tv_sec = 3;
myval.it_value.tv_usec = 0;
// 设置第一次以后的频率
myval.it_interval.tv_sec = 2;
myval.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &myval, NULL);
// 防止当前进程退出
while(1)
{
sleep(10);
}
return 0;
}
二、线程
> 线程是轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。
>
> 操作系统会以进程为单位,分配系统资源,所以我们也说,进程是资源分配的最小单位。线程是操作系统调度执行的最小单位.
>
> - 每个进程对应一个虚拟地址空间
> - 多个子线程和父线程共用同一个虚拟地址空间
> - 线程是从进程中分出去的, 多个线程共用同一个地址空间
> - 进程会争抢cup资源
> - 线程被内核当做了进程
> - 线程也会争抢cpu资源
2.1 线程之间共享和非共享资源
- 共享资源
- 1. 文件描述符表
- 2. 每种信号的处理动作 -> 信号捕捉到之后的回调函数
- 3. 当前工作目录
- 4. 用户ID和组ID
- 5. 内存地址空间 (.text/.data/.bss/heap/共享库)
- 不共享资源
- 1. 线程id -> 无符号长整形
- 2. 处理器现场和栈指针(内核栈)
- 3. 独立的栈空间(用户空间栈)
- 4. errno变量
- 5. 阻塞信号集
- 6. 调度优先级 -> 线程的优先级
粗糙的说:
2.2 创建线程 pthread_create
#include <pthread.h>
// pthread_t 线程id的类型, 无符号长整形
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
- thread: 传出参数, 线程创建成功之后, 子线程的线程ID被写到该变量中
- attr: 设置线程属性, 一般使用默认值, NULL
- start_routine: 函数指针, 这个函数就是子线程的处理逻辑
- arg: 给第三个参数传参
返回值:
成功: 0, 失败: 错误号, 这个错误号和之前的errno不一样
打印错误信息: char *strerror(int errnum);
$ gcc pthread_create.c
/tmp/ccVvnhFB.o: In function `main':
pthread_create.c:(.text+0x78): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
// 在编译程序的时候需要添加线程库的名字 pthread
gcc pthread_create.c -lpthread
2.3 获取当前线程的线程ID pthread_self
#include <pthread.h>
// 返回线程ID
pthread_t pthread_self(void);
2.4 线程退出 pthread_exit
// 当前线程退出, 不会影响其他线程的正常运行
#include <pthread.h>
void pthread_exit(void *retval);
- 参数: retval, 线程退出时候的返回值
2.5 回收子线程资源 pthread_join
这是个阻塞函数, 调用一次回收一个子线程。这个函数在主线程(父线程)中使用
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:
- thread: 要回收的子线程的线程ID
- retval: 使用这个变量接收子线程退出的时候返回的值
2.6 线程分离 pthread_detach
如果设置了线程分离, 子线程结束之后, 对应的资源不需要父线程释放,再调用 pthread_join 该函数会报错
#include <pthread.h>
int pthread_detach(pthread_t thread);
- thread: 子线程线程ID
2.7 线程取消 pthread_cancel
- 在父线程(主线程)中调用了线程取消函数, 可以终止某个子线程的运行
- 该函数调用之后, 并不能马上终止子线程, 当子线程的处理函数运行到一个取消点的位置, 线程就终止了
- 如果没有取消点, 子线程是不会被终止的
- 取消点: 在程序中有从用户区到内核区的切换, 这个位置称之为取消点
#include <pthread.h>
// 在父线程中
int pthread_cancel(pthread_t thread);
- thread: 子线程的线程ID
2.8 比较线程ID是否相同 pthread_equal
在linux下线性ID - 无符号长整形
在不同的平台下, pthread_t 封装有可能不同, 有些平台下 pthread_t 是一个结构体
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
返回值:
相同: 非0值, 不同: 0