目錄
一、 守護進程
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