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

 

 

 

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