c/c++:守护进程、线程、线程之间共享和非共享资源、线程常用函数

目录

一、 守护进程

1.1 进程组

1.2 会话

1.3 创建守护进程的步骤

二、线程

 2.1 线程之间共享和非共享资源

2.2 创建线程 pthread_create

2.3 获取当前线程的线程ID   pthread_self

2.4 线程退出  pthread_exit

2.5 回收子线程资源    pthread_join

2.6 线程分离   pthread_detach

2.7 线程取消   pthread_cancel

2.8 比较线程ID是否相同  pthread_equal


 

 

一、 守护进程

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

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章