3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

linux学习笔记 -- 系统编程

發(fā)布時(shí)間:2024/3/13 linux 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux学习笔记 -- 系统编程 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

系統(tǒng)編程

  • 相關(guān)概念
    • 概念
      • 簡(jiǎn)易cpu結(jié)構(gòu)
      • mmu內(nèi)存管理單元
    • 環(huán)境變量
      • PATH
      • SHELL
      • HOME
      • LANG
      • TERM
      • getenv
      • setenv
      • unsetenv
    • 進(jìn)程控制
      • fork函數(shù)
      • getpid
      • getppid
      • getuid
      • getgid
      • 父子進(jìn)程共享
      • gdb調(diào)試
    • exec
      • execl
      • execlp
      • execle
      • execv
      • execvp
      • execve
      • 一般規(guī)律
    • 回收子進(jìn)程
      • 孤兒進(jìn)程
      • 僵尸進(jìn)程
      • wait
      • waitpid
  • 2.IPC方法
    • 管道
      • 管道的概念
      • pipe函數(shù)
      • 管道的讀寫行為
      • 管道緩沖區(qū)大小
      • 管道的優(yōu)勢(shì)
    • FIFO
    • 共享存儲(chǔ)映射(共享內(nèi)存)
      • 文件進(jìn)程間通訊
      • 存儲(chǔ)映射I/O
      • mmap父子進(jìn)程通訊
      • 匿名映射
      • mmap無(wú)血緣關(guān)系進(jìn)程間通訊
  • 3.信號(hào)
    • 信號(hào)的事件和狀態(tài)
    • 信號(hào)的產(chǎn)生
      • 終端按鍵產(chǎn)生的信號(hào)
      • 硬件異常產(chǎn)生信號(hào)
      • kill產(chǎn)生
      • raise和abort
      • 軟件條件產(chǎn)生
    • 信號(hào)的捕捉
      • **signal函數(shù):**
      • **sigaction函數(shù):**
      • 信號(hào)捕捉特性
    • 信號(hào)集操作函數(shù)
      • 信號(hào)集設(shè)定
      • sigprocmask
      • sigpending函數(shù)
    • 競(jìng)態(tài)條件
      • pause
      • 時(shí)序競(jìng)態(tài)
      • 全局變量異步IO
      • 可/不可重入函數(shù)
    • SIGCHLD信號(hào)
      • SIGCHLD的產(chǎn)生條件
      • 借助SIGCHLD信號(hào)回收子進(jìn)程
    • 信號(hào)傳參
      • 發(fā)送信號(hào)傳參
      • 捕捉函數(shù)傳參
    • 中斷系統(tǒng)調(diào)用
  • 4.守護(hù)進(jìn)程
    • 終端
      • 終端的啟動(dòng)流程
      • ttyname
      • 網(wǎng)絡(luò)終端
    • 進(jìn)程組
      • 進(jìn)程操作函數(shù)
    • 會(huì)話
      • 創(chuàng)建會(huì)話
    • 守護(hù)進(jìn)程
      • 創(chuàng)建守護(hù)進(jìn)程模型
  • 5.線程
    • 線程的概念
      • 什么是線程
      • linux內(nèi)核線程實(shí)現(xiàn)原理
      • 線程共享資源
      • 線程非共享資源
      • 線程優(yōu)缺點(diǎn)
    • 線程控制原語(yǔ)
      • pthread_self
      • pthread_create
      • pthread_exit
      • pthread_join
      • pthread_detach
      • pthread_cancel
      • pthread_equal
      • 控制原語(yǔ)對(duì)比
    • 線程屬性
      • 線程屬性初始化
      • 線程的分離狀態(tài)
      • 線程的棧地址
      • 線程的棧大小
    • NPTL
    • 線程注意事項(xiàng)
  • 線程同步
    • 同步概念
      • 線程同步
      • 數(shù)據(jù)混亂原因
    • 互斥量mutex(互斥鎖)
      • 主要應(yīng)用函數(shù)
      • 加鎖與解鎖
      • 加鎖步驟測(cè)試
    • 死鎖
    • 讀寫鎖
      • 讀寫鎖狀態(tài)
      • 讀寫鎖特性
      • 主要應(yīng)用函數(shù)
      • 讀寫鎖實(shí)例
    • 條件變量
      • 主要應(yīng)用函數(shù):
      • 生產(chǎn)者消費(fèi)者條件變量模型
      • 條件變量的優(yōu)點(diǎn)
    • 信號(hào)量
      • 主要應(yīng)用函數(shù)
      • 生產(chǎn)者消費(fèi)者信號(hào)量模型
    • 進(jìn)程間同步
      • 互斥量mutex
      • 文件鎖
  • 其他
    • 文件存儲(chǔ)
    • UNIX
    • 優(yōu)化

黑馬教程分享視頻學(xué)習(xí)筆記

相關(guān)概念

概念

程序:編輯好的二進(jìn)制文件(.out);在磁盤上,不占用系統(tǒng)資源(cpu、內(nèi)存、打開的文件、設(shè)備、鎖);進(jìn)程:抽象的概念,與操作系統(tǒng)原理聯(lián)系緊密。進(jìn)程是活躍的程序,占用系統(tǒng)資源。在內(nèi)存中執(zhí)行(程序運(yùn)行起來(lái),產(chǎn)生一個(gè)進(jìn)程)同一個(gè)程序可以同時(shí)啟動(dòng)兩個(gè)進(jìn)程。并發(fā):并行執(zhí)行。 單道程序設(shè)計(jì)模式:不同任務(wù)之間排隊(duì)使用cpu(DOS) 多道程序設(shè)計(jì)模式:不同程序同時(shí)使用cpu;通過(guò)時(shí)鐘中斷,硬件手段在不同進(jìn)程之間回收、分配cpu使用權(quán)力;

簡(jiǎn)易cpu結(jié)構(gòu)


數(shù)據(jù)讀取時(shí),硬盤到內(nèi)存中,再到緩沖區(qū)中,然后進(jìn)入cpu的寄存器中。cpu的預(yù)取器取出一條指令;譯碼器翻譯指令并存儲(chǔ)相關(guān)數(shù)據(jù)到寄存器中;然后交給ALU進(jìn)行計(jì)算,操作寄存器堆,再放回緩存,返回到內(nèi)存。

mmu內(nèi)存管理單元


虛擬地址:可用的地址空間有4G。

mmu將虛擬內(nèi)存地址與物理內(nèi)存地址進(jìn)行映射;設(shè)置修改內(nèi)存訪問(wèn)級(jí)別(內(nèi)核空間和用戶空間),cpu可使用的級(jí)別有四種,linux只使用了最高和最低兩種。

PCB:進(jìn)程描述符、進(jìn)程控制塊。

進(jìn)程之間彼此獨(dú)立,每運(yùn)行一個(gè)程序,需要進(jìn)行物理內(nèi)存映射,開辟新的物理內(nèi)存空間使用。但不同進(jìn)程之間的映射的物理內(nèi)存是同一塊的,由mmu實(shí)現(xiàn)不同進(jìn)程的PCB描述數(shù)據(jù)不同。

進(jìn)程控制塊PCB:

? 在linux內(nèi)核的進(jìn)程控制塊是task_struct結(jié)構(gòu)體。查找結(jié)構(gòu)體命令:grep -r "task_struct {" /usr/

? 存在于/usr/src/linux-headers-3.16.0-30/include/linux/sched.h,常用成員如下:

1. 進(jìn)程ID。系統(tǒng)中每個(gè)進(jìn)程有唯一的id,在c語(yǔ)言中用pid_t類型表示,非負(fù)整數(shù) 2. 進(jìn)程狀態(tài)。就緒(包括初始化,等待cpu分配時(shí)間片)、運(yùn)行(占用cpu)、掛起(等待除cpu以外的其他資源 主動(dòng)放棄cpu)、停止 3. 進(jìn)程切換時(shí)需要保存和恢復(fù)的一些cpu寄存器 4. 描述虛擬地址空間的信息 5. 描述控制終端的信息 6. 當(dāng)前工作目錄 7. umask掩碼 8. 文件描述符表,包括很多指向file結(jié)構(gòu)體的指針。 9. 和信號(hào)相關(guān)的信息。 10. 用戶id和組id 11. 會(huì)話和進(jìn)程組。 12. 進(jìn)程可以使用的資源上限。``ulimit -a`` linux系統(tǒng)中查看資源上下限

環(huán)境變量

linux 是多任務(wù)、多用戶的開源操作系統(tǒng)。

環(huán)境變量,是指在操作系統(tǒng)中用來(lái)指定操作系統(tǒng)運(yùn)行環(huán)境的一些參數(shù)。具備一下特征:

  • 字符串(本質(zhì))
  • 有統(tǒng)一的格式:名=值[:值] ,多個(gè)值用:分割
  • 值用來(lái)描述進(jìn)程環(huán)境信息
  • 存儲(chǔ)形式:與命令行參數(shù)類似。char* []數(shù)組,數(shù)組名environ,內(nèi)部存儲(chǔ)字符串,NULL作為哨兵結(jié)尾。

    使用形式:與命令行參數(shù)類似。

    加載位置:與命令行參數(shù)類似。位于用戶區(qū),高于stack的起始位置。

    引入環(huán)境變量表:需聲明環(huán)境變量。extern char** environ;

    #include<stdio.h>extern char **environ;int main(void) {int i;for(i = 0; environ[i]; i++){printf("%s\n",environ[i]);}return 0; }

    PATH

    ? 可執(zhí)行文件的搜索路徑。ls命令也是一個(gè)程序,執(zhí)行他不需要提供完整的路徑名稱/bin/ls,但是通常我們執(zhí)行當(dāng)前目錄下的程序a.out確需要提供完整的路徑名/a.out,這是因?yàn)镻ATH環(huán)境變量里的值包含了ls命令所在的目錄/bin,卻不包含a.out所在的目錄。PATH環(huán)境變量的值可以包含多個(gè)目錄,用:號(hào)隔開,使用時(shí),shell會(huì)按照環(huán)境變量順序,從前到后檢索對(duì)應(yīng)路徑下是否有可用的應(yīng)用程序,知道最后或找到。在shell中用echo命令可以查看這個(gè)環(huán)境變量的值:echo $PATH

    SHELL

    ? 當(dāng)前Shell,他通常是/bin/bash,當(dāng)前命令解析器。

    HOME

    ? 當(dāng)前的家目錄

    LANG

    ? 當(dāng)前的預(yù)言,執(zhí)行echo $LANG后可以看到為 zh_CN.UTF-8.

    TERM

    ? 當(dāng)前終端類型,在圖形界面終端下它的值通常是xterm,終端類型決定了一些程序的顯示方式,比如圖形界面終端可以顯示漢字,而字符終端一般不行。

    getenv

    獲取環(huán)境變量值

    char *getenv(const char * name);成功:返回環(huán)境變量的值;失敗:NULL(name不存在)

    setenv

    設(shè)置環(huán)境變量的值

    int setenv(const char *name, const char *value, int overwrite);成功:0;失敗-1.

    參數(shù)overwrite取值:1. 覆蓋原環(huán)境變量;0. 不覆蓋(常用于設(shè)置新環(huán)境變量)

    unsetenv

    刪除環(huán)境變量name的定義

    int unsetenv(const char * name);成功:0;失敗:-1

    注意: name不存在仍返回0,當(dāng)name命名為“ABC=”時(shí)會(huì)出錯(cuò)。

    #include<stdio.h> #include<stdlib.h> #include<string.h>int main(void) {char *val;const char *name = "wxf";val = getenv(name);printf("1 %s=%s\n",name,val);int ret = setenv(name, "hallo world", 1);val = getenv(name);printf("2 %s=%s\n",name,val);#if 0ret = unsetenv("wxf=");printf("3 ret = %d\n",ret);val = getenv(name);printf("4 %s=%s\n",name,val); #elseret = unsetenv(name);printf("3 ret = %d\n",ret);val = getenv(name);printf("4 %s=%s\n",name,val); #endifreturn 0; }

    進(jìn)程控制

    fork函數(shù)

    ? 創(chuàng)建一個(gè)子進(jìn)程。

    ? pid_t fork(void); 失敗返回-1;成功返回:父進(jìn)程返回子進(jìn)程的ID(非負(fù))、子進(jìn)程返回0 。

    ? pid_t 類型表示進(jìn)程ID,但為了表示-1,他是有符號(hào)整形。(0不是有效的進(jìn)程ID,init最小,為1)

    ? 注意返回值,不是fork函數(shù)能返回兩個(gè)值,而是fork后,fork函數(shù)變成兩個(gè),父子需各自返回一個(gè)。fork函數(shù)執(zhí)行完成后,創(chuàng)建了子進(jìn)程,父、子進(jìn)程同時(shí)繼續(xù)執(zhí)行fork函數(shù)后面的代碼。

    #include<stdio.h> #include<unistd.h> #include<stdlib.h>int main(void) {printf("begin 88888888888888888888\n");pid_t pid;pid = fork();if(pid == -1){perror("fork");exit(1);}printf("pid = %d \n",pid);if(pid == 0){printf("child process,pid = %u,ppid=%u\n",getpid(),getppid());}else{printf("parent process,pid = %u,ppid=%u\n",getpid(),getppid());}printf("end 88888888888888888888\n");return 0; }

    循環(huán)創(chuàng)建n個(gè)子進(jìn)程

    ? 一次fork函數(shù)調(diào)用可以創(chuàng)建一個(gè)子進(jìn)程。那么創(chuàng)建N個(gè)子進(jìn)程應(yīng)該如何實(shí)現(xiàn)呢,簡(jiǎn)單想,``for(int i=0;i<n;i++)){fork()}即可,但是是這樣嗎。

    #include<stdio.h> #include<unistd.h> #include<stdlib.h>int main(void) {printf("begin 88888888888888888888\n");pid_t pid;int times = 5;int i = 0;for ( i = 0; i < times; i++){pid = fork();if(pid == -1){perror("fork");exit(1);}if(pid == 0){printf("child %d process,pid = %u,ppid=%u\n",i+1, getpid(),getppid());break;//子進(jìn)程跳出循環(huán),不再產(chǎn)生孫進(jìn)程}}if(i < 5){sleep(i);//為了保證輸入的先后順序printf("end child %d pid = %u\n",i+1,getpid()); }else{sleep(i);printf("end 88888888888888888888\n");}return 0; }

    ? 子進(jìn)程產(chǎn)生后與父進(jìn)程同時(shí)搶奪cpu使用時(shí)間,(tips:沒(méi)有理論依據(jù),但父進(jìn)程獲取cpu的幾率大些)

    ? 去除sleep后打印混亂。執(zhí)行可執(zhí)行命令的shell進(jìn)程(爺爺進(jìn)程)在父進(jìn)程return后輸出控制臺(tái),而子進(jìn)程可能還沒(méi)結(jié)束,將繼續(xù)打印。

    getpid

    返回當(dāng)前進(jìn)程IDpid_t getpid();

    getppid

    返回當(dāng)前進(jìn)程父進(jìn)程IDpid_t getppid();

    getuid

    獲取當(dāng)前進(jìn)程實(shí)際用戶ID:uid_t getuid(void);

    對(duì)應(yīng) 獲取當(dāng)前進(jìn)程有效用戶ID:uid_t geteuid(void);

    getgid

    獲取當(dāng)前進(jìn)程實(shí)際用戶組ID:gid_t getgid(void);

    對(duì)應(yīng) 獲取當(dāng)前進(jìn)程有效用戶組ID:gid_t getegid(void);

    父子進(jìn)程共享

    剛fork之后(后續(xù)執(zhí)行代碼后按照各自進(jìn)程處理):

    ? 父子相同處:全局變量、.data、.text、棧、堆、環(huán)境變量、用戶ID、宿主目錄、進(jìn)程工作目錄、信號(hào)處理方式等。

    ? 父子不同處:

    1. 進(jìn)程ID2. fork返回值3. 父進(jìn)程ID4. 進(jìn)程運(yùn)行時(shí)間5. 鬧鐘(定時(shí)器)6. 未決信號(hào)集

    ? 父子進(jìn)程間遵循讀時(shí)共享,寫時(shí)復(fù)制的原則。無(wú)論子進(jìn)程執(zhí)行父進(jìn)程的邏輯還是執(zhí)行自己的邏輯都能節(jié)省內(nèi)存開銷。

    注意:父子進(jìn)程共享:

  • 文件描述符(打開文件的結(jié)構(gòu)體)
  • mmap建立的映射區(qū)(進(jìn)程間通信詳解)
  • gdb調(diào)試

    編譯時(shí)需要加-g:gcc fork.c -g

    進(jìn)入調(diào)試:gbd a.out

    ? 使用gdb調(diào)試的時(shí)候,只能跟蹤一個(gè)進(jìn)程,可以在fork之前,通過(guò)指令設(shè)置gdb調(diào)試工具跟蹤父進(jìn)程或者子進(jìn)程。默認(rèn)跟蹤父進(jìn)程。

    ? set follow-fork-mode child 命令設(shè)置gdb在fork之后跟蹤子進(jìn)程

    ? set follow-fork-mode parent 命令設(shè)置gdb在fork之后跟蹤父進(jìn)程

    exec

    ? fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往需要調(diào)用一種exec函數(shù)以執(zhí)行另一個(gè)程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)例程(main)開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的ID并未改變。

    ? 將當(dāng)前進(jìn)程的.text、.data替換為所要加載的程序的.text、.data,然后讓進(jìn)程從新的.text第一條指令開始執(zhí)行,但進(jìn)程ID不變,換核不換殼。

    ? 其實(shí)有六種以exec開頭的函數(shù),統(tǒng)稱為exec函數(shù)。

    execl

    加載一個(gè)進(jìn)程,通過(guò) 路徑+程序名 來(lái)加載。

    int execl(const char * path, const char * arg,...);

    成功:無(wú)返回;失敗:-1

    對(duì)比execlp, 如加載ls命令帶有-l, -F參數(shù)

    execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。

    execl("/bin/ls", "ls", "-l", "-F", NULL); 使用參數(shù)1給出的絕對(duì)路徑搜索

    也可以執(zhí)行自己的程序,如當(dāng)前目錄下的fork:execl("./fork", "fork", NULL);

    execlp

    int execlp(const char *file, const char * arg,...);

    list,path

    參數(shù):file 可執(zhí)行程序文件名;arg命令行參數(shù),注意第一個(gè)arg相當(dāng)于argv[0],相當(dāng)于ls -l中的ls,一般而言,可執(zhí)行程序可能不會(huì)讀取argv[0],所以argv[0],只起到站位的作用,只要后續(xù)參數(shù)不錯(cuò)就行。另外需要以NULL結(jié)尾。

    該函數(shù)需要配合PATH環(huán)境變量來(lái)使用,當(dāng)PATH中所有目錄搜索后沒(méi)有參數(shù)1則出錯(cuò)返回。

    該函數(shù)通常用來(lái)調(diào)用系統(tǒng)程序。如:ls、date、cp、cat等命令。

    #include<stdio.h> #include<stdlib.h> #include<unistd.h>int main() {pid_t pid;pid = fork();if(pid == -1){perror("fork");exit(1);}if(pid > 0){printf("parent process\n");sleep(1);}else{execlp("ls", "ls", "-l", "-a", NULL);}return 0; }

    只有發(fā)生錯(cuò)誤的時(shí)候,函數(shù)才會(huì)有返回值-1,成功時(shí)不會(huì)有返回值。

    execle

    int execle(const char * path, const char * arg, ... ,char * const envp[]);

    需要引入環(huán)境變量。

    execv

    int execv(const char * path,char * const argv[]);

    char *argv[] = {"ls", "-l","-a",NULL}; execv("/bin/ls",argv);

    execvp

    int execvp(const char * file, char * const argv[]);

    execve

    int execve(const char * path,char * const argv[], char *const envp[]);

    將所有進(jìn)程保存在文件中

    #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<fcntl.h>int main() {int fd;fd = open("ps.out", O_WRONLY | O_CREAT | O_TRUNC, 0644);if(fd < 0){perror("open");exit(1);}dup2(fd, STDOUT_FILENO);execlp("ps", "ps", "ax", NULL);return 0; }

    一般規(guī)律

    ? exec函數(shù)一旦調(diào)用成功立即執(zhí)行新的程序,不返回。只有失敗才返回,錯(cuò)誤值-1。所以通常我們直接在exec函數(shù)調(diào)用后直接調(diào)用perror()和exit(),無(wú)需if判斷。

    l(list) 命令行參數(shù)列表

    p(path) 搜索file時(shí)使用path變量

    v(vector) 使用命令行參數(shù)數(shù)組

    e(environment) 使用環(huán)境變量數(shù)組,不適用進(jìn)程原有的環(huán)境變量, 設(shè)置新加載程序運(yùn)行的環(huán)境變量。

    ? 事實(shí)上,只有execve是真正的系統(tǒng)調(diào)用,其他五個(gè)函數(shù)最終都調(diào)用execve,所以execve在man手冊(cè)第二節(jié),其他函數(shù)在第三節(jié),關(guān)系如下圖。

    回收子進(jìn)程

    孤兒進(jìn)程

    ? 父進(jìn)程先于子進(jìn)程結(jié)束,則子進(jìn)程成為孤兒進(jìn)程,子進(jìn)程的父進(jìn)程成為init進(jìn)程,稱為init進(jìn)程領(lǐng)養(yǎng)孤兒進(jìn)程。

    僵尸進(jìn)程

    ? 進(jìn)程終止,父進(jìn)程尚未回收,子進(jìn)程殘留資源(PCB)存放在內(nèi)核中,變成僵尸進(jìn)程。

    ? 注意:僵尸進(jìn)程是不能使用kill命令清除掉的。因?yàn)閗ill命令只是用來(lái)終止進(jìn)程的,而僵尸進(jìn)程已經(jīng)終止。

    ? 通過(guò)殺死進(jìn)程的父進(jìn)程,使init進(jìn)程領(lǐng)養(yǎng)該進(jìn)程,int進(jìn)而回收此進(jìn)程資源。

    wait

    ? 一個(gè)進(jìn)程在終止時(shí)會(huì)關(guān)閉所有文件描述符,釋放在用戶空間分配的內(nèi)存,但它的PCB還保留著,內(nèi)核在其中保存了一些信息:如果是正常終止則保存退出狀態(tài),如果是異常終止則保存著導(dǎo)致該進(jìn)程終止的信號(hào)是哪個(gè)。這個(gè)進(jìn)程的父進(jìn)程可以調(diào)用wait或waitpid獲取這些信息,然后徹底清除掉這個(gè)進(jìn)程。我們知道一個(gè)進(jìn)程的退出狀態(tài)可以在shell中使用特殊變量$?查看,因?yàn)閟hell是它的父進(jìn)程,它終止時(shí)shell調(diào)用wait或者waitpid得到它的退出狀態(tài)同時(shí)徹底清除這個(gè)進(jìn)程。

    ? 父進(jìn)程調(diào)用wait函數(shù)可以回收子進(jìn)程終止信息。該函數(shù)有三個(gè)功能:

    1. 阻塞等待子進(jìn)程退出 2. 回收子進(jìn)程殘留資源 3. 獲取子進(jìn)程退出狀態(tài)(原因)

    **pid_t wait(int * status);**成功:清除掉的子進(jìn)程ID;失敗:-1(沒(méi)有子進(jìn)程)

    ? 當(dāng)進(jìn)程終止時(shí),操作系統(tǒng)的隱式回收機(jī)制會(huì):1. 關(guān)閉所有文件描述符 2. 釋放用戶空間分配的內(nèi)存。內(nèi)核的PCB仍存在。其中保存該進(jìn)程的退出狀態(tài)。(正常終止–退出值;異常終止–終止信號(hào))

    ? 可使用wait函數(shù)傳出參數(shù)status來(lái)保存進(jìn)程的退出狀態(tài)。借助宏函數(shù)來(lái)進(jìn)一步判斷進(jìn)程終止的具體原因。宏函數(shù)可分為如下三組:

  • WIFEXITED(status) 為非0,進(jìn)程正常結(jié)束

    WEXITSTATUS(status) 如上宏為真,使用此宏,獲取進(jìn)程退出狀態(tài)(exit的參數(shù))

  • WIFSIGNALED(status) 為非0, 進(jìn)程異常終止
    WTERMSIG(status) 如上宏為真,使用此宏,獲取使進(jìn)程終止的信號(hào)的編號(hào)

  • WIFSTOPPED(status) 為非0,進(jìn)程處于暫停狀態(tài)
    WSTOPSIG(status) 如上宏為真,使用此宏,獲取使進(jìn)程暫停的信號(hào)的編號(hào)
    WIFCONTINUED(status) 為真,進(jìn)程暫停后已經(jīng)繼續(xù)運(yùn)行

  • #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h>int main() {pid_t pid, wpid;pid = fork();int status;int times = 10;if(pid == 0){printf("child process, parent pid=%d,go to sleep\n",getppid());sleep(3);printf("child stop");exit(33);// int a = 8/0;}else if(pid > 0){wpid = wait(&status);if(wpid == -1){perror("wait");exit(1);}if(WIFEXITED(status)){printf("child exit with %d\n",WEXITSTATUS(status));}if(WIFSIGNALED(status)){printf("child killed by %d\n",WTERMSIG(status));}while(times){printf("parent process, pid=%d, child pid=%d\n",getpid(),pid);sleep(1);times-=1;}}else{perror("fork");return 1;}return 0; }

    ? 一次wait調(diào)用,回收一個(gè)子進(jìn)程。

    waitpid

    ? 作用同wait,但可指定pid進(jìn)程清理,可以不阻塞。

    ? pid_t waitpid(pid_t pid,int *status, in options);成功:返回清理掉的子進(jìn)程ID;失敗-1

    ? 特殊參數(shù)和返回情況:

    ? 參數(shù)pid

    1. 大于0,回收指定ID的子進(jìn)程2. -1, 回收任意子進(jìn)程(相當(dāng)于wait)3. 0, 回收和當(dāng)前調(diào)用waitpid一個(gè)組的所有子進(jìn)程(一次回收一個(gè))4. 小于0, 回收指定進(jìn)程組內(nèi)的任意子進(jìn)程

    ? 參數(shù)status:用戶獲取進(jìn)程退出狀態(tài)。

    ? 參數(shù)options:0:阻塞;WNOHANG:非阻塞。

    ? 返回0:參數(shù)3為WNOHANG,且子進(jìn)程正在運(yùn)行,不阻塞;使用時(shí)如果為了保證進(jìn)程退出回收,可以輪詢調(diào)用。其他情況與wait一樣。

    ? 一次wait和waitpid調(diào)用只能清理一個(gè)子進(jìn)程,清理多個(gè)子進(jìn)程應(yīng)使用循環(huán)。

    #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h>int main(int argc, char* argv[]) {int n = 5,i;pid_t p, q, wpid;if(argc == 2){n = atoi(argv[1]);}for ( i = 0; i < n; i++){p = fork();if(p == 0){break;}else if(i == 3){q = p;}}if(n == i){sleep(n);printf("parent process,pid = %d\n",getpid()); #if 0while(wait(NULL)>0){}printf("parent end\n"); #else// while(waitpid(-1,,NULL,0)>0){//阻塞,同wait// }// printf("parent end\n");// waitpid(q, NULL, o);//阻塞,回收指定進(jìn)程do{wpid = waitpid(-1, NULL, WNOHANG);//非阻塞if(wpid > 0){n--;}//如果wpid==0,說(shuō)明子進(jìn)程正在運(yùn)行sleep(1);}while(n > 0);printf("parent end\n"); #endif}else{sleep(i);printf("child %d process,pid=%d\n",i+1,getpid());}return 0; }

    2.IPC方法

    ? Linux環(huán)境下,進(jìn)程地址空間相互獨(dú)立,每個(gè)進(jìn)程各自有不同的用戶地址空間。任何一個(gè)進(jìn)程的全局變量在另一個(gè)進(jìn)程中都看不到,所以進(jìn)程和進(jìn)程之間不能互相訪問(wèn),要交換數(shù)據(jù)必須通過(guò)內(nèi)核,在內(nèi)核中開辟一塊緩沖區(qū),進(jìn)程1把數(shù)據(jù)從用戶空間考到內(nèi)核緩沖區(qū),進(jìn)程2再?gòu)膬?nèi)核緩沖區(qū)把數(shù)據(jù)讀走,內(nèi)核提供的這種機(jī)制稱為進(jìn)程間通訊(IPC,InterProcess Communication)。

    ? 在進(jìn)程間完成數(shù)據(jù)傳遞需要借助操作系統(tǒng)提供的方法,如:文件、管道、信號(hào)、共享內(nèi)存、消息隊(duì)列、套接字、命名管道等。隨著計(jì)算機(jī)的蓬勃發(fā)展,一些方法由于自身設(shè)計(jì)缺陷被淘汰和棄用,如今的進(jìn)程間通訊方式有:

    1. 管道(使用最簡(jiǎn)單) 2. 信號(hào)(開銷最小) 3. 共享映射區(qū)(無(wú)血緣關(guān)系) 4. 本地套接字(最穩(wěn)定)

    管道

    管道的概念

    ? 管道是一種最基本的IPC機(jī)制,作用于有血緣關(guān)系的進(jìn)程之間,完成數(shù)據(jù)傳遞。調(diào)用pipe系統(tǒng)函數(shù)即可創(chuàng)建一個(gè)管道,如下特質(zhì):

    1. 本質(zhì)是一個(gè)偽文件(實(shí)為內(nèi)核緩沖區(qū)) 2. 由兩個(gè)文件描述符引用,一個(gè)表示讀端,一個(gè)表示寫端。 3. 規(guī)定數(shù)據(jù)從管道的寫端流入管道,從讀端流出。

    占用存儲(chǔ)空間的文件類型(普通文件-;符號(hào)鏈接s;目錄d)。

    偽文件類型(套接字s;塊設(shè)備b;字符設(shè)備c;管道p)。

    管道的原理:管道實(shí)為內(nèi)核使用環(huán)形隊(duì)列機(jī)制,借助內(nèi)核緩沖區(qū)(4k)實(shí)現(xiàn)。

    管道的局限性:

    ? 1) 數(shù)據(jù)自己讀不能自己寫。

    ? 2)數(shù)據(jù)一旦被讀走,便不再管道中存在,不可反復(fù)讀取。

    ? 3)由于管道采用半雙工通信方式。因此,數(shù)據(jù)只能在一個(gè)方向上流動(dòng)。

    ? 4)只能在有公共祖先的進(jìn)程間(有血緣關(guān)系的進(jìn)程)使用管道。

    pipe函數(shù)

    ? 創(chuàng)建管道

    ? int pipe(int pipefd[2]); 成功:0;失敗:-1,設(shè)置errno

    ? 函數(shù)調(diào)用成功返回r/w兩個(gè)文件描述符。無(wú)需open,但需手動(dòng)close。規(guī)定:fd[0] ,r;fd[1],w;就像0對(duì)應(yīng)標(biāo)準(zhǔn)輸入,1對(duì)應(yīng)標(biāo)準(zhǔn)輸出一樣。向管道文件讀寫數(shù)據(jù)其實(shí)是在讀寫內(nèi)核緩沖區(qū)。

    ? 管道創(chuàng)建成功以后,創(chuàng)建該管道的進(jìn)程(父進(jìn)程)同時(shí)掌握著管道的讀端和寫端。如何實(shí)現(xiàn)父子進(jìn)程通信呢,通常步驟如下:

    1. 父進(jìn)程調(diào)用pipe函數(shù)創(chuàng)建管道,得到兩個(gè)文件描述符fd[0]、fd[1]指向管道的讀端和寫端。 2. 父進(jìn)程調(diào)用fork創(chuàng)建子進(jìn)程,那么子進(jìn)程也有兩個(gè)文件描述符指向同一管道。 3. 父進(jìn)程關(guān)閉管道讀端,子進(jìn)程關(guān)閉管道寫端。父進(jìn)程可以向管道中寫入數(shù)據(jù),子進(jìn)程將管道中的數(shù)據(jù)讀出。由于管道是利用環(huán)形隊(duì)列實(shí)現(xiàn)的,數(shù)據(jù)從寫端流入管道,從讀端流出,這樣就實(shí)現(xiàn)了進(jìn)程間通信。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h>int main(int argc, char* argv[]) {int fd[2];int ret = pipe(fd);if(ret == -1){perror("pipe");exit(1);}pid_t pid = fork();if(-1 == pid){perror("fork");exit(1);}if( 0 == pid ){printf("child process read\n");close(fd[1]);char buf[1024];ret = read(fd[0], buf, sizeof(buf));if(ret == 0){printf("read over\n");}write(STDOUT_FILENO, buf, ret);}else{printf("parent process write\n");close(fd[0]);write(fd[1], "pipe trans\n",strlen("pipe trans\n"));}return 0; }

    管道的讀寫行為

    1. 如果所有指向管道寫端的文件描述符都關(guān)閉了(管道寫端引用計(jì)數(shù)為0),而仍然有進(jìn)程從管道讀端讀數(shù)據(jù),那么管道中剩余的數(shù)據(jù)都被讀取后,再次read會(huì)返回0,就像讀到文件末尾一樣。 2. 如果有指向管道寫端的文件描述符沒(méi)有關(guān)閉(管道寫端引用計(jì)數(shù)不為0),且持有寫端描述符的進(jìn)程也沒(méi)有向管道中寫數(shù)據(jù),這時(shí)讀端從管道中讀取數(shù)據(jù)后,將會(huì)阻塞read,直到管道中有了數(shù)據(jù)再繼續(xù)讀取。 3. 如果所有指向管道讀端的文件描述符都關(guān)閉了(管道讀端引用計(jì)數(shù)為0),這時(shí)持有管道寫端文件描述符的進(jìn)程會(huì)接收到信號(hào)SIGPIPE,導(dǎo)致該進(jìn)程異常終止。 4. 如果有指向管道讀端的文件描述符沒(méi)有關(guān)閉(讀端引用計(jì)數(shù)不為0),此時(shí)如果管道已經(jīng)數(shù)據(jù)寫滿了,那么將會(huì)阻塞write,直到有空間可以寫入數(shù)據(jù)。

    管道緩沖區(qū)大小

    ? 可以使用ulimit -a命令來(lái)查看當(dāng)前系統(tǒng)中創(chuàng)建管道文件所對(duì)應(yīng)的內(nèi)核緩沖區(qū)大小,其大小通常為4k,每個(gè)扇區(qū)512b,使用8個(gè)扇區(qū)。

    管道的優(yōu)勢(shì)

    優(yōu)點(diǎn):簡(jiǎn)單

    缺點(diǎn):1. 只能單向通信,雙向可建立兩個(gè)管道

    ? 2. 只能用于父子進(jìn)程,兄弟進(jìn)程通信。

    FIFO

    ? FIFO通常叫做命名管道,以區(qū)分管道(pipe)。

    ? FIFO是linux基礎(chǔ)文件類型中的一種,但是FIFO在磁盤上沒(méi)有數(shù)據(jù)庫(kù),僅僅用來(lái)標(biāo)識(shí)內(nèi)核中的一條通道。各個(gè)進(jìn)程可以打開這個(gè)文件進(jìn)行讀寫操作,實(shí)際上是在讀寫內(nèi)核通道,這樣就實(shí)現(xiàn)了進(jìn)程間通信。

    ? int mkfifo(const char * pathname, mode_t mode); 成功:0;失敗:-1

    ? 創(chuàng)建FIFO后,可以使用open打開他,常用的io操作都可用于FIFO,如:close、read、write、unlink等。

    ? 可以在非血緣關(guān)系進(jìn)程間實(shí)現(xiàn)通訊,借助隊(duì)列實(shí)現(xiàn),不能反復(fù)讀取。

    共享存儲(chǔ)映射(共享內(nèi)存)

    文件進(jìn)程間通訊

    ? 使用文件也可以完成IPC,理論依據(jù)是,fork后,父子進(jìn)程共享文件描述符,也就是共享打開的文件。

    存儲(chǔ)映射I/O

    ? 存儲(chǔ)映射I/O(memory-mapped I/O)使一個(gè)磁盤文件與存儲(chǔ)空間中的一個(gè)緩沖區(qū)相映射。于是當(dāng)從緩沖區(qū)中取數(shù)據(jù),就相當(dāng)于讀文件中的相應(yīng)字節(jié)。與此類似,將數(shù)據(jù)存入緩沖區(qū),則相應(yīng)的字節(jié)就自動(dòng)寫入文件。這樣,就可在不使用read和write函數(shù)的情況下,使用地址(指針)完成I/O操作。

    ? 使用這種方法,首先應(yīng)通知內(nèi)核,將一個(gè)指定文件映射到存儲(chǔ)區(qū)域中。這個(gè)映射工作可以通過(guò)mmap函數(shù)來(lái)實(shí)現(xiàn)。

    mmap父子進(jìn)程通訊

    mmap函數(shù):

    void * mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);

    返回:成功:返回創(chuàng)建的映射區(qū)首地址; 失敗:MAP_FAILED宏

    參數(shù):

  • addr: 建立映射區(qū)的首地址,由Linux內(nèi)核指定。使用時(shí),直接傳遞NULL。
  • length: 要?jiǎng)?chuàng)建映射區(qū)的大小(一般與文件大小相同)。
  • prot: 映射區(qū)權(quán)限PROT_READ 、PROT_WRITE、 PROT_READ|PROT_WRITE。
  • flags: 標(biāo)志位參數(shù)(常用于設(shè)定更新物理區(qū)域、設(shè)置共享、創(chuàng)建匿名映射區(qū))
    MAP_SHARED: 會(huì)將映射區(qū)所做的操作反應(yīng)到物理設(shè)備(磁盤)上。
    MAP_PRIVATE:映射區(qū)所做的修改不會(huì)反應(yīng)到物理設(shè)備。
  • fd: 用來(lái)建立映射區(qū)的文件描述符
  • offset: 映射文件的偏移(4k的整數(shù)倍)(只映射文件的一部分)
  • #include<stdio.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<sys/mman.h>int main(void) {int fd = open("test.txt", O_CREAT | O_RDWR, 0644);char *p = NULL;if(-1 == fd){perror("open");exit(1);}int len = ftruncate(fd, 8);//拓展if(-1 == len){perror("ftruncate");exit(1);}p = mmap(NULL, 8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if(MAP_FAILED == p){perror("mmap");exit(1);}strcpy(p,"abcdefg");int ret = munmap(p, 8);if(-1 == ret){perror("munmap");exit(1);}close(fd);return 0; }

    注意:

  • 如果新創(chuàng)建的文件沒(méi)有ftruncate,則不可以創(chuàng)建一個(gè)大小為0的映射區(qū),即第二個(gè)參數(shù)不能是0。
  • 映射區(qū)指針地址自加后,munmap會(huì)失敗,即 p++ 后munmap失敗,mmap與munmap地址需一致。
  • 文件描述符如果是只讀方式打開,open使用O_RDONLY,則無(wú)法向映射區(qū)中寫入內(nèi)容。如果mmap創(chuàng)建映射區(qū)方式為MAP_PRIVATE,則無(wú)所謂,因?yàn)閙map的權(quán)限是對(duì)內(nèi)存的限制。只寫方式打開,只寫方式創(chuàng)建映射區(qū)權(quán)限也不足。創(chuàng)建映射區(qū)權(quán)限應(yīng)小于等于打開文件權(quán)限,創(chuàng)建映射區(qū)過(guò)程隱含了一次對(duì)文件的讀操作
  • offset參數(shù)必須時(shí)4k的整數(shù)倍,比如如果是1000則會(huì)報(bào)錯(cuò)。
  • 文件描述符先關(guān)閉,對(duì)mmap操作無(wú)影響,映射區(qū)創(chuàng)建后文件描述符即可關(guān)閉。
  • 父子進(jìn)程通信

    ? MAP_PRIVATE:父子進(jìn)程獨(dú)享映射,進(jìn)程內(nèi)的映射區(qū)不受其他進(jìn)程影響
    ? MAP_SHARED: 父子進(jìn)程共享映射

    #include<stdio.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<sys/mman.h> #include<sys/wait.h>int var = 100;int main(void) {int *p;pid_t pid;int fd;fd = open("temp", O_CREAT | O_RDWR | O_TRUNC, 0644);if(fd == -1){perror("open");}unlink("temp");//刪除臨時(shí)文件目錄項(xiàng),使之具備被釋放條件,所有占用該文件的進(jìn)程結(jié)束后,文件被刪除ftruncate(fd, 4);p = (int *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);// p = (int *)mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);if(p == MAP_FAILED){perror("mmap error");exit(1);}close(fd);//映射區(qū)關(guān)閉,就可以關(guān)閉文件pid = fork(); //創(chuàng)建子進(jìn)程if(0 == pid) //子進(jìn)程{*p = 2000;var = 1000;printf("child ,*p = %d, var = %d\n",*p, var);//2000,1000}else{sleep(1);printf("parent, *p = %d, var = %d\n", *p, var);//2000,100wait(NULL);//回收子進(jìn)程int ret = munmap(p, 4);if(-1 == ret){perror("munmap");exit(1);}}return 0; }

    當(dāng)以上mmap創(chuàng)建方式使用MAP_PRIVATE后,第二次打印出的信息為parent, *p = 0, var = 100

    父子進(jìn)程共享:

  • 打開的文件
  • mmap建立的映射區(qū)(但是必須使用MAP_SHARED)
  • 匿名映射

    ? 通過(guò)使用發(fā)現(xiàn),使用映射區(qū)來(lái)完成文件讀寫操作十分方便,父子進(jìn)程間通信也比較容易。但缺陷是,每次創(chuàng)建映射區(qū)一定要依賴一個(gè)文件才能實(shí)現(xiàn)。通過(guò)為了創(chuàng)建映射區(qū)要open一個(gè)temp文件,創(chuàng)建好了之后再unlink、close掉,比較麻煩。可以直接使用匿名映射來(lái)代替。其實(shí)linux系統(tǒng)為我們提供了創(chuàng)建匿名映射的方法,無(wú)需依賴文件即可創(chuàng)建映射區(qū)。同樣需要借助標(biāo)志位參數(shù)flags來(lái)指定。

    使用MAP_ANONYMOUS(或者M(jìn)AN_ANON),如:

    ? int *p=mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1 ,0);

    4隨意舉例,該位置表大小,按需求填寫即可。

    ? 需要注意的是,MAP_ANONYMOUS 和 MAP_ANON 這兩個(gè)宏是linux操作系統(tǒng)特有的宏,在類unix系統(tǒng)中無(wú)該宏定義,可使用如下兩步來(lái)完成匿名映射區(qū)的建立。/dev/zero文件可大可小,沒(méi)有限制。

    fd = open('/dev/zero', O_RDWR); p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,fd,0);

    ?

    mmap無(wú)血緣關(guān)系進(jìn)程間通訊

    ? 實(shí)質(zhì)上mmap是內(nèi)核借助文件幫我們創(chuàng)建了一個(gè)映射區(qū),多個(gè)進(jìn)程間利用該映射區(qū)完成數(shù)據(jù)傳遞。由于內(nèi)核空間多進(jìn)程共享,因此無(wú)血緣關(guān)系的進(jìn)程間也可以使用mmap來(lái)完成通信。只要設(shè)置相應(yīng)的標(biāo)志位參數(shù)即可MAP_SHARED,映射的同一個(gè)文件即可。

    讀進(jìn)程:

    #include<stdio.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<sys/mman.h> #include<sys/stat.h>struct STU{int id;char name[20];char sex; };int main(int argc, char *argv[]) {int fd;struct STU student;struct STU *mm;if(argc < 2){printf("./a.out file_shared\n");exit(1);}fd = open(argv[1], O_RDONLY);if(-1 == fd){printf("open");exit(1);}mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED , fd, 0);if(mm == MAP_FAILED){perror("mmap error");exit(1);}close(fd);while(1){printf("id=%d\tname=%s\t%c\n",mm->id,mm->name,mm->sex);sleep(2);}munmap(mm, sizeof(student));return 0; }

    寫進(jìn)程:

    #include<stdio.h> #include<fcntl.h> #include<string.h> #include<unistd.h> #include<stdlib.h> #include<sys/mman.h> #include<sys/stat.h> #include<sys/types.h>struct STU{int id;char name[20];char sex; };int main(int argc, char *argv[]) {int fd;struct STU student = {10,"xiaoming",'m'};struct STU *mm;if(argc < 2){printf("./a.out file_shared\n");exit(1);}fd = open(argv[1], O_RDWR | O_CREAT, 0664);if(-1 == fd){printf("open");exit(1);}ftruncate(fd, sizeof(student));mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED , fd, 0);if(mm == MAP_FAILED){perror("mmap error");exit(1);}close(fd);while(1){memcpy(mm, &student, sizeof(student));student.id++;sleep(1);}munmap(mm, sizeof(student));return 0; }

    練習(xí)

  • 多進(jìn)程拷貝文件
  • #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/wait.h>void err_int(int ret, const char *err) {if (ret == -1) {perror(err);exit(1);}return ; }void err_str(char *ret, const char *err) {if (ret == MAP_FAILED) {perror(err);exit(1);} }int main(int argc, char *argv[]) { int fd_src, fd_dst, ret, len, i, n;char *mp_src, *mp_dst, *tmp_srcp, *tmp_dstp;pid_t pid;struct stat sbuf;if (argc < 3 || argc > 4) {printf("Enter like this please: ./a.out file_src file_dst [process number]\n");exit(1);} else if (argc == 3) {n = 5; //用戶未指定,默認(rèn)創(chuàng)建5個(gè)子進(jìn)程} else if (argc == 4) {n = atoi(argv[3]);}//打開源文件fd_src = open(argv[1], O_RDONLY);err_int(fd_src, "open dict.txt err");//打開目的文件, 不存在則創(chuàng)建fd_dst = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0664);err_int(fd_dst, "open dict.cp err");//獲取文件大小ret = fstat(fd_src, &sbuf);err_int(ret, "fstat err");len = sbuf.st_size;if (len < n) //文件長(zhǎng)度小于進(jìn)程個(gè)數(shù)n = len;//根據(jù)文件大小拓展目標(biāo)文件ret = ftruncate(fd_dst, len);err_int(ret, "truncate fd_dst err");//為源文件創(chuàng)建映射mp_src = (char *)mmap(NULL, len, PROT_READ, MAP_SHARED, fd_src, 0);err_str(mp_src, "mmap src err");//為目標(biāo)文件創(chuàng)建映射mp_dst = (char *)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_dst, 0);err_str(mp_dst, "mmap dst err");tmp_dstp = mp_dst;tmp_srcp = mp_src;//求出每個(gè)子進(jìn)程該拷貝的字節(jié)數(shù)int bs = len / n; //每個(gè)子進(jìn)程應(yīng)該拷貝的字節(jié)數(shù)int mod = len % bs; //求出均分后余下的字節(jié)數(shù),讓最后一個(gè)子進(jìn)程處理//創(chuàng)建N個(gè)子進(jìn)程for (i = 0; i < n; i++) {if ((pid = fork()) == 0) {break;}}if (n == i) { //父進(jìn)程for (i = 0; i < n; i++)wait(NULL);} else if (i == (n-1)){ //最后一個(gè)子進(jìn)程,它多處理均分后剩余幾個(gè)字節(jié)memcpy(tmp_dstp+i*bs, tmp_srcp+i*bs, bs+mod); } else if (i == 0) { //第一個(gè)子進(jìn)程memcpy(tmp_dstp, tmp_srcp, bs); } else { //其他子進(jìn)程memcpy(tmp_dstp+i*bs, tmp_srcp+i*bs, bs); }munmap(mp_src, len);munmap(mp_dst, len);return 0; }

    博主鏈接:https://blog.csdn.net/bureau123/category_10691972.html

    2. 簡(jiǎn)易shell #include <unistd.h> #include <string.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <malloc.h>#define MAX_CMD_LENGTH 255 #define MAX_PATH_LENGTH 255 #define MAX_BUF_SIZE 4096 #define MAX_ARG_NUM 50 #define MAX_VAR_NUM 50 #define MAX_CMD_NUM 10 #define MAX_VAR_LENGTH 500#define FORK_ERROR 2 #define EXEC_ERROR 3struct cmd{struct cmd * next;int begin,end; // pos in cmdStrint argc;char lredir,rredir; 0:no redirect 1 <,> ; 2 >>char toFile[MAX_PATH_LENGTH],fromFile[MAX_PATH_LENGTH]; // redirect file pathchar *args[MAX_ARG_NUM];char bgExec; //failExec };struct cmd cmdinfo[MAX_CMD_NUM]; char cmdStr[MAX_CMD_LENGTH]; int cmdNum,varNum; char envVar[MAX_VAR_NUM][MAX_PATH_LENGTH];void Error(int ); void debug(struct cmd*); void init(struct cmd*); void setIO(struct cmd*,int ,int ); int getInput(); int parseCmds(int); int handleVar(struct cmd *,int); int getItem(char *,char *,int); int parseArgs(); int execInner(struct cmd*); int execOuter(struct cmd*);int main(){while (1){cmdNum = varNum = 0;printf("# ");fflush(stdin);int n = getInput();if(n<=0)continue; parseCmds(n);if(parseArgs()<0)continue;for(int i=0;i<cmdNum;++i){struct cmd *pcmd=cmdinfo+i, * tmp;//debug(pcmd);//pcmd = reverse(pcmd);int status = execInner(pcmd);if(status==1){/*notice!!! Use child proc to execute outer cmd, bacause exec funcs won't return when successfully execed. */pid_t pid = fork();if(pid==0)execOuter(pcmd);else if(pid<0)Error(FORK_ERROR);if(!pcmd->bgExec)wait(NULL); //background exec/* free malloced piep-cmd-node,and the first one is static , no need to free; */ pcmd=pcmd->next; while(pcmd){tmp = pcmd->next;free(pcmd);pcmd=tmp;}}}}return 0; }/* funcs implementation */ void init(struct cmd *pcmd){pcmd->bgExec=0;pcmd->argc=0;pcmd->lredir=pcmd->rredir=0;pcmd->next = NULL;pcmd->begin=pcmd->end=-1;/* // notice!!! Avoid using resudent args */for(int i=0;i<MAX_ARG_NUM;++i)pcmd->args[i]=NULL; }void Error(int n){switch(n){case FORK_ERROR:printf("fork error\n");break;case EXEC_ERROR:printf("exec error\n");break;default:printf("Error, exit ...\n");}exit(1); }int getInput(){/* multi line input */int pCmdStr=0,cur;char newline = 1;while(newline){cur = MAX_CMD_LENGTH-pCmdStr;if(cur<=0){printf("[Error]: You cmdStr is too long to exec.\n");return -1;// return -1 if cmdStr size is bigger than LENGTH}fgets(cmdStr+pCmdStr,cur,stdin);newline = 0;while(1){if(cmdStr[pCmdStr]=='\\'&&cmdStr[pCmdStr+1]=='\n'){newline=1;cmdStr[pCmdStr++]='\0';break;}else if(cmdStr[pCmdStr]=='\n'){break;}++pCmdStr;}}return pCmdStr; }int parseCmds(int n){/* clean the cmdStr and get pos of each cmd in the cmdStr (OoO) */char beginCmd=0;struct cmd * head; // use head cmd to mark background.for( int i=0;i<=n;++i){switch(cmdStr[i]){case '&':{if(cmdStr[i+1]=='\n'||cmdStr[i+1]==';'){cmdStr[i]=' ';head->bgExec=1;}}case '\t':cmdStr[i]=' ';break;case ';':{//including ';' a new cmdStrbeginCmd = 0;cmdStr[i]='\0'; cmdinfo[cmdNum++].end=i;break;}case '\n':{cmdStr[i]='\0';cmdinfo[cmdNum++].end =i;return 0;}case ' ':break;default:if(!beginCmd){beginCmd=1;head = cmdinfo+cmdNum;cmdinfo[cmdNum].begin = i;}}} }int getItem(char *dst,char*src, int p){ /* get redirect file path from the cmdStr */int ct=0;while(src[++p]==' ');if(src[p]=='\n')return -1; //no file char c;while(c=dst[ct]=src[p]){if(c==' '||c=='|'||c=='<'||c=='>'||c=='\n')break;++ct,++p;}dst[ct]='\0';return p-1; }int handleVar(struct cmd *pcmd,int n){char * arg = pcmd->args[n];int p_arg=0,p_var=0;while(arg[p_arg]){if((arg[p_arg]=='$')&&(arg[p_arg-1]!='\\')){if(arg[p_arg+1]=='{')p_arg+=2;else p_arg+=1;char *tmp=&envVar[varNum][p_var];int ct=0;while(tmp[ct]=arg[p_arg]){if(tmp[ct]=='}'){++p_arg;break;}if(tmp[ct]==' '||tmp[ct]=='\n'||tmp[ct]=='\0')break;++ct,++p_arg;}tmp[ct]='\0';tmp = getenv(tmp);for(int i=0;envVar[varNum][p_var++]=tmp[i++];);p_var-=1; //necessary}else envVar[varNum][p_var++]=arg[p_arg++];}envVar[varNum][p_var]='\0';pcmd->args[n] = envVar[varNum++];return 0; }int parseArgs(){/* get args of each cmd and create cmd-node seperated by pipe */char beginItem=0,beginQuote=0,beginDoubleQuote=0,hasVar=0,c;int begin,end;struct cmd* pcmd;for(int p=0;p<cmdNum;++p){if(beginQuote||beginItem||beginDoubleQuote){return -1; // wrong cmdStr}pcmd=&cmdinfo[p];begin = pcmd->begin,end = pcmd->end;init(pcmd);// initalize for(int i=begin;i<end;++i){c = cmdStr[i];if((c=='\"')&&(cmdStr[i-1]!='\\'&&(!beginQuote))){if(beginDoubleQuote){cmdStr[i]=beginDoubleQuote=beginItem=0;if(hasVar){hasVar=0;handleVar(pcmd,pcmd->argc-1); //note that is argc-1, not argc}}else{beginDoubleQuote=1;pcmd->args[pcmd->argc++]=cmdStr+i+1;}continue;}else if(beginDoubleQuote){if((c=='$') &&(cmdStr[i-1]!='\\')&&(!hasVar))hasVar=1;continue;}if((c=='\'')&&(cmdStr[i-1]!='\\')){if(beginQuote){cmdStr[i]=beginQuote=beginItem=0;}else{beginQuote=1;pcmd->args[pcmd->argc++]=cmdStr+i+1;}continue;}else if(beginQuote) continue;if(c=='<'||c=='>'||c=='|'){if(beginItem)beginItem=0;cmdStr[i]='\0';}if(c=='<'){if(cmdStr[i+1]=='<'){pcmd->lredir+=2; //<<cmdStr[i+1]=' ';}else{pcmd->lredir+=1; //<}int tmp = getItem(pcmd->fromFile,cmdStr,i);if(tmp>0)i = tmp;}else if(c=='>'){if(cmdStr[i+1]=='>'){pcmd->rredir+=2; //>>cmdStr[i+1]=' ';}else{pcmd->rredir+=1; //>}int tmp = getItem(pcmd->toFile,cmdStr,i);if(tmp>0)i = tmp;}else if (c=='|'){/*when encountering pipe | , create new cmd node chained after the fommer one */pcmd->end = i;pcmd->next = (struct cmd*)malloc(sizeof(struct cmd));pcmd = pcmd->next;init(pcmd);}else if(c==' '||c=='\0'){if(beginItem){beginItem=0;cmdStr[i]='\0';}}else{if(pcmd->begin==-1)pcmd->begin=i;if(!beginItem){beginItem=1;if((c=='$') &&(cmdStr[i-1]!='\\')&&(!hasVar))hasVar=1;pcmd->args[pcmd->argc++]=cmdStr+i;}}if(hasVar){hasVar=0;handleVar(pcmd,pcmd->argc-1); //note that is argc-1, not argc}}pcmd->end=end;//printf("%dfrom:%s %dto:%s\n",pcmd->lredir,pcmd->fromFile,pcmd->rredir,pcmd->toFile);} }int execInner(struct cmd* pcmd){ /*if inner cmd, {exec, return 0} else return 1 */if (!pcmd->args[0])return 0;if (strcmp(pcmd->args[0], "cd") == 0) {struct stat st;if (pcmd->args[1]){stat(pcmd->args[1],&st);if (S_ISDIR(st.st_mode))chdir(pcmd->args[1]);else{printf("[Error]: cd '%s': No such directory\n",pcmd->args[1]);return -1;}}return 0;}if (strcmp(pcmd->args[0], "pwd") == 0) {printf("%s\n",getcwd(pcmd->args[1] , MAX_PATH_LENGTH));return 0;}if (strcmp(pcmd->args[0], "unset") == 0) {for(int i=1;i<pcmd->argc;++i)unsetenv(pcmd->args[i]);return 0;}if (strcmp(pcmd->args[0], "export") == 0) {for(int i=1;i<pcmd->argc;++i){ //putenv(pcmd->args[i]);char *val,*p;for(p = pcmd->args[i];*p!='=';++p);*p='\0';val = p+1;setenv(pcmd->args[i],val,1);}return 0;}if (strcmp(pcmd->args[0], "exit") == 0)exit(0);return 1; } void setIO(struct cmd *pcmd,int rfd,int wfd){/* settle file redirect */if(pcmd->rredir>0){ // >, >>int flag ;if(pcmd->rredir==1)flag=O_WRONLY|O_TRUNC|O_CREAT; // > note: trunc is necessary!!!else flag=O_WRONLY|O_APPEND|O_CREAT; //>>int wport = open(pcmd->toFile,flag);dup2(wport,STDOUT_FILENO);close(wport);}if(pcmd->lredir>0){ //<, <<int rport = open(pcmd->fromFile,O_RDONLY);dup2(rport,STDIN_FILENO);close(rport);}/* pipe */if(rfd!=STDIN_FILENO){dup2(rfd,STDIN_FILENO);close(rfd);}if(wfd!=STDOUT_FILENO){dup2(wfd,STDOUT_FILENO);close(wfd);} } int execOuter(struct cmd * pcmd){if(!pcmd->next){setIO(pcmd,STDIN_FILENO,STDOUT_FILENO);execvp(pcmd->args[0],pcmd->args);}int fd[2];pipe(fd);pid_t pid = fork();if(pid<0){Error(FORK_ERROR);}else if (pid==0){close(fd[0]);setIO(pcmd,STDIN_FILENO,fd[1]);execvp(pcmd->args[0],pcmd->args);Error(EXEC_ERROR);}else{wait(NULL);pcmd = pcmd->next; //noticeclose(fd[1]);setIO(pcmd,fd[0],STDOUT_FILENO); execOuter(pcmd);} }

    博主鏈接:https://www.jianshu.com/p/d6d9b5b976e8

    3. 本地聊天室簡(jiǎn)單 //server #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h>#define SERVER_FIFO "/home/wx/test/sysday3/codes/SERVER_FIFO"struct client {char clientName[20];//客戶端名字int fifoDis;//私有管道的描述符 };typedef struct client CL; //用來(lái)記錄客戶機(jī)的數(shù)量 int clientlen=0; //利用數(shù)組將存儲(chǔ)客戶隊(duì)列(不方便,而且會(huì)浪費(fèi)),可以改造為鏈表(最好)。 CL clientDueue[100];struct messagePacket {int messageNo;//消息編號(hào)char senderName[20];//消息發(fā)送方char receiverName[20];//消息接收方char data[1024];//數(shù)據(jù)采用不定長(zhǎng)消息 };typedef struct messagePacket MSP;//公共管道 int serFifo; //服務(wù)器啟動(dòng)標(biāo)志 int startFlags=0;//初始化,負(fù)責(zé)初始化服務(wù)器。 void initServer(); //負(fù)責(zé)接收客戶端發(fā)送的包 void receiverPacket(); //負(fù)責(zé)將客戶端發(fā)送的包解析 void parsingPacket(MSP *msp); //負(fù)責(zé)客戶端登陸,將客戶端插入客戶隊(duì)列中,并創(chuàng)建私有管道 void clientLogin(char* loginName); //負(fù)責(zé)將消息發(fā)送到對(duì)應(yīng)的接受方 void messageSend(MSP *pMsp); //負(fù)責(zé)客戶端的退出,將客戶端從客戶隊(duì)列中刪除,并刪除創(chuàng)建的管道 void clientQuit(char* quitName); //負(fù)責(zé)關(guān)閉服務(wù)器,關(guān)閉打開的管道和刪除客戶機(jī)列表 void closeServer(); //負(fù)責(zé)處理輸入的數(shù)據(jù) void messageHanle(char* pMes);#define BUFSIZE 1068void initServer() {//將STDIN_FILENO修改為非阻塞int serFlags=fcntl(STDIN_FILENO,F_GETFL);serFlags|=O_NONBLOCK;fcntl(STDIN_FILENO,F_SETFL,serFlags);int results = mkfifo(SERVER_FIFO, 0666);if(results<0){perror("SERVER mkfifo:");exit(1);}//以非阻塞只讀的方式打開管道serFifo=open(SERVER_FIFO,O_RDONLY|O_NONBLOCK);if(serFifo<0){perror("SERVER OPEN:");exit(1);}printf("服務(wù)器已啟動(dòng),正在監(jiān)聽(tīng)...\n");startFlags=1-startFlags; }void receiverPacket() {char buf[BUFSIZE];MSP *msp;int len=read(serFifo,buf,sizeof(buf));if(len>0){msp=(MSP*)buf;parsingPacket(msp);} }void parsingPacket(MSP *msp) {//根據(jù)相應(yīng)的功能號(hào),調(diào)用相應(yīng)的函數(shù)。switch(msp->messageNo){case 0:clientLogin(msp->senderName);break;case 1:messageSend(msp);break;case 2:clientQuit(msp->senderName);break;} }void clientLogin(char* loginName) {//不能直接賦值,會(huì)造成淺拷貝strcpy(clientDueue[clientlen].clientName,loginName);char path[23]="./";strcat(path,loginName);//確保創(chuàng)建的文件的權(quán)限為分配權(quán)限umask(0);//創(chuàng)建管道m(xù)kfifo(path,0777);//將管道的文件描述符存入數(shù)組中clientDueue[clientlen].fifoDis=open(path,O_WRONLY);char buf[]="您和服務(wù)器的連接已經(jīng)成功建立,可以開始通訊了\n";write(clientDueue[clientlen].fifoDis,buf,sizeof(buf));//這里應(yīng)該將管道創(chuàng)建為臨時(shí)的,如果是使用數(shù)據(jù)庫(kù),可以創(chuàng)建為永久的unlink(path);//沒(méi)有對(duì)cientlen進(jìn)行限制++clientlen; }void clientQuit(char* quitName) {//最好是利用鏈表管理登錄的客戶機(jī)int i=0;for(i=0;i<clientlen;i++){if(strcmp(quitName,clientDueue[i].clientName)==0){//關(guān)閉對(duì)應(yīng)的私有通過(guò)close(clientDueue[i].fifoDis);clientDueue[i].fifoDis=-1;clientDueue[i].clientName[0]='\0';break;}}printf("%s已退出\n",quitName); }void messageSend(MSP *pMes) {int i=0;char* buf=(void*)pMes;if(strlen(pMes->receiverName)!=0){//單發(fā)for(i=0;i<clientlen;++i){if(strcmp(pMes->receiverName,clientDueue[i].clientName)==0){write(clientDueue[i].fifoDis,buf,BUFSIZE);break;}}}else{//群發(fā)for(i=0;i<clientlen;++i){write(clientDueue[i].fifoDis,buf,BUFSIZE);}} }void messageHanle(char* pMes) {if(strcmp(pMes,"quit-->()")==0){closeServer();}//可以繼續(xù)增加一些命令(顯示有幾個(gè)客戶端,客戶端的管道描述符等) } void closeServer() {char buf[]="服務(wù)器維護(hù)中,請(qǐng)稍后登錄。";int i=0;for(i=0;i<clientlen;++i){if(clientDueue[i].fifoDis!=-1){write(clientDueue[i].fifoDis,buf,strlen(buf));close(clientDueue[i].fifoDis);}}close(serFifo);startFlags=1-startFlags;printf("以關(guān)閉所有管道,服務(wù)器安全退出"); } int main() {initServer();char mes[1024];while(startFlags){receiverPacket();if(scanf("%s",mes)!=EOF){messageHanle(mes);}}return 0; } //Client #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define SERVER_FIFO "/home/wx/test/sysday3/codes/SERVER_FIFO"int linkFlags=0;//連接標(biāo)志 int serFifo;//公共管道文件描述符 int cliFifo;//客戶端私有端道文件描述符 char clientName[20];//客戶端名稱struct messagePacket {int messageNo;//消息編號(hào)char senderName[20];//消息發(fā)送方char receiverName[20];//消息接收方char data[1024];//數(shù)據(jù)采用定長(zhǎng)消息 };typedef struct messagePacket MSP;//初始化客戶大端 void initClient(); //登陸服務(wù)器 void loginServer(); //處理用戶輸入的數(shù)據(jù) void messageHanle(char* pMes); //向服務(wù)器發(fā)送消息 void sendSerMes(int mesNO); //向其他用戶發(fā)送消息 void sendMessage(char* receiverName,char* data); //接收消息 void receiverMes(); //關(guān)閉客戶端 void closeClient();//localClient.c #define BUFSIZE 1068void initClient() {loginServer();//將連接標(biāo)志置為1.linkFlags=1-linkFlags;//將STDIN文件屬性修改為非阻塞int flags=fcntl(STDIN_FILENO,F_GETFL);flags |= O_NONBLOCK;fcntl(STDIN_FILENO,F_SETFL,flags); }void loginServer() {printf("請(qǐng)輸入客戶端名稱(不超過(guò)20個(gè)字符):\n");//write(STDIN_FILENO,clientName,20);scanf("%s",clientName);serFifo=open(SERVER_FIFO,O_WRONLY|O_NONBLOCK);if(serFifo<0){perror("open server fifo");exit(1);}sendSerMes(0);char path[23]="./";strcat(path,clientName);//測(cè)試管道是否創(chuàng)建成功while(access(path,F_OK)!=0);cliFifo=open(path,O_RDONLY|O_NONBLOCK);if(cliFifo<0){perror("open client fifo");}printf("私有管道創(chuàng)建成功\n"); }void sendSerMes(int mesNO) {MSP msp;char *buf;msp.messageNo=mesNO;strcpy(msp.senderName,clientName);buf=(void*)&msp;write(serFifo,buf,sizeof(msp)); }void messageHanle(char* pMes) {//將“quit-->()”設(shè)置為退出消息if(strcmp(pMes,"quit-->()")==0){sendSerMes(2);closeClient();return;}//發(fā)送數(shù)據(jù)格式為:接受者姓名:消息內(nèi)容//如果數(shù)據(jù)不符合規(guī)范,則將消息轉(zhuǎn)為群發(fā)。int i=0;int j=0;char receiverName[20];char data[1024];while(pMes[i]!='\0'&&pMes[i]!=':'){receiverName[i]=pMes[i];++i;}receiverName[i]='\0';if(pMes[i]==':'){//將:跳過(guò)++i;}else{i=0;receiverName[0]='\0';}while(pMes[i]!='\0'){data[j++]=pMes[i++];}data[j]='\0';sendMessage(receiverName,data); }void sendMessage(char* receiverName,char* data) {MSP msp;char *buf;msp.messageNo=1;strcpy(msp.senderName,clientName);strcpy(msp.receiverName,receiverName);strcpy(msp.data,data);buf=(void*)&msp;write(serFifo,buf,sizeof(msp)); }void receiverMes() {char buf[BUFSIZE];int len=read(cliFifo,buf,sizeof(MSP));MSP *pMes=NULL;pMes=(void*)buf; if(len>0&&pMes->messageNo==1){printf("%s:%s\n",pMes->senderName,pMes->data);}else if(len>0){printf("系統(tǒng)提示:%s",buf);} }void closeClient() {//將連接標(biāo)志置為0linkFlags=1-linkFlags;//關(guān)閉私有管道close(cliFifo);//關(guān)閉公共管道close(serFifo);printf("以關(guān)閉所以管道,客戶端安全退出\n"); }int main() {initClient();char mesBuf[1024];while(linkFlags){//scanf()默認(rèn)遇空格終止scanf("%49[^\n]",mesBuf)!=EOF//int len=write(STDIN_FILENO,mesBuf,BUFSIZE);if(scanf("%s",mesBuf)!=EOF){messageHanle(mesBuf);} receiverMes();}return 0; }

    原文鏈接:https://blog.csdn.net/qq_39038983/article/details/88418412

    3.信號(hào)

    ? 信號(hào)是信息的載體,linux環(huán)境下,經(jīng)典的通信方式,依然是主要的通信手段。

    ? 機(jī)制:A給B發(fā)送信號(hào),B收到信號(hào)之前執(zhí)行自己的代碼,收到信號(hào)后,不管執(zhí)行到程序的什么位置,都要暫停運(yùn)行,去處理信號(hào),處理完畢再繼續(xù)執(zhí)行。與硬件中斷類似,異步模式。但是信號(hào)是軟件層次上實(shí)現(xiàn)的中斷,早期被稱為軟中斷。

    ? 特質(zhì):由于信號(hào)是通過(guò)軟件的方法實(shí)現(xiàn),所以導(dǎo)致信號(hào)有很強(qiáng)的延時(shí)性。但對(duì)于用戶,不易察覺(jué)。

    ? 每個(gè)進(jìn)程收到的所有信號(hào),都是由內(nèi)核負(fù)責(zé)發(fā)送的,內(nèi)核處理。

    信號(hào)的事件和狀態(tài)

    產(chǎn)生信號(hào):

    1. 按鍵產(chǎn)生:如ctrl + c 2. 系統(tǒng)調(diào)用: 如kill 3. 軟件條件:定時(shí)器 4. 硬件異常:如非法訪問(wèn)內(nèi)存(段錯(cuò)誤)、除0 5. 命令產(chǎn)品:kill命令

    **遞達(dá):**遞送并到達(dá)進(jìn)程

    未決:產(chǎn)生和遞達(dá)之間的狀態(tài)。主要由于阻塞(屏蔽)導(dǎo)致該狀態(tài)

    信號(hào)的處理方式:

    1. 執(zhí)行默認(rèn)動(dòng)作 2. 忽略(丟棄) 3. 捕捉(調(diào)用戶處理函數(shù))

    ? linux內(nèi)核的進(jìn)程控制塊PCB是一個(gè)結(jié)構(gòu)體,task_struct,除了包含進(jìn)程id,狀態(tài),工作目錄,用戶id,組id,文件描述符表,還包含了信號(hào)相關(guān)的信息,主要指阻塞信號(hào)集和未決信號(hào)集。

    ? **阻塞信號(hào)集(信號(hào)屏蔽字):**將某些信號(hào)加入集合,對(duì)他們?cè)O(shè)置屏蔽,當(dāng)屏蔽x信號(hào)后,再收到該信號(hào),該信號(hào)的處理將推后(解除屏蔽后)。

    ? 未決信號(hào)集:

    1. 信號(hào)產(chǎn)生,未決信號(hào)集中描述該信號(hào)的位立刻翻轉(zhuǎn)為1,表示信號(hào)處于未決狀態(tài)。當(dāng)信號(hào)被處理對(duì)應(yīng)位翻轉(zhuǎn)回為0,這一過(guò)程往往非常短暫。 2. 信號(hào)產(chǎn)生后由于某些原因(主要是阻塞)不能抵達(dá)。這類信號(hào)的集合稱之為未決信號(hào)集。在屏蔽解除前,信號(hào)一直處于未決狀態(tài)。

    信號(hào)的編號(hào):

    ? 可以使用kill -l命令查看當(dāng)前系統(tǒng)可使用的信號(hào)有哪些。

    信號(hào)的四要素:

    1. 編號(hào) 2. 名稱 3.事件 4.默認(rèn)處理動(dòng)作

    可通過(guò)命令man 7 signal查看幫助文檔獲取。

    ? 默認(rèn)處理動(dòng)作:

    ? Term:終止進(jìn)程
    ? core:終止進(jìn)程,生成core文件(檢查進(jìn)程死亡原因,用戶gdb調(diào)試)
    ? stop:停止(暫停)進(jìn)程
    ? cont:繼續(xù)運(yùn)行進(jìn)程
    ? ign:忽略信號(hào)(默認(rèn)即時(shí)對(duì)該種信號(hào)忽略操作)

    ? 9-SIGKILL19-SIGSTOP信號(hào),不允許忽略和捕捉,只能執(zhí)行默認(rèn)動(dòng)作,甚至不能設(shè)置為阻塞。

    信號(hào)的產(chǎn)生

    終端按鍵產(chǎn)生的信號(hào)

    ctrl+c → 2-SIGINT(終止/終端)

    ctrl+z → 20-SIGTSTP(暫停/停止)停止終端交互進(jìn)程的運(yùn)行

    ctrl+ \ → 3-SIGQUIT(退出)

    硬件異常產(chǎn)生信號(hào)

    除0操作 → 8-SIGFPE(浮點(diǎn)數(shù)例外)

    非法訪問(wèn)內(nèi)存 → 11-SIGSEGV(段錯(cuò)誤)

    總線錯(cuò)誤 → 7-SIGBUS

    kill產(chǎn)生

    kill命令產(chǎn)生信號(hào):kill SIGKILL pid

    kill函數(shù):給指定進(jìn)程發(fā)送指定信號(hào)(不一定殺死)

    int kill(pid_t pid, int sig);成功:0,失敗-1(ID非法,信號(hào)非法,普通用戶殺init進(jìn)程等權(quán)級(jí)問(wèn)題。設(shè)置errno。

    sig:不推薦直接使用數(shù)字,應(yīng)使用宏名,因?yàn)椴煌僮飨到y(tǒng)信號(hào)編號(hào)可能不同,但名稱一致。

    pid > 0:發(fā)送信號(hào)給指定的進(jìn)程。

    pid = 0:發(fā)送信號(hào)給 與調(diào)用kill函數(shù)進(jìn)程 屬于同一進(jìn)程組的所有進(jìn)程。

    pid < 0:取|pid|發(fā)給對(duì)應(yīng)進(jìn)程組。

    pid = -1:發(fā)送給進(jìn)程有權(quán)限發(fā)送的系統(tǒng)中所有進(jìn)程。

    ? 進(jìn)程組:每個(gè)進(jìn)程都屬于一個(gè)進(jìn)程組,進(jìn)程組是一個(gè)或多個(gè)進(jìn)程集合,他們相互關(guān)聯(lián),共同完成一個(gè)實(shí)體任務(wù),每個(gè)進(jìn)程組都有一個(gè)進(jìn)程組長(zhǎng),默認(rèn)進(jìn)程組ID與進(jìn)程組長(zhǎng)ID相同。

    ? 權(quán)限保護(hù):super用戶(root)可以發(fā)送信號(hào)給任意用戶,普通用戶是不能向系統(tǒng)用戶發(fā)送信號(hào)的。kill -9(root用戶的pid)是不可行的。同樣普通用戶也不能向其他普通用戶發(fā)送信號(hào),終止其進(jìn)程。只能向自己創(chuàng)建的進(jìn)程發(fā)送信號(hào)。普通用戶基本規(guī)則:發(fā)送者實(shí)際或有效用戶ID == 接受者實(shí)際或有效用戶ID

    raise和abort

    raise:給當(dāng)前進(jìn)程發(fā)送指定信號(hào)(自己給自己發(fā)) raise(signo)== kill(getpid(),signo);

    ? int raise(int sig);成功:0,失敗:非0值

    abort:給自己發(fā)送異常終止信號(hào) 6-SIGABRT信號(hào),終止產(chǎn)生的core文件

    ? void abort(void);函數(shù)無(wú)返回

    軟件條件產(chǎn)生

    alarm函數(shù):

    ? 設(shè)置定時(shí)器(鬧鐘)。在指定seconds后,內(nèi)核會(huì)給當(dāng)前進(jìn)程發(fā)送 14-SIGALRM信號(hào)。進(jìn)程收到該信號(hào),默認(rèn)動(dòng)作終止。

    ? 每個(gè)進(jìn)程都有且只有為一個(gè)定時(shí)器。

    ? unsigned int alarm(unsigned int seconds);返回0或剩余的秒數(shù),無(wú)失敗。

    ? 常用:取消定時(shí)器alarm(0),返回舊鬧鐘余下秒數(shù)。

    ? 定時(shí),與進(jìn)程狀態(tài)無(wú)關(guān)(自然定時(shí)法)就緒、運(yùn)行、掛起(阻塞、暫停)、終止、僵尸,無(wú)論進(jìn)程處于何種狀態(tài),alarm都計(jì)時(shí)。

    ? 可以使用time a.out查看程序運(yùn)行時(shí)間,實(shí)際時(shí)間,用戶時(shí)間,內(nèi)核時(shí)間。實(shí)際時(shí)間=用戶時(shí)間+內(nèi)核時(shí)間+等待時(shí)間。

    setitimer函數(shù):

    ? 設(shè)置定時(shí)器(鬧鐘)。可以代替alarm函數(shù)。精度微秒us,可以實(shí)現(xiàn)周期定時(shí)。

    ? int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);成功:0,失敗:-1,設(shè)置errno

    ? 參數(shù):which:指定定時(shí)方式

    ? 1、自然定時(shí):ITIMER_REAL → 14-SIGALRM 計(jì)算自然時(shí)間

    ? 2、虛擬空間計(jì)時(shí)(用戶空間):ITIMER_VIRTUAL → 26-SIGVTALRM 只計(jì)算進(jìn)程占用cpu的時(shí)間

    ? 3、運(yùn)行時(shí)計(jì)時(shí)(用戶+內(nèi)核):ITIMER_PROF → 27-SIGPROF 計(jì)算占用cpu及執(zhí)行系統(tǒng)調(diào)用的時(shí)間

    struct itimerval {struct timeval it_interval;//下一次定時(shí)的值struct timeval it_value;//當(dāng)前定時(shí)的值 } struct timeval{time_t tv_sec;//秒suseconds_t tv_usec;//微妙 } #include<stdio.h> #include<stdlib.h> #include<sys/time.h>unsigned int myalarm(unsigned int sec) {struct itimerval it, oldit;int ret;it.it_value.tv_sec = sec;it.it_value.tv_usec = 0;it.it_interval.tv_sec = 0;it.it_interval.tv_usec = 0;ret = setitimer(ITIMER_REAL, &it, &oldit);if(-1 == ret){perror("setitimer");exit(1);}return oldit.it_value.tv_sec; }int main(){int i ;myalarm(1);for ( i = 0; ; i++){printf("%d\n",i);}return 0; }

    信號(hào)的捕捉

    signal函數(shù):

    ? typedef void (*sighandler_t)(int);

    ? sighandler_t signal(int signum, sighandler_t handler);

    ? 參數(shù):signum,信號(hào),可以使用宏

    ? handler,信號(hào)捕捉函數(shù)

    #include<stdio.h> #include<stdlib.h> #include<sys/time.h> #include<signal.h> #include<errno.h>typedef void (*sighandler_t)(int);void myfunc(int signo) {printf("deal signal = %d\n",signo); }int main(){struct itimerval it, oldit;sighandler_t handler;handler = signal(SIGALRM, myfunc); //注冊(cè)sigalrm信號(hào)的捕捉處理函數(shù)if(handler == SIG_ERR){perror("signal");exit(1);}int ret;it.it_value.tv_sec = 5;it.it_value.tv_usec = 0;it.it_interval.tv_sec = 3;it.it_interval.tv_usec = 0;ret = setitimer(ITIMER_REAL, &it, &oldit);if(-1 == ret){perror("setitimer");exit(1);}while(1);return 0; }

    sigaction函數(shù):

    int sigactiong(int signum, const struct sigactiong *act, struct sigaction *oldact);

    返回:成功:0;失敗:-1;設(shè)置errno

    struct sigaction{void (*sa_handler)(int);//指定信號(hào)捕捉后的處理函數(shù)名(即注冊(cè)函數(shù))。也可賦值SIG_IGN表示忽略或SIG_DFL表示執(zhí)行默認(rèn)動(dòng)作void (*sa_sigaction)(int, siginfo_t *, void *);//當(dāng)sa_flags被指定為SA_SIGINFO標(biāo)志時(shí),使用該信號(hào)處理程序(很少使用)sigset_t sa_mask;//信號(hào)處理函數(shù)期間屏蔽的信號(hào)集;調(diào)用信號(hào)處理函數(shù)時(shí),所要屏蔽的信號(hào)集合(信號(hào)屏蔽字)。注意:僅在處理函數(shù)被調(diào)用期間屏蔽生效,臨時(shí)性設(shè)置。int sa_flags;//捕捉函數(shù)處理期間對(duì)相同信號(hào)的處理;通常為0,表示使用默認(rèn)屬性。void (*sa_restorer)(void);//過(guò)時(shí),不應(yīng)該使用,棄用 }

    參數(shù):signum,信號(hào)

    ? act:傳入?yún)?shù),新的處理方式

    ? oldact:傳出參數(shù),舊的處理方式

    #include<stdio.h> #include<stdlib.h> #include<sys/time.h> #include<signal.h>void myfunc(int signo) {printf("deal signal = %d\n",signo); }int main(){struct sigaction act;act.sa_handler = myfunc;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask,SIGQUIT);act.sa_flags = 0;//自動(dòng)屏蔽本信號(hào)int ret = sigaction(SIGINT, &act, NULL);if(-1 == ret){perror("sigaction");exit(1);}while(1);return 0; }

    信號(hào)捕捉特性

    1. 進(jìn)程正常運(yùn)行時(shí),默認(rèn)PCB中有一個(gè)信號(hào)屏蔽字,假定為*,他決定了進(jìn)程自動(dòng)屏蔽哪些信號(hào)。當(dāng)注冊(cè)了某個(gè)信號(hào)捕捉函數(shù),捕捉到該信號(hào)以后,要調(diào)用該函數(shù)。而該函數(shù)有可能執(zhí)行很長(zhǎng)時(shí)間,在這期間所屏蔽的信號(hào)不由 *來(lái)指定。而是用 sa_mask來(lái)指定。調(diào)用完信號(hào)處理函數(shù),再恢復(fù)為 *。 2. 某個(gè)信號(hào)捕捉函數(shù)執(zhí)行期間,該信號(hào)自動(dòng)被屏蔽。 3. 阻塞的常規(guī)信號(hào)不支持排隊(duì),產(chǎn)生多次只記錄一次。(后32個(gè)實(shí)時(shí)信號(hào)支持排隊(duì))

    ? 被屏蔽的信號(hào)會(huì)在執(zhí)行捕捉函數(shù)執(zhí)行后執(zhí)行,如果該信號(hào)是被捕捉的信號(hào),則由捕捉函數(shù)處理,如果是其他信號(hào),則按照其默認(rèn)處理方式處理。

    信號(hào)集操作函數(shù)

    ? 內(nèi)核通過(guò)讀取未決信號(hào)集來(lái)判斷信號(hào)是否應(yīng)被處理。信號(hào)屏蔽字mask可以影響未決信號(hào)集。我們可以在應(yīng)用程序中自定義set來(lái)改變mask。以達(dá)到屏蔽指定信號(hào)的目的。

    信號(hào)集設(shè)定

    sigset_t set; //typedef unsigned log sigset_t int sigemptyset(sigset_t *set); 將某個(gè)信號(hào)集清0 成功:0;失敗-1 int sigfillset(sigset_t *set); 將某個(gè)信號(hào)集置1 成功:0;失敗-1 int sigaddset(sigset_t *set, int signum); 將某個(gè)信號(hào)加入信號(hào)集 成功:0;失敗-1 int sigdelset(sigset_t * set,int signum); 將某個(gè)信號(hào)清出信號(hào)集 成功:0;失敗-1 int sigismember(const sigset_t *set,int signum); 判斷某個(gè)信號(hào)是否在信號(hào)集中 返回值:在集合:1;不在:0;出錯(cuò)-1

    ? sigset_t 類型的本質(zhì)是位圖,但不應(yīng)該直接使用位操作,而應(yīng)該使用上述函數(shù),保證跨系統(tǒng)操作有效。

    對(duì)比認(rèn)知select函數(shù)。

    sigprocmask

    ? 用來(lái)屏蔽信號(hào)解除屏蔽也使用該函數(shù)。其本質(zhì),讀取或修改進(jìn)程的信號(hào)屏蔽字(PCB中)

    ? 嚴(yán)格注意,屏蔽信號(hào):只是將信號(hào)處理延后執(zhí)行(延至解除屏蔽);而忽略表示將信號(hào)丟處理。

    ? int sigprocmask(int how,const sigset_t *set, sigset_t *oldset);成功:0;失敗:-1,設(shè)置errno。

    ? 參數(shù):
    ? set :傳入?yún)?shù),是一個(gè)位圖,set中哪位置1,就表示當(dāng)前進(jìn)程屏蔽哪個(gè)信號(hào)。
    ? oldset:傳出參數(shù),保存舊的信號(hào)屏蔽集。
    ? how:參數(shù)取值:假設(shè)當(dāng)前的信號(hào)屏蔽字為mask

    1. SIG_BLOCK:當(dāng)how設(shè)置此值,set表示需要屏蔽的信號(hào)。相當(dāng)于mask=mask | set。2. SIG_UNBLOCK:當(dāng)how設(shè)置此值,set表示需要解除屏蔽的信號(hào)。相當(dāng)于mask=mask & ~set。3. SIG_SETMASK:當(dāng)how設(shè)置此值,set表示用于替代原始屏蔽集的新屏蔽集。相當(dāng)于mask=set。如果,調(diào)用sigprocmask解除了對(duì)當(dāng)前若干信號(hào)的阻塞,則在sigprocmask返回前,至少將其中一個(gè)信號(hào)遞達(dá)。

    sigpending函數(shù)

    ? 讀取當(dāng)前進(jìn)程的未決信號(hào)集。

    ? int sigpending(sigset_t * set);set傳出參數(shù)。成功:0;失敗:-1,設(shè)置errno

    #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<signal.h>void myfunc(sigset_t *ped) {int i = 0;for(i = 1; i < 32; i++){if(sigismember(ped,i) == 1){putchar('1');}else{putchar('0');}}printf("\n"); }int main(){sigset_t myset, oldset, ped;sigemptyset(&myset);sigaddset(&myset, SIGQUIT);sigaddset(&myset, SIGINT);int ret = sigprocmask(SIG_BLOCK, &myset, &oldset);while(1){sigpending(&ped);myfunc(&ped);sleep(1);}return 0; }

    競(jìng)態(tài)條件

    ? 也稱為時(shí)序競(jìng)態(tài)

    pause

    ? 調(diào)用該函數(shù)可以造成進(jìn)程主動(dòng)掛起,等待信號(hào)喚醒。調(diào)用該系統(tǒng)調(diào)用的進(jìn)程將處于阻塞狀態(tài)(主動(dòng)放棄cpu)直到信號(hào)遞達(dá)將其喚醒。

    ? int pause(void); 返回值:-1,并設(shè)置errno為EINTR。

    ? 返回值:

    1. 如果信號(hào)的默認(rèn)處理動(dòng)作是終止進(jìn)程,則進(jìn)程終止,pause函數(shù)沒(méi)有機(jī)會(huì)返回。 2. 如果信號(hào)的默認(rèn)處理動(dòng)作是忽略,進(jìn)程繼續(xù)處于掛起狀態(tài),pause函數(shù)不返回。 3. 如果信號(hào)的默認(rèn)處理動(dòng)作是捕捉,則 調(diào)用完信號(hào)處理函數(shù)之后,pause返回-1,errno設(shè)置為EINTR,表示被信號(hào)中斷。 4. pause收到的信號(hào)不能被屏蔽,如果被屏蔽,那么pause就不能被喚醒。. //模范sleep #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h>void catch(int singno) {; }unsigned int sleepwx(unsigned int seconds) {int ret;struct sigaction act, oldact;act.sa_handler = catch;sigemptyset(&act.sa_mask);act.sa_flags = 0;ret=sigaction(SIGALRM, &act, &oldact);if(-1 == ret){perror("sigaction");exit(1);}alarm(seconds);ret = pause();if(-1 == ret && errno == EINTR){printf("pause ok\n"); }ret = alarm(0);sigaction(SIGALRM, &oldact, NULL);//恢復(fù)alrm信號(hào)原處理方式return ret; }int main(void) {while(1){sleepwx(5);printf("sleep end!!!!!!!!!!\n"); }return 0; }

    ? pause函數(shù)使調(diào)用進(jìn)程掛起直到捕捉到一個(gè)信號(hào)。只有執(zhí)行了一個(gè)信號(hào)處理程序并從其返回時(shí),pause才返回。

    時(shí)序競(jìng)態(tài)

    ? 如果在pause之前,調(diào)用alarm之后,程序失去cpu且時(shí)間較長(zhǎng),這期間 定時(shí)結(jié)束,發(fā)送完信號(hào),再執(zhí)行pause,程序?qū)?huì)一直卡住。

    解決時(shí)序問(wèn)題

    ? 可以通過(guò)設(shè)置屏蔽SIGALRM的方法來(lái)控制程序執(zhí)行邏輯,但無(wú)論如何設(shè)置,程序都有可能在“解除信號(hào)屏蔽”與“掛起等待信號(hào)”這兩個(gè)操作間隙失去cpu資源。除非將這兩步驟合并成一個(gè)“原子操作”。sigsuspend函數(shù)具備這個(gè)功能。在對(duì)時(shí)序要求嚴(yán)格的場(chǎng)合下都應(yīng)該使用sigsuspend替換pause。

    ? int sigsuspend(const sigset_t *mask); 掛起等待信號(hào)。

    ? sigsuspend 函數(shù)調(diào)用期間,進(jìn)程信號(hào)屏蔽字由其參數(shù)mask指定。

    ? 可將某個(gè)信號(hào) 如SIGALRM從臨時(shí)信號(hào)屏蔽字mask中刪除,這樣在調(diào)用sigsuspend時(shí)將解除對(duì)該信號(hào)的屏蔽,然后掛起等待,當(dāng)sigsuspend返回時(shí),進(jìn)程的信號(hào)屏蔽字恢復(fù)為原來(lái)的值。如果原來(lái)對(duì)該信號(hào)是屏蔽態(tài),sigsuspend函數(shù)返回后仍然屏蔽該信號(hào)。

    #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h>void catch(int singno) {; }unsigned int sleepwx(unsigned int seconds) {int ret;struct sigaction act, oldact;sigset_t newmask, oldmask, suspmask;act.sa_handler = catch;sigemptyset(&act.sa_mask);act.sa_flags = 0;ret = sigaction(SIGALRM, &act, &oldact);if(-1 == ret){perror("sigaction");exit(1);}//設(shè)置阻塞信號(hào)集集,阻塞sigalrm信號(hào)sigemptyset(&newmask);sigaddset(&newmask, SIGALRM);sigprocmask(SIG_BLOCK, &newmask, &oldmask);//定時(shí)alarm(seconds);//構(gòu)造一個(gè)調(diào)用sigsuspend臨時(shí)有效的阻塞信號(hào)集,在臨時(shí)阻塞信號(hào)集里解除sigalrm的阻塞suspmask = oldmask;sigdelset(&suspmask, SIGALRM);//sigsuspend調(diào)用期間,采用臨時(shí)阻塞信號(hào)集suspmask替換原有的阻塞信號(hào)集;這個(gè)信號(hào)集中不包含sigalrm信號(hào),同時(shí)掛起等待,當(dāng)sigsuspend被信號(hào)喚醒時(shí)返回,恢復(fù)原有阻塞信號(hào)集。sigsuspend(&suspmask);ret = alarm(0);sigaction(SIGALRM, &oldact, NULL);//恢復(fù)alrm信號(hào)原處理方式sigprocmask(SIG_SETMASK, &oldmask, NULL);//解除SIG_ALRM的阻塞return ret; }int main(void) {while(1){sleepwx(5);printf("sleep end!!!!!!!!!!\n"); }return 0; }

    總結(jié):

    ? 競(jìng)態(tài)條件,跟系統(tǒng)負(fù)載有很緊密的關(guān)系,體現(xiàn)出信號(hào)的不可靠性。系統(tǒng)負(fù)載越嚴(yán)重,信號(hào)不可靠性越強(qiáng)。

    ? 不可靠由其實(shí)現(xiàn)原理所致。信號(hào)是通過(guò)軟件方式(跟內(nèi)核調(diào)度高度依賴,延時(shí)性強(qiáng)),每次系統(tǒng)調(diào)用結(jié)束后,或中斷處理結(jié)束后,需通過(guò)掃描PCB中的未決信號(hào)集,來(lái)判斷是否應(yīng)該處理某個(gè)信號(hào),當(dāng)系統(tǒng)負(fù)載過(guò)重時(shí),會(huì)出現(xiàn)時(shí)序混亂。

    ? 這種意外情況只能在編寫程序過(guò)程中,提早遇見(jiàn),主動(dòng)避免,而無(wú)法通過(guò)gdb程序調(diào)試等其他手段彌補(bǔ)。且由于該錯(cuò)誤不具規(guī)律性,后期捕捉和重現(xiàn)十分困難。

    全局變量異步IO

    ? 如下例子,去掉回調(diào)函數(shù)中的sleep后將會(huì)出現(xiàn)錯(cuò)誤。

    #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h>int n = 0, flag = 0;void sys_err(char *str) {perror(str);exit(1); }void do_sig_child(int num) {printf("i am child %d\t%d\n",getpid(), n);n += 2;flag = 1;sleep(1); }void do_sig_parent(int num) {printf("i am parent %d\t%d\n",getpid(), n);n += 2;flag = 1;sleep(1); }int main(void) {pid_t pid;struct sigaction act;if((pid = fork())<0)sys_err("fork");else if(pid > 0){n = 1;sleep(1);//子進(jìn)程可以注冊(cè)完信號(hào)act.sa_handler = do_sig_parent;sigemptyset(&act.sa_mask);act.sa_flags = 0;//注冊(cè)自己的信號(hào)捕捉函數(shù) 父進(jìn)程使用sigusr2信號(hào)sigaction(SIGUSR2, &act, NULL);do_sig_parent(0);while(1){if(flag == 1){//父進(jìn)程數(shù)數(shù)完畢kill(pid, SIGUSR1);flag = 0;//標(biāo)志已經(jīng)給子進(jìn)程發(fā)送完信號(hào)}}}else if(pid == 0){n = 2;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGUSR1, &act, NULL);while(1){if(flag == 1){//進(jìn)程數(shù)數(shù)完畢kill(pid, SIGUSR2);//如果此處失去cpu,且父進(jìn)程已經(jīng)處理完并發(fā)送來(lái)信號(hào),子進(jìn)程數(shù)完數(shù)不會(huì)再次發(fā)送信號(hào),而是執(zhí)行下面的flag=0;flag = 0;//標(biāo)志已經(jīng)給父進(jìn)程發(fā)送完信號(hào)}}}return 0; }

    ? 例子通過(guò)flag變量標(biāo)記進(jìn)程執(zhí)行進(jìn)度。flag置1表述處理完信號(hào)(數(shù)數(shù)+2)。flag置0表示給對(duì)方發(fā)送信號(hào)完成。

    ? 問(wèn)題出現(xiàn)的位置,在父、子進(jìn)程kill函數(shù)之后需要緊接著調(diào)用flag,復(fù)位0,標(biāo)記信號(hào)已經(jīng)發(fā)送。但是,這期間有可能被內(nèi)核調(diào)度,失去執(zhí)行權(quán)力,而對(duì)方獲取了執(zhí)行時(shí)間,通過(guò)發(fā)送信號(hào)回調(diào)捕捉函數(shù),從而修改了全局flag。

    ? 避免全局變量,在多個(gè)時(shí)序中進(jìn)行全局變量進(jìn)行修改。可在回調(diào)函數(shù)中發(fā)送信號(hào)。

    可/不可重入函數(shù)

    ? 一個(gè)函數(shù)在被調(diào)用執(zhí)行期間(尚未調(diào)用結(jié)束),由于某種時(shí)序又被重復(fù)調(diào)用,稱之為“重入“(類似遞歸)。根據(jù)函數(shù)實(shí)現(xiàn)的方法可分為”可重入函數(shù)“和”不可重入函數(shù)“兩種。

  • 定義可重入函數(shù),函數(shù)內(nèi)不能含有全局和靜態(tài)變量,不適用malloc和free。
  • 信號(hào)捕捉函數(shù)應(yīng)設(shè)計(jì)為可重入函數(shù)
  • 信號(hào)處理程序可以調(diào)用的可重入函數(shù)可參考man 7 signal
  • 沒(méi)有包含在上述列表中的函數(shù)大多是不可重入的,因?yàn)?br /> 使用了靜態(tài)數(shù)據(jù)結(jié)構(gòu)
    調(diào)用了 malloc 和 free
    標(biāo)準(zhǔn)的I/O函數(shù)
  • SIGCHLD信號(hào)

    SIGCHLD的產(chǎn)生條件

    ? 子進(jìn)程終止時(shí)

    ? 子進(jìn)程收到SIGSTOP信號(hào)停止時(shí)

    ? 子進(jìn)程處在停止態(tài),接收到SIGCONT后喚醒時(shí)

    借助SIGCHLD信號(hào)回收子進(jìn)程

    ? 子進(jìn)程結(jié)束運(yùn)行,其父進(jìn)程會(huì)收到SIGCHLD信號(hào)。該信號(hào)的默認(rèn)處理動(dòng)作是忽略。可以捕捉該信號(hào),在捕捉函數(shù)中完成子進(jìn)程狀態(tài)的回收。

    #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/types.h> #include<sys/wait.h>void sys_err(char *str) {perror(str);exit(1); } //一次捕捉,回收所有死亡進(jìn)程 void do_sig_child(int signo) {int status;pid_t pid;// if((pid = waitpid(0, &status, WNOHANG)) > 0){//可能同時(shí)死亡,只有一個(gè)信號(hào)while((pid = waitpid(0, &status, WNOHANG)) > 0){//盡量循環(huán)調(diào)用if(WIFEXITED(status))printf("-----------child %d exit %d \n",pid, WEXITSTATUS(status));else if(WIFSIGNALED(status))printf("child %d cancel signal %d\n",pid, WTERMSIG(status));} }int main(void) {pid_t pid;int i;//阻塞SIGCHLDfor(i = 0; i < 10; i++){if((pid = fork()) == 0){break;}else if(pid < 0)sys_err("fork");}if(0 == pid){int n = 1;while(n--){printf("child ID %d\n", getpid());sleep(1);}return i + 1;}else if(pid > 0){//阻塞SIGCHLDstruct sigaction act;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);//解除SIGCHLD阻塞while(1){printf("parent id %d \n",getpid());sleep(1);}}return 0; } #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/wait.h> #include<string.h> void do_wait(int signo) {pid_t pid;int status;while((pid = (waitpid(0, &status, WNOHANG)))>0){printf("-----------------wait child \n");if(WIFEXITED(status)){printf("child exit with %d/n", WEXITSTATUS(status));}else if(WIFSIGNALED(status)){printf("child killed by %d/n", WTERMSIG(status));}} }int main(void) {pid_t pid;int fd[2];pipe(fd);pid = fork();if(0 == pid){close(fd[1]); //子進(jìn)程從管道中讀數(shù)據(jù),關(guān)閉寫端// dup2(fd[0], STDIN_FILENO); //讓wc從管道中讀數(shù)據(jù)char bufout[256] = {'0'};int read_ret = read(fd[0],bufout,256);if(read_ret == -1){perror("write error!");exit(-1);}else{printf("read %d bytes :%s\n",read_ret,bufout);}return 0;//execlp("wc", "wc", "-l, NULL");//wc命令默認(rèn)從標(biāo)準(zhǔn)讀入取數(shù)據(jù)}else{struct sigaction act;char bufin[256] = {"hello child"};act.sa_handler = do_wait;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);close(fd[0]);//父進(jìn)程向通道中寫數(shù)據(jù),關(guān)閉讀端// dup2(fd[1], STDOUT_FILENO);write(fd[1],bufin,strlen(bufin));//execlp("ls", "ls", NULL);//ls輸出結(jié)果默認(rèn)對(duì)應(yīng)屏幕sleep(5);}return 0; }

    信號(hào)傳參

    發(fā)送信號(hào)傳參

    sigqueue函數(shù)對(duì)應(yīng)kill函數(shù),但可向指定進(jìn)程發(fā)送信號(hào)的同時(shí)攜帶參數(shù)

    int sigqueue(pid_t pid, int sig, const union sigval value); union sigval{int sival_int;void *sival_ptr; }

    ? pid :要發(fā)送進(jìn)程的進(jìn)程ID

    ? sig :要發(fā)送的信號(hào)

    ? value:攜帶的數(shù)據(jù)

    返回:成功:0;失敗:-1,設(shè)置errno;

    ? 向指定進(jìn)程發(fā)送指定信號(hào)的同時(shí),攜帶參數(shù)。但,如傳地址,需注意,不同進(jìn)程之間虛擬地址空間各自獨(dú)立,將當(dāng)前進(jìn)程地址傳遞給另一進(jìn)程沒(méi)有實(shí)際意義(可以應(yīng)用在給本進(jìn)程發(fā)送信號(hào))。

    捕捉函數(shù)傳參

    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); struct sigaction{void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t*, void*);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void); };

    ? 當(dāng)注冊(cè)信號(hào)捕捉函數(shù),希望獲得更多信號(hào)相關(guān)信息,不應(yīng)使用sa_handler而應(yīng)該使用sa_sigaction。但此時(shí)的sa_flags必須指定為SA_SIGINFO。siginfo_t是一個(gè)成員十分豐富的結(jié)構(gòu)體類型,可以攜帶各種與信號(hào)相關(guān)的數(shù)據(jù)。

    中斷系統(tǒng)調(diào)用

    系統(tǒng)調(diào)用可分為兩類:慢速系統(tǒng)調(diào)用和其他系統(tǒng)調(diào)用。

  • 慢速系統(tǒng)調(diào)用:可能會(huì)使進(jìn)程永遠(yuǎn)阻塞的一類。如果在阻塞期間收到一個(gè)信號(hào),該系統(tǒng)調(diào)用就中斷,不再繼續(xù)執(zhí)行(早期);也可以設(shè)定系統(tǒng)調(diào)用是否重啟。如,read、write、pause、wait
  • 其他系統(tǒng)調(diào)用:getpid、getppid、fork
  • 結(jié)合pause,回顧慢速系統(tǒng)調(diào)用:

    ? 慢速系統(tǒng)調(diào)用被中斷的相關(guān)行為,實(shí)際上就是pause的行為:如,read

    1. 想中斷pause,信號(hào)不能被屏蔽 1. 信號(hào)的處理方式必須時(shí)捕捉(默認(rèn)和忽略都不可以) 1. 中斷后返回-1,設(shè)置errno為eintr(表示“被信號(hào)中斷”)

    ? 可修改sa_flags參數(shù)來(lái)設(shè)置被信號(hào)中斷后系統(tǒng)調(diào)用是否重啟。SA_INTERRURT不重啟。SA_RESTART重啟。

    ? sa_flags有許多可選參數(shù),適用于不同情況。如:捕捉到信號(hào)后,在執(zhí)行捕捉函數(shù)期間,不希望自動(dòng)阻塞該信號(hào),可將sa_flags設(shè)置為SA_NODEFER,除非sa_mask中包含該信號(hào)。

    4.守護(hù)進(jìn)程

    終端

    ? 輸入輸出設(shè)備總稱

    ? 在unix系統(tǒng)中,用戶通過(guò)終端登錄系統(tǒng)后得到一個(gè)shell進(jìn)程,這個(gè)終端稱為shell進(jìn)程的控制終端,在進(jìn)程中,控制終端是保存在pcb中的信息,而fork會(huì)復(fù)制pcb中的信息,因此由shell進(jìn)程啟動(dòng)的其他進(jìn)程的控制終端也是這個(gè)終端。默認(rèn)情況下(沒(méi)有重定向),每個(gè)進(jìn)程的標(biāo)準(zhǔn)輸入輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出都指向控制終端,進(jìn)程從標(biāo)準(zhǔn)輸入讀也就是用戶的鍵盤輸入,進(jìn)程往標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出寫也就是輸出到顯示器上。信號(hào)中,在控制終端輸入一些特殊控制鍵可以給前臺(tái)進(jìn)程發(fā)信號(hào),如ctrl + c 表示SIGINT。

    ? Alt+Ctl+F1、F2、F3、F4、F5、F6 字符終端

    ? pts(pseudo terminal slave)指?jìng)谓K端

    ? Alt+F7 圖形終端

    ? SSH Telnet 網(wǎng)絡(luò)終端

    終端的啟動(dòng)流程

    ? 每個(gè)進(jìn)程都可以通過(guò)一個(gè)特殊的設(shè)備文件/dev/tty 訪問(wèn)他的控制終端。事實(shí)上每個(gè)終端設(shè)備都對(duì)應(yīng)一個(gè)不同的設(shè)備文件,/dev/tty提供了一個(gè)通用的接口,一個(gè)進(jìn)程要訪問(wèn)他的控制終端既可以通過(guò)/dev/tty,也可以通過(guò)該終端設(shè)備對(duì)應(yīng)的設(shè)備文件來(lái)訪問(wèn)。ttyname函數(shù)可以由文件描述符查出對(duì)應(yīng)的文件名,該文件描述符必須指向一個(gè)終端設(shè)備而不能是任意文件。

    ? 其步驟如下:

    ? init→fork→exec→getty→用戶輸入賬號(hào)→login→輸入密碼→exec→bash

    ? 硬件驅(qū)動(dòng)程序負(fù)責(zé)讀寫實(shí)際的硬件設(shè)備,比如從鍵盤讀入字符和把字符輸出到顯示器,線路規(guī)程(line disciline,用來(lái)過(guò)濾鍵盤輸入的內(nèi)容)像一個(gè)過(guò)濾器,對(duì)于某些特殊字符并不是讓他直接通過(guò),而是做特殊處理,比如在鍵盤上按下ctrl + z,對(duì)應(yīng)的字符并不會(huì)被用戶程序的read讀到,而是被線路規(guī)程截獲,解釋成SIGTSTP信號(hào)發(fā)送給前臺(tái)進(jìn)程,通常會(huì)使該進(jìn)程停止。線路規(guī)程應(yīng)該過(guò)濾哪些字符和哪些特殊處理是可以配置的。

    ttyname

    ? 由文件描述符查出對(duì)應(yīng)的文件名

    ? char *ttyname(int fd); 成功:終端名稱;失敗NULL,設(shè)置errno

    借助ttyname查看不同終端設(shè)備的名稱:

    #include<stdio.h> #include<unistd.h>int main(void) {printf("fd 0:%s\n",ttyname(0));//printf("fd 1:%s\n",ttyname(1));printf("fd 2:%s\n",ttyname(2));return 0; }

    網(wǎng)絡(luò)終端

    ? 虛擬終端或串口終端的數(shù)目是有限的,虛擬終端(字符控制終端)一般就是/dev/tty1 - /dev/tty6六個(gè),串口終端的數(shù)目也不超過(guò)串口的數(shù)據(jù)。然而網(wǎng)絡(luò)終端或圖形終端窗口的數(shù)目卻是不受限制的,這是通過(guò)偽終端(Pseudo TTY)實(shí)現(xiàn)的。一套偽終端由一個(gè)主設(shè)備(PTY Master)和一個(gè)從設(shè)備(PTY Slave)組成。主設(shè)備在概念上相當(dāng)于鍵盤和顯示器,只不過(guò)他不是真正的硬件而是一個(gè)內(nèi)核模塊,操作它的也不是用戶而是另外一個(gè)進(jìn)程。從設(shè)備和上面介紹的/dev/tty1 這樣的終端設(shè)備模塊類似,只不過(guò)它的底層驅(qū)動(dòng)程序不是訪問(wèn)硬件,而是訪問(wèn)主設(shè)備。網(wǎng)絡(luò)終端或圖形終端窗口的shell進(jìn)程以及它啟動(dòng)的其他進(jìn)程都會(huì)認(rèn)為自己控制終端是偽終端從設(shè)備。例如/dev/pts/0、/dev/pts/1等。

    ? TCP/IP協(xié)議棧:在數(shù)據(jù)包上添加報(bào)頭。

    ? 如果telnet客戶端和服務(wù)器之間的網(wǎng)絡(luò)延遲較大,我們會(huì)觀察到按下一個(gè)鍵之后要過(guò)幾秒鐘才能回顯到屏幕上。這說(shuō)明我們沒(méi)按一個(gè)鍵,telnet客戶端都會(huì)立刻把該字符發(fā)送到服務(wù)器,然后這個(gè)字符經(jīng)過(guò)偽終端主設(shè)備和從設(shè)備之后被shell進(jìn)程讀取,同時(shí)回顯到偽終端從設(shè)備,回顯字符再經(jīng)過(guò)偽終端主設(shè)備、telnet服務(wù)器和網(wǎng)絡(luò)發(fā)回給telnet客戶端,顯示給用戶看。每一個(gè)按鍵都會(huì)走一個(gè)來(lái)回。

    進(jìn)程組

    ? 進(jìn)程組,也叫作業(yè)。代表一個(gè)或多個(gè)進(jìn)程的集合。每個(gè)進(jìn)程都屬于一個(gè)進(jìn)程組。在waitpid函數(shù)和kill函數(shù)的參數(shù)中都可以使用。操作系統(tǒng)設(shè)計(jì)進(jìn)程組的概念,是為了簡(jiǎn)化對(duì)多個(gè)進(jìn)程的管理。

    ? 當(dāng)父進(jìn)程,創(chuàng)建子進(jìn)程的時(shí)候,默認(rèn)子進(jìn)程與父進(jìn)程屬于同一進(jìn)程組。進(jìn)程組ID等于第一個(gè)進(jìn)程ID(組長(zhǎng)進(jìn)程)。所有,組長(zhǎng)進(jìn)程標(biāo)識(shí):其進(jìn)程組ID==其進(jìn)程ID。

    ? 可以使用kill -SIGKILL -進(jìn)程組ID(負(fù)數(shù))來(lái)將整個(gè)進(jìn)程組內(nèi)的進(jìn)程全部殺死。

    ? 組長(zhǎng)進(jìn)程可以創(chuàng)建一個(gè)進(jìn)程組,創(chuàng)建該進(jìn)程組中的進(jìn)程,然后終止。只要進(jìn)程組中有一個(gè)進(jìn)程存在,進(jìn)程組就存在,與組長(zhǎng)進(jìn)程是否終止無(wú)關(guān)。

    ? 進(jìn)程組生存期:進(jìn)程組創(chuàng)建到最后一個(gè)進(jìn)程離開(終止或轉(zhuǎn)移到另一個(gè)進(jìn)程組)。

    ? 一個(gè)進(jìn)程可以為自己或子進(jìn)程設(shè)置進(jìn)程組ID。

    進(jìn)程操作函數(shù)

    getpgrp

    ? 獲取當(dāng)前進(jìn)程的進(jìn)程組ID

    ? pid_t getpgrp(void);總是返回調(diào)用者的進(jìn)程組ID。

    getpgid

    ? 獲取指定進(jìn)程的進(jìn)程組ID

    ? pid_t getpgid(pid_t pid);成功:0;失敗:-1,設(shè)置errno

    ? 如果pid=0,那么該函數(shù)作用和getpgrp一樣

    setpgid

    ? 改變進(jìn)程默認(rèn)所屬的進(jìn)程組。通常可用來(lái)加入一個(gè)現(xiàn)有的進(jìn)程組或創(chuàng)建一個(gè)新的進(jìn)程組。

    ? int setpgid(pid_t pid,pid_t pgid);成功:0;失敗:-1,設(shè)置errno

    將參數(shù)1對(duì)應(yīng)的進(jìn)程,加入?yún)?shù)2對(duì)應(yīng)的進(jìn)程組中。

    注意:

    1. 如改變子進(jìn)程為新的組,應(yīng)forkhou,exec前。 2. 權(quán)級(jí)問(wèn)題。非root進(jìn)程只能改變自己創(chuàng)建的子進(jìn)程,或有權(quán)限操作的進(jìn)程(如自己)。 #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/types.h> #include<sys/wait.h>void sys_err(char *str) {perror(str);exit(1); }int main(int argc, char *argv[]) {pid_t pid;if(argc < 2){printf("./a.out num\n");return 0;}int count = atoi(argv[1]);int i = 0;//阻塞SIGCHLDfor(i = 0; i < count; i++){if((pid = fork()) == 0){break;}else if(pid < 0)sys_err("fork");}if(0 == pid){printf("child ID %d,parent ID %d,group id %d\n", getpid(),getppid(),getpgid(0));sleep(2);}else if(pid > 0){printf("parent id %d,parent ID %d,group id %d \n",getpid(),getppid(),getpgid(0));sleep(4);pid_t cpid;while ((cpid = wait(NULL))>0){printf("child %d is over\n",cpid);}}return 0; }

    會(huì)話

    可以理解一組進(jìn)程組為會(huì)話。

    創(chuàng)建會(huì)話

    注意事項(xiàng):

  • 調(diào)用進(jìn)程不能是進(jìn)程組組長(zhǎng),該進(jìn)程變成新會(huì)話首進(jìn)程
  • 該進(jìn)程成為一個(gè)新進(jìn)程組的組長(zhǎng)進(jìn)程
  • 需有root權(quán)限(ubuntu不需要)
  • 新會(huì)話丟棄原有的控制終端,該會(huì)話沒(méi)有控制終端
  • 該調(diào)用進(jìn)程是組長(zhǎng)進(jìn)程,則出錯(cuò)返回
  • 建立新會(huì)話時(shí),先調(diào)用fork,父進(jìn)程終止,子進(jìn)程調(diào)用setsid
  • getsid:

    獲取進(jìn)程所屬的會(huì)話ID

    ? pid_t getsid(pid_t pid);成功:返回調(diào)用進(jìn)程的會(huì)話ID;失敗:-1,設(shè)置errno

    pid為0表示查看當(dāng)前進(jìn)程session ID

    ps ajx命令查看系統(tǒng)中的進(jìn)程。參數(shù)a表示不僅列當(dāng)前用戶的進(jìn)程,也列出所有其他用戶的進(jìn)程,參數(shù)x表示不僅列出有控制終端的進(jìn)程,也列出所有無(wú)控制終端的進(jìn)程,參數(shù)j表示列出與作業(yè)控制相關(guān)的信息。

    組長(zhǎng)進(jìn)程不能成為新會(huì)話首進(jìn)程,新會(huì)話首進(jìn)程必定會(huì)成為組長(zhǎng)進(jìn)程。

    setsid

    創(chuàng)建一個(gè)會(huì)話,并以自己的ID設(shè)置進(jìn)程組ID,同時(shí)也是新會(huì)話的ID。

    ? pid_t setsid(void):成功,返回調(diào)用進(jìn)程的會(huì)話ID,失敗:-1,設(shè)置errno

    調(diào)用了setsid函數(shù)的進(jìn)程,即使新的會(huì)長(zhǎng),也是新的組長(zhǎng)。

    #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<signal.h> #include<errno.h> #include<sys/types.h> #include<sys/wait.h>void sys_err(char *str) {perror(str);exit(1); }int main(void) {pid_t pid;if((pid = fork()) < 0){sys_err("fork");}else if(pid == 0){sleep(2);printf("child id %d,group id %d,sid %d\n",getpid(),getpgrp(),getsid(0));setsid();printf("child id %d,group id %d,sid %d\n",getpid(),getpgrp(),getsid(0));}return 0; }

    守護(hù)進(jìn)程

    ? Daemon(精靈)進(jìn)程,是linux中的后臺(tái)服務(wù)進(jìn)程,通常獨(dú)立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。一般采用以d結(jié)尾的名字。

    ? linux后臺(tái)的一些系統(tǒng)服務(wù)進(jìn)程,沒(méi)有控制終端,不能直接和用戶交互。不受用戶登錄、注銷的影響,一直在運(yùn)行著,他們都是守護(hù)進(jìn)程。如:預(yù)讀入緩輸出機(jī)制的實(shí)現(xiàn);ftp服務(wù)器;nfs服務(wù)器等。

    ? 創(chuàng)建守護(hù)進(jìn)程,最關(guān)鍵的一步是調(diào)用setsid函數(shù)創(chuàng)建一個(gè)新的session,并稱為session leader。

    創(chuàng)建守護(hù)進(jìn)程模型

  • 創(chuàng)建子進(jìn)程,父進(jìn)程退出
    所有工作在子進(jìn)程中進(jìn)行,形式上脫離了控制終端
  • 在子進(jìn)程中創(chuàng)建新會(huì)話
    setsid()函數(shù)
    使子進(jìn)程完全獨(dú)立出來(lái),脫離控制
  • 改變當(dāng)前目錄為根目錄
    chdir()函數(shù)
    防止占用可卸載的文件系統(tǒng)
    也可以換成其他路徑
  • 重設(shè)文件權(quán)限掩碼
    umask()函數(shù)
    防止繼承的文件創(chuàng)建屏蔽字拒絕某些權(quán)限
  • 關(guān)閉文件描述符0、1、2
    繼承的打開文件不會(huì)用到,浪費(fèi)系統(tǒng)資源,無(wú)法卸載
  • 開始執(zhí)行守護(hù)進(jìn)程核心工作
  • 守護(hù)進(jìn)程退出處理程序模型
  • #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/stat.h> #include<fcntl.h>int mydaemond(void){pid_t pid,sid;int ret;pid = fork();if(pid > 0){exit(0);}sid = setsid();ret = chdir("/home/wx");if(ret < 0){perror("chidr");exit(1);}umask(0022);// close(0);close(STDIN_FILENO);open("/dev/null",O_RDWR);dup2(0, STDOUT_FILENO);dup2(0, STDERR_FILENO); }int main(void) {mydaemond();while(1){}return 0; }

    .bashrc修改設(shè)置程序自動(dòng)啟動(dòng)。

    5.線程

    線程的概念

    什么是線程

    ? LWP:light weight process 輕量級(jí)的進(jìn)程,本質(zhì)仍然是進(jìn)程(在linux環(huán)境下)

    ? 進(jìn)程:獨(dú)立地址空間,擁有PCB

    ? 線程:也有PCB,但沒(méi)有獨(dú)立的地址空間(共享)

    ? 區(qū)別:在于是否共享地址空間 獨(dú)居(進(jìn)程);合租(線程)

    ? linux下:線程:最小的執(zhí)行單位
    ? 進(jìn)程:最小的分配資源單位,可看成是只有一個(gè)線程的進(jìn)程。

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-ukl7IkYQ-1639009614674)(G:\學(xué)習(xí)資料\視頻\linux服務(wù)器開發(fā)二-系統(tǒng)編程視頻\day06\day07.zip)]

    linux內(nèi)核線程實(shí)現(xiàn)原理

    ? 類似unix系統(tǒng)中,早期沒(méi)有線程的概念,80年代引入,借助進(jìn)程機(jī)制實(shí)現(xiàn)了線程的概念。因此在這類系統(tǒng)中,進(jìn)程和線程關(guān)系密切。

  • 輕量級(jí)進(jìn)程(light weight process),也有PCB,創(chuàng)建線程使用的低層函數(shù)和進(jìn)程一樣,都是clone,創(chuàng)建新的pcb后,進(jìn)程中多了新的pcb,有更大概率獲取cpu時(shí)間片的使用權(quán)限。
  • 從內(nèi)核里看進(jìn)程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內(nèi)存資源的三級(jí)頁(yè)表(頁(yè)目錄,頁(yè)表,物理頁(yè)面(指向內(nèi)存單元))是相同的。所以多個(gè)線程對(duì)于進(jìn)程地址空間是共享。
  • 進(jìn)程可以蛻變成線程
  • 線程可看做寄存器和棧的集合
  • 在linux下,線程是最小的執(zhí)行單位,進(jìn)程是最小的分配資源單位
  • 查看lwp號(hào)(劃分給線程時(shí)間片的依據(jù)):ps -L pid查看指定線程的lwp號(hào)。線程ID和線程號(hào)不同。

    ps -Lf 進(jìn)程ID 查看進(jìn)程內(nèi)的線程。

    線程共享資源

  • 文件描述符(可以訪問(wèn)打開的一個(gè)文件)
  • 每種信號(hào)的處理方式
  • 當(dāng)前工作目錄
  • 用戶ID和組ID
  • 內(nèi)存地址空間(.text .data .bss heap 共享庫(kù))
  • 線程非共享資源

  • 線程id
  • 處理器現(xiàn)場(chǎng)和棧指針(內(nèi)核棧)
  • 獨(dú)立的棧空間(用戶空間棧)
  • errno變量
  • 信號(hào)屏蔽字
  • 調(diào)度優(yōu)先級(jí)
  • 線程優(yōu)缺點(diǎn)

    優(yōu)點(diǎn):

  • 提高程序并發(fā)性
  • 開銷小(針對(duì)多進(jìn)程而言,同樣數(shù)目的線程開銷小于同樣數(shù)目的進(jìn)程)
  • 數(shù)據(jù)通信、共享數(shù)據(jù)方便
  • 缺點(diǎn):

  • 庫(kù)函數(shù),不穩(wěn)定
  • 調(diào)試、編寫困難、gdb不支持
  • 對(duì)信號(hào)支持不好
  • 總結(jié):

    ? 優(yōu)點(diǎn)相對(duì)突出,缺點(diǎn)不是硬傷。linux下由于實(shí)現(xiàn)方法導(dǎo)致進(jìn)程、線程差別不是很大。

    線程控制原語(yǔ)

    pthread_self

    獲取線程ID。其作用對(duì)應(yīng)進(jìn)程中g(shù)etpid()函數(shù)。

    pthread_t pthread_self(void);返回值:成功:0;失敗:無(wú)

    線程ID:pthread_t類型,本質(zhì):在linux下為無(wú)符號(hào)整數(shù)(%lu),其他系統(tǒng)中可能是結(jié)構(gòu)體實(shí)現(xiàn)。

    線程ID是進(jìn)程內(nèi)部,識(shí)別標(biāo)志。(兩個(gè)進(jìn)程間,線程ID允許相同)

    注意:不應(yīng)使用全局變量pthread_t tid,在子線程中通過(guò)pthread_create傳出參數(shù)來(lái)獲取線程ID,而應(yīng)使用pthread_self。gcc編譯時(shí),應(yīng)使用-pthread參數(shù)調(diào)用線程庫(kù)。

    pthread_create

    創(chuàng)建一個(gè)新線程。其作用,對(duì)應(yīng)進(jìn)程中的fork()函數(shù)。

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *( *start_routine)(void * ),void * arg);

    返回值:成功:0;失敗:錯(cuò)誤號(hào)。linux環(huán)境下,所有線程特點(diǎn),失敗均直接返回錯(cuò)誤號(hào)。

    參數(shù):

    ? pthread_t: 當(dāng)前l(fā)inux中可理解為 typedef unsigned long int pthread_t;

    ? 參數(shù)1:傳出參數(shù),保存系統(tǒng)為我們分配好的線程ID

    ? 參數(shù)2:通常傳NULL,表示使用線程默認(rèn)屬性。若想使用具體屬性,可修改該參數(shù)。

    ? 參數(shù)3:函數(shù)指針,指向線程主函數(shù)(線程體),該函數(shù)運(yùn)行結(jié)束,則線程結(jié)束。

    ? 參數(shù)4:線程主函數(shù)執(zhí)行期間所使用的參數(shù)。

    #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>void * thread_func(void* arg) {printf("in thread, thread_id = %lu, pid=%d\n",pthread_self(),getpid());return NULL; }int main() {pthread_t tid;int ret;printf("in main, thread_id = %lu, pid=%d\n",pthread_self(),getpid());ret = pthread_create(&tid,NULL,thread_func,NULL);if(ret != 0){printf("pthread_create error=%s\n",strerror(ret));perror("pthread_create");exit(1);}sleep(1);printf("in main, thread_id = %lu, pid=%d,son thread ID=%lu\n",pthread_self(),getpid(),tid);return 0; }

    編譯:gcc creat.c -o creat -pthread

    線程默認(rèn)共享數(shù)據(jù)段、代碼段等地址空間,常用的全局變量。而進(jìn)程不共享全局變量,只能借助mmap。

    #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>int var = 100; void * thread_func(void* arg) {var = 200;int i = (int)arg;//*((int *)arg) 此種情況回去主函數(shù)地址中取i值,i的值不定printf("in thread %d, thread_id = %lu, pid=%d\n",i,pthread_self(),getpid());return NULL; }int main() {pthread_t tid;int ret,i;for( i = 0; i < 5 ; i++){ret = pthread_create(&tid,NULL,thread_func,(void *)i);//(void *)&iif(ret != 0){perror("pthread_create");exit(1);}}sleep(1);printf("in main, var = %d\n",var);return 0; }

    pthread_exit

    將當(dāng)個(gè)線程退出

    void pthread_exit(void *retval);

    參數(shù):retval表示線程退出狀態(tài),通常傳NULL

    線程中,禁止使用exit函數(shù),會(huì)導(dǎo)致進(jìn)程內(nèi)所有線程全部退出。

    ? 在不添加sleep控制輸出順序情況下。pthread_create在循環(huán)中,近乎瞬間創(chuàng)建了五個(gè)線程,但只有一個(gè)線程有機(jī)會(huì)輸出(或者兩個(gè),也可能沒(méi)有,取決于內(nèi)核調(diào)度)如果第三個(gè)線程執(zhí)行了exit,將整個(gè)進(jìn)程退出了,全部的線程就退出了。

    #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>void * thread_func(void* arg) {int i = (int)arg;printf("in thread %d, thread_id = %lu, pid=%d\n",i,pthread_self(),getpid());return NULL;//退出線程,類似 pthread_exit(NULL)// pthread_exit(NULL);//退出線程 }int main() {pthread_t tid;int ret,i;for( i = 0; i < 5 ; i++){ret = pthread_create(&tid,NULL,thread_func,(void *)i);if(ret != 0){perror("pthread_create");exit(1);}}printf("in main thread_id = %lu, pid=%d\n",pthread_self(),getpid());pthread_exit((void*)1);//main所在的線程退出// return 0;//h會(huì)使主控進(jìn)程退出//exit(1);//也是退出進(jìn)程,在其他線程中也會(huì)退出整個(gè)進(jìn)程 }

    ? 多線程環(huán)境中,應(yīng)盡量少用,或者不適用exit函數(shù),取而代之使用pthread_exit函數(shù),將單個(gè)線程退出。任何線程里exit導(dǎo)致進(jìn)程退出,其他線程未工作結(jié)束,主控線程退出時(shí)不能return或exit。

    ? pthread_exit或者return返回的指針?biāo)赶虻膬?nèi)存單元必須時(shí)全局的或者是用malloc分配的,不能在線程函數(shù)的棧上分配,因?yàn)楫?dāng)其他線程得到這個(gè)返回指針時(shí),線程函數(shù)已經(jīng)退出了。

    ? return:返回到調(diào)用者那里去。

    ? pthread_exit():將調(diào)用該函數(shù)的線程退出

    ? exit:將進(jìn)程退出。

    pthread_join

    ? 阻塞等待線程推出,獲取線程退出狀態(tài)。他的作用對(duì)應(yīng)進(jìn)程中的waitpid()函數(shù)。

    int pthread_join(pthread_t thread, void ** retval);成功:0;失敗:錯(cuò)誤號(hào)

    參數(shù):thread:線程ID(不是指針);retval:存儲(chǔ)線程結(jié)束狀態(tài)。

    對(duì)比記憶:

    ? 進(jìn)程中:main返回值、exit參數(shù)–>int;等待子進(jìn)程結(jié)束wait函數(shù)參數(shù)–>int*
    ? 線程中:線程主函數(shù)返回值、pthread_exit–>void*;等待線程結(jié)束pthread_join函數(shù)參數(shù)–>void **

    調(diào)用該函數(shù)的線程將掛起等待,知道id為thread的線程終止。通過(guò)pthread_join得到的終止?fàn)顟B(tài)是不同的,總結(jié)如下:

    1. 如果thread線程通過(guò)return返回,retval所指向的單元里存放的是thread線程函數(shù)的返回值。 1. 如果thread線程被別的線程調(diào)用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數(shù)PTHREAD_CANCLED 1. 如果pthread線程自己調(diào)用pthread_exit終止,retval所指向的單元里存放的是傳給pthread_exit的參數(shù)。 1. 如果thread線程的終止?fàn)顟B(tài)不感興趣,可以傳NULL給retval參數(shù)。 1. 也可以在兄弟線程中回收其他線程 #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>typedef struct{int a;int b; }exit_t;void * tfn(void* arg) {exit_t *ret;ret = malloc(sizeof(exit_t));ret->a = 100;ret->b = 300;pthread_exit((void *)ret); }void * tfn2(void* arg) {exit_t *ret = (exit_t *)arg;ret->a = 100;ret->b = 300;pthread_exit((void *)ret); }int main(void) {pthread_t tid;exit_t *retval;pthread_create(&tid,NULL,tfn,NULL);pthread_join(tid, (void **)&retval);printf("a = %d, b=%d\n",retval->a,retval->b);free(retval);exit_t * ret = malloc(sizeof(exit_t));pthread_create(&tid,NULL,tfn2,(void *)ret);pthread_join(tid, (void **)&ret);printf("tfn2 a = %d, b=%d\n",ret->a,ret->b);free(ret);return 0; }

    pthread_detach

    實(shí)現(xiàn)線程分離

    int pthread_detach(pthread_t thread);成功:0;失敗:錯(cuò)誤號(hào)

    ? 線程分離狀態(tài):指定該狀態(tài),線程主動(dòng)與主控線程斷開關(guān)系。線程結(jié)束后,其退出狀態(tài)不由其他線程獲取,而直接自己自動(dòng)釋放。網(wǎng)絡(luò)、多線程服務(wù)器常用。

    ? 進(jìn)程如果有該機(jī)制,將不會(huì)產(chǎn)生僵尸進(jìn)程。僵尸進(jìn)程的產(chǎn)生主要由于進(jìn)程死后,大部分資源被釋放,一點(diǎn)殘留資源仍然存在于系統(tǒng)中,導(dǎo)致內(nèi)核認(rèn)為該進(jìn)程仍存在。

    ? 也可以使用pthread_create 函數(shù)參數(shù)2(線程屬性)來(lái)設(shè)置線程分離。

    ? 一般情況下,線程終止后,其終止?fàn)顟B(tài)一直保留到其他線程調(diào)用pthread_join獲取他的狀態(tài)為止。但是線程也可以被置為detach狀態(tài),這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止?fàn)顟B(tài)。不能對(duì)一個(gè)已經(jīng)處于detach狀態(tài)的線程調(diào)用pthread_join,這樣的調(diào)用將返回einval錯(cuò)誤。也就是說(shuō),如果已經(jīng)對(duì)一個(gè)線程調(diào)用了pthread_detach就不能再調(diào)用pthread_join了。

    #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h> #include<string.h>void * tfn(void* arg) {int n = 3;while(n--){printf("thread count %d\n",n);sleep(1);}return (void *)1; }int main(void) {pthread_t tid;void * tret;int err;#if 0pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_create(&tid, &attr, tfn,NULL); #elsepthread_create(&tid, NULL, tfn, NULL);pthread_detach(tid); #endifwhile(1){err = pthread_join(tid,&tret);printf("----------------------err=%d\n",err);if(err != 0)fprintf(stderr,"thread exit %s \n",strerror(err));elsefprintf(stderr,"thread exit code %d\n",(int)tret);sleep(1);}return 0; }

    優(yōu)點(diǎn):可以自動(dòng)清理pcb。

    pthread_cancel

    殺死(取消)線程,其作用,對(duì)應(yīng)進(jìn)程中的kill()函數(shù)

    int pthread_cancel(pthread_t thread); 成功:0;失敗:錯(cuò)誤號(hào)

    ? 線程的取消不是實(shí)時(shí)的,而有一定的延時(shí)。需要等待線程到達(dá)某個(gè)取消點(diǎn)(檢查點(diǎn))。

    ? 類似于玩游戲必須存檔,必須到達(dá)指定的場(chǎng)所才能存儲(chǔ)進(jìn)度。殺死線程也不是立刻就能完成,必須要到達(dá)取消點(diǎn)。

    ? 取消點(diǎn):是線程檢查是否被取消,并按請(qǐng)求進(jìn)行動(dòng)作的一個(gè)位置。通常是一些系統(tǒng)調(diào)用creat,open,pause,close,read,write…執(zhí)行命令man 7 pthreads可以查看具備這些取消點(diǎn)的系統(tǒng)調(diào)用列表。也可參閱APUE.12.7取消選項(xiàng)小節(jié)。

    ? 可粗略認(rèn)為一個(gè)系統(tǒng)調(diào)用(進(jìn)入內(nèi)核)即為一個(gè)取消點(diǎn)。如線程中沒(méi)有取消點(diǎn),可以通過(guò)pthread_testcancel函數(shù)自行設(shè)置幾個(gè)取消點(diǎn)。

    ? 被取消的線程,退出值定義在linux的pthread庫(kù)中。常數(shù)PTHREAD_CANCELED的值是-1.頭文件中pthread.h中他的定義:``define PTHREAD_CANCELED((void *)-1)。因此我們對(duì)一個(gè)已經(jīng)被取消的線程使用pthread_join回收時(shí),得到的返回值是-1。

    #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h>void * tfn(void* arg) {printf("thread 1 return\n");return (void *)111; }void * tfn2(void* arg) {printf("thread 2 return\n");pthread_exit((void *)222); }void * tfn3(void* arg) {while (1){printf("thread 3 : gonging to die\n");sleep(1);//以上兩句屏蔽后,線程不會(huì)被殺死// pthread_testcancel();//函數(shù)內(nèi)如果不存在取消點(diǎn),可手動(dòng)加上這個(gè)取消點(diǎn)}return (void*)666; }int main(void) {pthread_t tid;exit_t *retval;pthread_create(&tid,NULL,tfn,NULL);pthread_join(tid, (void **)&retval);printf("pthread1 return=%d\n",(int)retval);pthread_create(&tid,NULL,tfn2,NULL);pthread_join(tid, (void **)&retval);printf("pthread2 return=%d\n",(int)retval);pthread_create(&tid,NULL,tfn2,NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, (void **)&retval);printf("pthread3 return=%d\n",(int)retval);return 0; }

    pthread_equal

    比較兩個(gè)線程ID是否相等

    int pthread_equal(pthread_t t1,pthread_t t2);

    有可能linux在未來(lái)線程ID pthread_t 類型被修改為結(jié)構(gòu)體實(shí)現(xiàn)。

    控制原語(yǔ)對(duì)比

    進(jìn)程 線程

    fork pthread_create

    exit pthread_exit

    wait pthread_join

    kill pthread_cancel

    getpid pthread_self

    線程屬性

    ? linux下線程的屬性時(shí)可以根據(jù)實(shí)際項(xiàng)目需要,進(jìn)行設(shè)置,之前我們討論的線程都是采用線程的默認(rèn)屬性,默認(rèn)屬性已經(jīng)可以解決絕大多數(shù)開發(fā)時(shí)遇到的問(wèn)題。如果我們對(duì)程序的性能提出更高的要求,那么需要設(shè)置線程屬性,比如 可以通過(guò)設(shè)置線程棧的大小來(lái)降低內(nèi)存的使用,增加最大線程個(gè)數(shù)。

    typedef struct {int etachstate; //線程的分離狀態(tài)int schedpolicy; //線程調(diào)度策略struct sched_param schedparam; //線程的調(diào)度參數(shù)int inheritsched; //線程的繼承性int scope; //線程的作用域size_t guardsize; //線程棧末尾的警戒緩沖區(qū)大小int stackaddr_set; //線程的棧設(shè)置void* stackaddr; //線程棧的位置size_t stacksize; //線程棧的大小 }pthread_attr_t;

    主要結(jié)構(gòu)體成員:

  • 線程分離狀態(tài)
  • 線程棧大小(默認(rèn)平均分配)
  • 線程棧警戒緩沖區(qū)大小(位于棧末尾)
  • ? 屬性值不能直接設(shè)置,必須使用相關(guān)函數(shù)進(jìn)行操作,初始化的函數(shù)為 pthread_attr_init,這個(gè)函數(shù)必須在 pthread_create 函數(shù)之前調(diào)用。之后需用 pthread_attr_destory 函數(shù)來(lái)釋放資源。

    ? 線程屬性主要包括如下屬性:作用域(scope)、棧尺寸(stack size)、棧地址(stack address)、優(yōu)先級(jí)(priority)、分離的狀態(tài)(detached state)、調(diào)度策略和參數(shù)(scheduling policy and parameters)。默認(rèn)的屬性為非綁定、非分離、缺省的堆棧、與父進(jìn)程同樣級(jí)別的優(yōu)先級(jí)。

    線程屬性初始化

    注意:應(yīng)先初始化線程屬性,再pthread_create創(chuàng)建線程

    初始化線程屬性

    int pthread_attr_init(pthread_attr_t *attr); 成功:0;失敗:錯(cuò)誤號(hào)

    銷毀線程屬性所占用的資源

    int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失敗:錯(cuò)誤號(hào)

    線程的分離狀態(tài)

    ? 線程的分離狀態(tài)決定了一個(gè)線程以什么樣的方式來(lái)終止自己。

    ? 非分離狀態(tài):線程的默認(rèn)屬性是非分離狀態(tài),這種情況下,原有的線程等待創(chuàng)建線程結(jié)束。只有當(dāng)pthread_join()函數(shù)返回時(shí),創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。

    ? 分離狀態(tài):分離線程沒(méi)有被其他的線程所等待,自己運(yùn)行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。應(yīng)該根據(jù)自己的需要,選擇適當(dāng)?shù)姆蛛x狀態(tài)。

    ? 線程分離狀態(tài)的函數(shù):

    ? 設(shè)置線程屬性,分離or非分離

    ? int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

    ? 獲取線程屬性,分離or非分離

    ? int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

    ? 參數(shù):attr:已初始化的線程屬性

    ? detachstate:PTHREAD_CREATE_DETACHED(分離線程)PTHREAD_CREATE_JOINABLE(結(jié)合)

    ? 這里需要注意的一點(diǎn)是,如果設(shè)置一個(gè)線程為分離線程,而這個(gè)線程運(yùn)行又非常快,他很可能在pthread_create函數(shù)返回之前就終止了,他終止以后就可能將線程號(hào)和系統(tǒng)資源移交給其他的線程使用,這樣調(diào)用pthread_create的線程就得到了錯(cuò)誤的線程號(hào)。要避免這種情況可以采取一定的同步措施,最簡(jiǎn)單的方法之一是可以在被創(chuàng)建的線程里調(diào)用pthread_cond_timedwait函數(shù),讓這個(gè)線程等待一會(huì),留出足夠的時(shí)間讓函數(shù)pthread_create返回。設(shè)置一段等待時(shí)間,是在多線程編程里常用方法。但是注意不要使用諸如wait()之類的函數(shù),他們是使整個(gè)進(jìn)程睡眠,并不能解決同步線程的問(wèn)題。

    線程的棧地址

    ? POSIX.1定義了兩個(gè)常量_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE 檢測(cè)系統(tǒng)是否支持棧屬性。也可以給sysconf函數(shù)傳遞_SC_THREAD_ATTR_STACKADDR 或 _SC_THREAD_ATTR_STACKSIZE來(lái)進(jìn)行檢測(cè)。

    ? 當(dāng)進(jìn)程棧地址空間不夠用時(shí),指定新建線程使用由malloc分配的空間作為自己的棧空間。通過(guò)pthread_attr_setstack 和 pthread_attr_getstack 兩個(gè)函數(shù)分別設(shè)置和獲取線程的棧地址。

    ? int pthread_attr_setstack(pthread_attr_t *attr, void * stackaddt, size_t stacksize);成功:0;失敗:錯(cuò)誤號(hào);

    ? int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);成功:0;失敗錯(cuò)誤碼;

    參數(shù):
    ? attr: 指向一個(gè)線程屬性的指針
    ? stackaddr:返回獲取的棧地址
    ? stacksize:返回獲取的棧大小。

    #include<stdlib.h> #include<stdio.h> #include<pthread.h> #include<unistd.h> #include<string.h>#define SIZE 0x100000000void * tfn(void* arg) {while(1)sleep(1); }int main(void) {pthread_t tid;int err, detachstate, i = 1;pthread_attr_t attr;size_t stacksize;void *stackaddr;pthread_attr_init(&attr);pthread_attr_getstack(&attr, &stackaddr, &stacksize);pthread_attr_getdetachstate(&attr, &detachstate);if(detachstate == PTHREAD_CREATE_DETACHED){printf("thread detach\n");}else if(detachstate == PTHREAD_CREATE_JOINABLE){printf("thread join\n");}else{printf("error\n");}pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);while(1){//堆棧中申請(qǐng)內(nèi)容stackaddr = malloc(SIZE);if(NULL == stackaddr){perror("malloc");exit(1);}stacksize = SIZE;pthread_attr_setstack(&attr, stackaddr, stacksize);err = pthread_create(&tid, &attr,tfn,NULL);if(err!=0){printf("%s\n",strerror(err));exit(1);}printf("%d\n",i++);}pthread_attr_destroy(&attr);return 0; }

    線程的棧大小

    ? 當(dāng)系統(tǒng)中有很多線程時(shí),可能需要減少每個(gè)線程棧的默認(rèn)大小,防止進(jìn)程地址空間不夠用,當(dāng)線程調(diào)用的函數(shù)會(huì)分配很大的局部變量或者函數(shù)調(diào)用層次很深時(shí),可能需要增加線程棧默認(rèn)大小。
    ? 函數(shù) pthread_attr_getstacksize 和 pthread_attr_setstacksize 提供設(shè)置。
    ? int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); 成功:0;失敗:錯(cuò)誤號(hào)
    ? int pthread_attr_getstacksize(pthread_attr_t *attr, size_t stacksize); 成功:0;失敗:錯(cuò)誤號(hào)
    ? 參數(shù):
    ? ? attr:指向一個(gè)線程屬性的指針
    ? ? stacksize:返回線程的堆棧大小

    NPTL

    1. 查看當(dāng)前 pthread 庫(kù)版本 `getconf GNU_LIBPTHREAD_VERSION` 2. NPTL 實(shí)現(xiàn)機(jī)制(POSIX),Native POSIX Thread Library 3. 使用線程庫(kù)時(shí)gcc指定 -lpthread

    線程注意事項(xiàng)

    1. 主線程退出其他線程不退出,主線程應(yīng)調(diào)用 pthread_exit 2. 避免僵尸線程

    ? ? ? pthread_join
    ? ? ? pthread_detach
    ? ? ? pthread_create指定分離屬性
    ? ? ? 被join線程可能在join函數(shù)返回前釋放完自己的所有內(nèi)存資源,所以不應(yīng)當(dāng)返回回收線程中的值;
    ? 3. malloc和mmap 申請(qǐng)的內(nèi)存可以被其他線程釋放
    ? 4. 應(yīng)避免在多線程模型中調(diào)用fork,除非馬上exec,子進(jìn)程中只有調(diào)用fork的線程存在,其他線程在子進(jìn)程中均為pthread_exit;
    ? 5. 信號(hào)的復(fù)雜語(yǔ)義很難和多線程共存,應(yīng)避免在多線程引入信號(hào)機(jī)制

    線程同步

    同步概念

    ? 所謂同步,即同時(shí)起步,協(xié)調(diào)一致。不同的對(duì)象,對(duì)同步的理解方式略有不同。如,設(shè)備同步,是指在兩個(gè)設(shè) 備之間規(guī)定一個(gè)同步的時(shí)間參考;數(shù)據(jù)庫(kù)同步,是指讓兩個(gè)或多個(gè)數(shù)據(jù)庫(kù)內(nèi)容保持一致,或者按照需要部分一致。
    ? 在編程和通信中所說(shuō)的同步,指協(xié)同、協(xié)助、互相配合。主旨在系統(tǒng)步調(diào),按預(yù)定的先后次序運(yùn)行。

    線程同步

    ? 同步即協(xié)同步調(diào),按預(yù)定的先后次序運(yùn)行。
    ? 線程同步,指一個(gè)線程發(fā)出某一功能調(diào)用時(shí),在沒(méi)有得到結(jié)果之前,該調(diào)用不返回。同時(shí)其他線程為保證數(shù)據(jù)一致性,不能調(diào)用該功能。
    ? 競(jìng)爭(zhēng)方式訪問(wèn)共享資源。
    ? 產(chǎn)生的現(xiàn)象叫做“與時(shí)間有關(guān)的錯(cuò)誤”。為了避免這種數(shù)據(jù)混亂,線程需要同步。
    ? 同步的目的,是為了避免數(shù)據(jù)混亂,解決與時(shí)間有關(guān)的錯(cuò)誤。實(shí)際上,不僅線程間需要同步,進(jìn)程間、信號(hào)間都需要同步機(jī)制。

    ? 所有多個(gè)控制流,共同操作一個(gè)共享資源的情況,都需要同步

    數(shù)據(jù)混亂原因

  • 資源共享(獨(dú)享資源不會(huì))
  • 調(diào)度隨機(jī)(意味著數(shù)據(jù)訪問(wèn)會(huì)出現(xiàn)競(jìng)爭(zhēng))
  • 線程間缺乏必要的同步機(jī)制
  • ? 以上3點(diǎn)鐘,前兩點(diǎn)不能改變,想要提高效率,傳遞數(shù)據(jù),資源必須共享。只要共享資源,就一定會(huì)出現(xiàn)競(jìng)爭(zhēng)。只要存在競(jìng)爭(zhēng)關(guān)系,數(shù)據(jù)就很容易出現(xiàn)混亂。所以只能從第三點(diǎn)著手,使多個(gè)線程在訪問(wèn)共享資源的時(shí)候,出現(xiàn)互斥。

    互斥量mutex(互斥鎖)

    linux中提供一把互斥鎖mutex(也稱之為互斥量)。
    每個(gè)線程在對(duì)資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結(jié)束解鎖。

    主要應(yīng)用函數(shù)

    ? pthread_mutex_t 類型,其本質(zhì)是一個(gè)結(jié)構(gòu)體。為簡(jiǎn)化理解,可以忽略實(shí)現(xiàn)細(xì)節(jié),當(dāng)成整數(shù)看待。
    ? pthread_mutex_t mutex;變量mutex只有兩種取值1 、 0;

    pthread_mutex_init

    ? 初始化一個(gè)互斥鎖(互斥量),初值可看做1

    int pthread_mutex_int(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t * restrict attr);

    參數(shù)1:傳出參數(shù),調(diào)用時(shí)應(yīng)傳&mutex

    ? restrict關(guān)鍵字:只用于限制指針,告訴編譯器,所有修改該指針指向內(nèi)存中的內(nèi)容的操作,只能通過(guò)本指針完成。不能通過(guò)除本指針以外的其他變量或指針修改。

    參數(shù)2:互斥量屬性。是一個(gè)傳入?yún)?shù),通常傳NULL,選用默認(rèn)屬性(線程間共享)。

  • 靜態(tài)初始化:如果互斥鎖mutex是靜態(tài)分配的(定義在全局,或加了static關(guān)鍵字修飾),可以直接使用宏進(jìn)行初始化。pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 動(dòng)態(tài)初始化:局部變量采用動(dòng)態(tài)初始化。pthread_mutex_init(&mutex,NULL)
  • pthread_mutex_destroy

    銷毀一個(gè)互斥鎖
    int pthread_mutex_destroy(pthread_mutex_t *mutex);

    pthread_mutex_lock

    加鎖。可理解為將mutex–(或者-1)
    int pthread_mutex_lock(pthread_mutex_t *mutex)

    pthread_mutex_unlock

    解鎖。可理解為將mutex++(或者+1)
    int pthread_mutex_unlock(pthread_mutex_t *mutex);

    pthread_mutex_trylock

    嘗試加鎖,不阻塞
    int pthread_mutex_trylock(pthread_mutex_t *mutex);

    加鎖與解鎖

    lock與unlock

    ? lock嘗試加鎖,如果加鎖不成功,線程阻塞,阻塞到持有該互斥量的其他線程解鎖為止。
    ? unlock主動(dòng)解鎖函數(shù),同時(shí)將阻塞該鎖上所有線程全部喚醒,至于哪個(gè)線程先被喚醒,取決于優(yōu)先級(jí)、調(diào)度。默認(rèn)先阻塞,先喚醒。
    ? 可假想mutex鎖init成功初值為1 。lock功能時(shí)將mutex–。unlock將mutex++

    lock與trylock

    ? lock加鎖失敗會(huì)阻塞,等待鎖釋放
    ? trylock加鎖失敗直接返回錯(cuò)誤號(hào)(如,EBUSY),不阻塞。

    加鎖步驟測(cè)試

    #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h>pthread_mutex_t mutex;void *tfn(void *arg){srand(time(NULL));while(1){pthread_mutex_lock(&mutex);printf("hello ");//模擬長(zhǎng)時(shí)間操作共享資源,導(dǎo)致cpu易主,產(chǎn)生與時(shí)間有關(guān)的錯(cuò)誤sleep(rand()%3);printf("world\n");pthread_mutex_unlock(&mutex);sleep(rand()%3);// pthread_mutex_unlock(&mutex);}return NULL; }int main(void){pthread_t tid;int flag = 5;srand(time(NULL));pthread_mutex_init(&mutex,NULL);//調(diào)用成功mutex = 1pthread_create(&tid, NULL, tfn, NULL);while(flag--){pthread_mutex_lock(&mutex);printf("HELLO ");sleep(rand()%3);printf("WORLD\n");pthread_mutex_unlock(&mutex);sleep(rand()%3);// pthread_mutex_unlock(&mutex);}pthread_cancel(tid);pthread_join(tid,NULL);pthread_mutex_destroy(&mutex);return 0; }//stdout 是共享資源
  • 定義全局互斥量,初始化init(&m,NULL)互斥量,添加對(duì)應(yīng)的destroy
  • 兩個(gè)線程while中,兩次printf前后,分別加lock和unlock
  • 將unlock挪至第二個(gè)sleep后,發(fā)現(xiàn)交替現(xiàn)象很難出現(xiàn)。
    線程在操作完共享資源后本應(yīng)該立即解鎖,但修改后,線程抱著鎖睡眠。睡醒解鎖后又立即加鎖,這兩個(gè)庫(kù)函數(shù)本身不會(huì)阻塞。
    所以在這兩行代碼之間失去cpu的概率很小。因此,另外一個(gè)線程很難得到加鎖的機(jī)會(huì)
  • main中加flag=5 將flag在while中 – ,這時(shí),主線程輸出5此后 嘗試銷毀鎖,但子線程未將鎖釋放,無(wú)法完成。
  • main中加pthread_cancel()將子線程取消。
  • 結(jié)論:在訪問(wèn)共享資源前加鎖,訪問(wèn)結(jié)束后立即解鎖。鎖的“粒度”應(yīng)越小越好。

    死鎖

  • 線程試圖對(duì)一個(gè)互斥量A加鎖兩次。
  • 線程1擁有A鎖,請(qǐng)求獲取B鎖;線程2擁有B鎖,請(qǐng)求獲取A鎖。通過(guò)trylock調(diào)用調(diào)整邏輯,拿不到鎖則釋放自己的鎖。
  • 讀寫鎖

    ? 與互斥量類似,但讀寫鎖允許更高的并行性。其特征為:寫?yīng)氄?#xff0c;讀共享。

    讀寫鎖狀態(tài)

    讀寫鎖具備三種狀態(tài):

  • 讀模式下加鎖狀態(tài)(讀鎖)
  • 寫模式下加鎖狀態(tài)(寫鎖)
  • 不加鎖狀態(tài)
  • 讀寫鎖特性

  • 讀寫鎖是“寫模式加鎖時(shí)”,解鎖前,所有對(duì)該鎖加鎖的線程都會(huì)被阻塞。
  • 讀寫鎖是“讀模式加鎖時(shí)”,如果線程以讀模式對(duì)其加鎖會(huì)成功;如果線程以寫模式加鎖會(huì)阻塞。
  • 讀寫鎖是“讀模式加鎖時(shí)”,既有試圖以寫模式加鎖的線程,也有試圖也讀模式加鎖的線程。那么讀寫鎖會(huì)阻塞隨后的讀模式鎖請(qǐng)求。優(yōu)先滿足寫模式鎖。讀鎖、寫鎖并行阻塞,寫鎖優(yōu)先級(jí)高
  • ? 讀寫鎖也叫共享-獨(dú)占鎖。當(dāng)讀寫鎖以讀模式鎖住時(shí),他是以共享模式鎖住的;當(dāng)它以寫鎖模式鎖住時(shí),他是以獨(dú)占模式鎖住的。寫?yīng)氄?#xff0c;讀共享
    ? 讀寫鎖非常適合對(duì)數(shù)據(jù)結(jié)構(gòu)讀的次數(shù)遠(yuǎn)大于寫的情況。

    主要應(yīng)用函數(shù)

    函數(shù)返回都是 成功0;失敗返回錯(cuò)誤號(hào)。

    pthread_rwlock_t 類型 用于定義一個(gè)讀寫鎖變量

    pthread_rwlock_t rwlock;

    pthread_rwlock_init

    ? 初始化一把讀寫鎖
    int pthread_rwlock_init(pthread_rwlock_t * restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    ? 參數(shù)2:attr表示讀寫鎖屬性,通常使用默認(rèn)屬性,傳NULL即可。

    pthread_rwlock_destroy

    ? 銷毀一把鎖
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

    pthread_rwlock_rdlock

    ? 加讀鎖
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

    pthread_rwlock_wrlock

    ? 加寫鎖
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

    pthread_rwlock_unlock

    ? 解鎖
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

    pthread_rwlock_tryrdlock

    ? 嘗試加讀鎖,不阻塞
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

    pthread_rwlock_trywrlock

    ? 嘗試加寫鎖,不阻塞
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

    讀寫鎖實(shí)例

    #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h>int counter; pthread_rwlock_t rwlock;void *th_write(void *arg){int t;int i = (int)arg;while(1){t = counter;usleep(1000);pthread_rwlock_wrlock(&rwlock);printf("write %d:%lu counter=%d ++counter=%d\n",i,pthread_self(),t,++counter);pthread_rwlock_unlock(&rwlock);usleep(5000);}return NULL; }void * th_read(void *arg){int i = (int)arg;while (1){pthread_rwlock_rdlock(&rwlock);printf("--------------read %d:%lu:%d\n",i,pthread_self(),counter);pthread_rwlock_unlock(&rwlock);usleep(900);}return NULL; }int main(void){pthread_t tid[8];int i;pthread_rwlock_init(&rwlock,NULL);for ( i = 0; i < 3; i++){pthread_create(&tid[i],NULL,th_write,(void *)i);}for ( i = 0; i < 5; i++){pthread_create(&tid[i+3],NULL,th_read,(void *)i);}for ( i = 0; i < 8; i++){pthread_join(tid[i],NULL);}pthread_rwlock_destroy(&rwlock);return 0; }

    條件變量

    ? 條件變量本身不是鎖,但它可以造成線程阻塞。通常與互斥鎖配合使用,給多線程提供一個(gè)回合的場(chǎng)所(共享數(shù)據(jù))。

    主要應(yīng)用函數(shù):

    以下函數(shù)都是成功返回0,失敗直接返回錯(cuò)誤號(hào)。

    pthread_cond_t類型 用于定義條件變量。

    pthread_cond_t cond;

    pthread_cond_init

    初始化條件變量
    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

    pthread_cond_destroy

    銷毀條件變量
    int pthread_cond_destroy(pthread_cond_t *cond);

    pthread_cond_wait

    阻塞等待一個(gè)條件變量
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t * restrict mutex);

    函數(shù)作用

  • 阻塞等待條件變量cond(參數(shù)1)滿足
  • 釋放已掌握的互斥鎖(解鎖互斥量)相當(dāng)于 pthread_mutex_unlock(&mutex);
    兩步為一個(gè)原子操作。
  • 當(dāng)被喚醒,pthread_cond_wait函數(shù)返回時(shí),解除阻塞并重新申請(qǐng)獲取互斥鎖pthread_mutex_lock(&mutex);
  • pthread_cond_timedwait

    限時(shí)等待一個(gè)條件變量

    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutext_t *restrict mutex, const struct timespec *restrict abstime);

    參數(shù)3:

    struct timespec{time_t tv_sec;//秒long tv_nsec;//納秒 }

    形參 abstime:絕對(duì)時(shí)間。

    如:time(NULL)返回的就是絕對(duì)時(shí)間。而alarm(1)是相對(duì)時(shí)間,相對(duì)當(dāng)前時(shí)間定時(shí)1秒鐘。
    struct timespec t = {1,0};
    pthread_cond_timedwait(&cond, &mutex, &t);只能定時(shí)到1970年1月1日00:00:01秒

    正確用戶:

    time_t cur=time(NULL); //獲取當(dāng)前時(shí)間。 struct timespec t; //定義timespec結(jié)構(gòu)體變量t t.tv_sec = cur+1; //定時(shí)1秒 pthread_cond_timedwait(&cond, &mutex, &t);

    還有另外一個(gè)時(shí)間類型

    struct timeval{time_t tv_sec;//秒susecond_t tv_usec;//微妙 }

    pthread_cond_signal

    喚醒至少一個(gè)阻塞在條件變量上的線程

    int pthread_cond_signal(pthread_cond_t *cond);

    pthread_cond_broadcast

    喚醒所有阻塞在條件變量上的線程

    int pthread_cond_broadcast(pthread_cond_t * cond);

    生產(chǎn)者消費(fèi)者條件變量模型

    ? 線程同步典型的案例即為生產(chǎn)者消費(fèi)者模型,而借助條件變量來(lái)實(shí)現(xiàn)這一模型,是比較常見(jiàn)的一種方法。假定有兩個(gè)線程,一個(gè)模擬生產(chǎn)者行為,一個(gè)模擬消費(fèi)者行為。兩個(gè)線程同時(shí)操作一個(gè)共享資源(一般稱之為匯聚),生產(chǎn)向其中添加產(chǎn)品,消費(fèi)者從中消費(fèi)掉產(chǎn)品。

    #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h>struct msg{struct msg*next;int num; };struct msg* head; struct msg* mp;//靜態(tài)初始化 一個(gè)條件變量和一個(gè)互斥量 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void *consumer(void *p) {for(;;){pthread_mutex_lock(&lock);while(head==NULL){//頭指針為空,說(shuō)明沒(méi)有節(jié)點(diǎn)printf("start wait\n");pthread_cond_wait(&has_product, &lock);printf("begin gon\n");}mp = head;head = mp->next;//模擬消費(fèi)掉一個(gè)產(chǎn)品printf("consume----%d\n",mp->num);free(mp);pthread_mutex_unlock(&lock);sleep(rand()%5);} }void *producer(void *p){for(;;){pthread_mutex_lock(&lock);mp = malloc(sizeof(struct msg));mp->num = rand()%1000 + 1;//模擬生產(chǎn)一個(gè)產(chǎn)品printf("produce ----%d\n",mp->num);mp->next = head;head = mp;pthread_mutex_unlock(&lock);pthread_cond_signal(&has_product);sleep(rand()%5);} }int main(void){pthread_t pid,cid;srand(time(NULL));pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid,NULL);pthread_join(cid,NULL);return 0; }//stdout 是共享資源

    ? 在head為空時(shí),pthread_cond_wait 解鎖,阻塞;當(dāng)head有值時(shí),生產(chǎn)線程發(fā)出pthread_cond_signal pthread_cond_wait 喚醒加鎖,向后執(zhí)行。

    ? 執(zhí)行截圖:

    條件變量的優(yōu)點(diǎn)

    ? 相較于mutex而言,條件變量可以減少競(jìng)爭(zhēng)。

    ? 如果直接使用mutex,除了生產(chǎn)者、消費(fèi)者之間要競(jìng)爭(zhēng)互斥量以外,消費(fèi)者之間也需要競(jìng)爭(zhēng)互斥量,但如果匯聚(鏈表)中沒(méi)有數(shù)據(jù),消費(fèi)者之間競(jìng)爭(zhēng)互斥鎖是無(wú)意義的。有了條件變量機(jī)制以后,只有生產(chǎn)者完成生產(chǎn),才會(huì)引起消費(fèi)者之間的競(jìng)爭(zhēng)。提升了程序效率。

    信號(hào)量

    進(jìn)化版互斥鎖

    ? 由于互斥鎖粒度比較大,如果我們希望在多個(gè)線程間對(duì)某一對(duì)象的部分?jǐn)?shù)據(jù)進(jìn)行共享,使用互斥鎖是沒(méi)有辦法實(shí)現(xiàn)的,只能將整個(gè)數(shù)據(jù)對(duì)象鎖住。這樣雖然達(dá)到了多線程操作共享數(shù)據(jù)時(shí)保證數(shù)據(jù)正確性的目的,卻在無(wú)形中導(dǎo)致線程的并發(fā)性下降。線程從并行執(zhí)行,變成了串行執(zhí)行。與直接使用單進(jìn)程無(wú)異。

    ? 信號(hào)量,是相對(duì)折中的一種處理方式,既能保證同步,數(shù)據(jù)不混亂,又能提高線程并發(fā)。

    主要應(yīng)用函數(shù)

    成功返回0,失敗返回-1,同時(shí)設(shè)置errno

    sem_t 類型,本質(zhì)仍是結(jié)構(gòu)體。但應(yīng)用期間可簡(jiǎn)單看作為整數(shù),忽略實(shí)現(xiàn)細(xì)節(jié)(類似于使用文件描述符)

    sem_t sem; 規(guī)定信號(hào)量sem不能小于0,頭文件<semaphore.h>

    信號(hào)量基本操作

    sem_init

    int sem_init(sem_t *sem, int pshared, unsigned int value);

    參數(shù):

    sem:信號(hào)量

    pshared:是否允許進(jìn)程間同步;取0用于線程間;取非0用于進(jìn)程間

    value:指定信號(hào)量初值,最大線程數(shù)

    sem_destroy

    int sem_destroy(sem_t *sem);

    銷毀信號(hào)量

    sem_wait

    int sem_wait(sem_t *sem);

    加鎖,類比pthread_mutex_lock

  • 信號(hào)量大于0,則信號(hào)量–
  • 信號(hào)量等于0,造成線程阻塞
  • sem_post

    int sem_post(sem_t *sem);

    將信號(hào)量++,同時(shí)喚醒阻塞在信號(hào)量上的線程,類比pthread_mutex_unlock

    但是,由于sem_t的實(shí)現(xiàn)對(duì)用戶隱藏,所以所謂的++、–操作只能通過(guò)函數(shù)來(lái)實(shí)現(xiàn),而不能直接++、–符號(hào)。

    信號(hào)量的初值,決定了占用信號(hào)量的線程的個(gè)數(shù)。

    sem_trywait

    int sem_trywait(sem_t *sem);

    嘗試對(duì)信號(hào)量加鎖–(與sem_wait的區(qū)別類比lock和trylock)

    sem_timedwait

    生產(chǎn)者消費(fèi)者信號(hào)量模型

    #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> #include<semaphore.h>#define NUM 5int queue[NUM]; //全局?jǐn)?shù)組實(shí)現(xiàn)環(huán)形隊(duì)列 sem_t blank_number,product_number;//空格信號(hào)量,產(chǎn)品信號(hào)量void *consumer(void *p) {int i = 0;for(;;){sem_wait(&product_number); //消費(fèi)者將產(chǎn)品--,為0則阻塞等待printf("-----donsume-----%d\n",queue[i]);queue[i]=0; //模擬消費(fèi)一個(gè)產(chǎn)品sem_post(&blank_number); //將空格數(shù)量++i = (i+1)%NUM; // 借助下標(biāo)實(shí)現(xiàn)環(huán)形sleep(rand()%3);} }void *producer(void *p){int i = 0;for(;;){sem_wait(&blank_number); //生產(chǎn)者將空格--,為0則阻塞等待queue[i] = rand()%1000 + 1; //模擬生產(chǎn)一個(gè)產(chǎn)品printf("-----Produce-----%d\n",queue[i]);sem_post(&product_number); //將產(chǎn)品數(shù)量++i = (i+1)%NUM; // 借助下標(biāo)實(shí)現(xiàn)環(huán)形sleep(rand()%1);} }int main(void){pthread_t pid,cid;srand(time(NULL));sem_init(&blank_number,0,NUM);sem_init(&product_number,0,0);pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid,NULL);pthread_join(cid,NULL);sem_destroy(&blank_number);sem_destroy(&product_number);return 0; }//stdout 是共享資源

    進(jìn)程間同步

    互斥量mutex

    ? 進(jìn)程間也可以使用互斥鎖,來(lái)達(dá)到同步的目的。但應(yīng)在pthread_mutex_init初始化之前,修改其屬性為進(jìn)程間共享。mutex的屬性修改函數(shù)主要有以下幾個(gè)。

    主要應(yīng)用函數(shù)

    pthread_mutexattr_t mattr;//用于定義mutex鎖的屬性 int pthread_mutexattr_init(pthread_mutexattr_t *attr);//初始化一個(gè)mutex屬性對(duì)象 int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);//銷毀mutex屬性 對(duì)象 int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);//修改mutex屬性 /* pshared取值:線程鎖:PTHREAD_PROCESS_PRIVATE(mutex的默認(rèn)屬性即為線程鎖,進(jìn)程間私有)進(jìn)程鎖:PTHREAD_PROCESS_SHARED */

    進(jìn)程間mutex實(shí)例

    #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> #include<fcntl.h> #include<string.h> #include<sys/mman.h> #include<sys/wait.h>struct mt{int num;pthread_mutex_t mutex;pthread_mutexattr_t mutexattr; };int main(void){int i;struct mt *mm;pid_t pid; /*int fd = open("mttest",O_CREAT | O_RDWR,0777);ftruncate(fd, sizeof(*mm));mm = mmap(NULL,sizeof(*mm),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);close(fd);unlink("mttest"); */mm = mmap(NULL, sizeof(*mm),PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1,0);memset(mm, 0, sizeof(*mm));pthread_mutexattr_init(&mm->mutexattr);pthread_mutexattr_setpshared(&mm->mutexattr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(&mm->mutex,&mm->mutexattr);pid = fork();if(pid == 0){for(i = 0;i<10;i++){pthread_mutex_lock(&mm->mutex);(mm->num)++;printf("child--------------------num++ %d\n",mm->num);pthread_mutex_unlock(&mm->mutex);sleep(1);}}else if(pid > 0){for(i = 0; i < 10 ; i++){sleep(1);pthread_mutex_lock(&mm->mutex);mm->num +=2;printf("father ------------------num+=2 %d\n",mm->num);pthread_mutex_unlock(&mm->mutex);}wait(NULL);}pthread_mutexattr_destroy(&mm->mutexattr);pthread_mutex_destroy(&mm->mutex);munmap(mm,sizeof(*mm)); //釋放映射區(qū)return 0; }

    文件鎖

    ? 借助fcntl函數(shù)來(lái)實(shí)現(xiàn)鎖機(jī)制。操作文件的進(jìn)程沒(méi)有獲得鎖時(shí),可以打開,但無(wú)法執(zhí)行read、write操作。

    fcntl函數(shù):獲取、設(shè)置文件訪問(wèn)控制屬性。

    int fcntl(int fd, int cmd,../ * arg * /);

    參數(shù)2:

    ? F_SETLK(struct flock*); 設(shè)置文件鎖(trylock)

    ? F_SETLKW(struct flock*) 設(shè)置文件鎖(lock)w–>wait

    ? F_GETLK(struct flock*); 獲取文件鎖

    參數(shù)3:

    struct flock{short l_type; //鎖的類型:F_RDLCK F_WRLCK F_UNLCKshort l_whence; //偏移位置:SEEK_SET SEEK_CUR SEEK_ENDoff_t l_start; //起始偏移:1000off_t l_len; //長(zhǎng)度:0表示整個(gè)文件加鎖pid_t l_pid; //持有該鎖的進(jìn)程ID:(f_getlk only) }

    進(jìn)程間文件鎖實(shí)例

    #include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> #include<fcntl.h> #include<string.h> #include<sys/stat.h> #include<sys/types.h>void sys_err(char *str){perror(str);exit(1); };int main(int argc, char *argv[]){int fd;struct flock f_lock;if(argc < 2){printf("./a.out filename\n");exit(1);}if((fd = open(argv[1],O_RDWR))<0)sys_err("open");f_lock.l_type = F_WRLCK; //寫鎖// f_lock.l_type = F_RDLCK; //讀鎖f_lock.l_whence = SEEK_SET; //開始位置f_lock.l_start = 0;f_lock.l_len = 0; //整個(gè)文件加鎖fcntl(fd, F_SETLKW, &f_lock);printf("set lock\n");sleep(10);f_lock.l_type = F_UNLCK; //解鎖fcntl(fd, F_SETLKW, &f_lock);printf("unlock\n");close(fd);return 0; }

    打開兩個(gè)終端,同時(shí)操作相同的文件,可以看到第一個(gè)終端打印unlock后,第二個(gè)終端菜打印set lock。

    ? 多線程共享文件描述符,而給文件加鎖,是通過(guò)修改文件描述符所指向的文件結(jié)構(gòu)體中的成員變量來(lái)實(shí)現(xiàn)的。因此,多線程中無(wú)法使用文件鎖

    其他

    在 vi 中對(duì)應(yīng)的函數(shù) shift + k,進(jìn)入man文檔。

    ps aux查看進(jìn)程信息

    ps ajx查看進(jìn)程信息,包括組ID

    雙向半雙工(像微信),雙向全雙工(像電話)

    ? 段錯(cuò)誤追蹤,使用-g方式編譯文件,gdb方式運(yùn)行調(diào)試程序,直接run,程序停留的地方就是方式錯(cuò)誤的位置。(strcpy,沒(méi)有寫權(quán)限時(shí)會(huì)產(chǎn)生段錯(cuò)誤).

    strace ./a.out查看可執(zhí)行程序,執(zhí)行過(guò)程中使用的系統(tǒng)調(diào)用。

    ls > out輸出重定向,輸入內(nèi)容寫入到out文件中。

    cat < t.c輸入重定向

    文件存儲(chǔ)

    ? inode屬性,存放文件 存儲(chǔ)信息,包括大小、權(quán)限、類型、所有者、文件存儲(chǔ)指針地址(指向磁盤存儲(chǔ)的位置)。

    ? denty 目錄項(xiàng),包括文件名,inode編號(hào)。

    ? 每次創(chuàng)建一個(gè)硬鏈接,則會(huì)創(chuàng)建一個(gè)denty,這些denty里的inode編號(hào)相同,指向同一個(gè)inode節(jié)點(diǎn);

    UNIX

    1969 unix 肯湯姆森

    ? 商業(yè):IBM、APPLE、惠普、sun
    ? linux:BSD–freeBSD、紅帽、debain-ubuntu

    優(yōu)化

    從io入口,不打印到終端。

    總結(jié)

    以上是生活随笔為你收集整理的linux学习笔记 -- 系统编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    久久99精品国产麻豆 | 欧美国产亚洲日韩在线二区 | 极品嫩模高潮叫床 | 亚洲国产精品美女久久久久 | 午夜福利试看120秒体验区 | 亚洲第一无码av无码专区 | 中文字幕 人妻熟女 | 亚欧洲精品在线视频免费观看 | 无码乱肉视频免费大全合集 | 内射欧美老妇wbb | 亚洲va欧美va天堂v国产综合 | 日本精品少妇一区二区三区 | 欧美一区二区三区 | 国产va免费精品观看 | 亚洲自偷精品视频自拍 | 动漫av网站免费观看 | 男女下面进入的视频免费午夜 | 天堂久久天堂av色综合 | 欧美熟妇另类久久久久久不卡 | 内射欧美老妇wbb | 国产亲子乱弄免费视频 | 国产综合久久久久鬼色 | 乱人伦人妻中文字幕无码 | 亚洲综合色区中文字幕 | 蜜桃臀无码内射一区二区三区 | 粉嫩少妇内射浓精videos | 夜夜高潮次次欢爽av女 | 又粗又大又硬又长又爽 | 波多野结衣一区二区三区av免费 | 国产精品久久久久无码av色戒 | 国产精品久久久久久久9999 | 欧美午夜特黄aaaaaa片 | 少妇性l交大片欧洲热妇乱xxx | 中文字幕人妻无码一夲道 | 精品国产麻豆免费人成网站 | 久久久中文久久久无码 | 麻豆果冻传媒2021精品传媒一区下载 | 亚洲国产精品毛片av不卡在线 | 亚洲成av人影院在线观看 | 亚洲色偷偷偷综合网 | 久久人人爽人人爽人人片av高清 | 免费人成在线视频无码 | 无码人妻少妇伦在线电影 | 精品无码成人片一区二区98 | 国产乱人伦av在线无码 | 蜜臀av在线观看 在线欧美精品一区二区三区 | √天堂中文官网8在线 | 小泽玛莉亚一区二区视频在线 | 欧美日韩一区二区免费视频 | 人妻互换免费中文字幕 | а√天堂www在线天堂小说 | 欧美老人巨大xxxx做受 | 日日噜噜噜噜夜夜爽亚洲精品 | 久久久久亚洲精品中文字幕 | 无码任你躁久久久久久久 | 精品久久久无码中文字幕 | 亚洲综合在线一区二区三区 | 久久久久免费看成人影片 | 中文精品久久久久人妻不卡 | 无码av中文字幕免费放 | 久久久久久a亚洲欧洲av冫 | 久在线观看福利视频 | 无码人妻出轨黑人中文字幕 | 性欧美熟妇videofreesex | 国产色精品久久人妻 | 丰满人妻一区二区三区免费视频 | 中文字幕 亚洲精品 第1页 | 四虎国产精品免费久久 | 国模大胆一区二区三区 | 久久亚洲精品成人无码 | 动漫av网站免费观看 | 成人欧美一区二区三区黑人 | 国产成人无码一二三区视频 | 性欧美疯狂xxxxbbbb | 国产xxx69麻豆国语对白 | 少妇厨房愉情理9仑片视频 | 内射老妇bbwx0c0ck | 国产人妻精品一区二区三区 | 亚洲中文字幕无码中文字在线 | 丰满少妇熟乱xxxxx视频 | 水蜜桃av无码 | 亚洲日韩中文字幕在线播放 | 76少妇精品导航 | 熟妇女人妻丰满少妇中文字幕 | 天堂一区人妻无码 | 亚洲精品无码国产 | 欧美阿v高清资源不卡在线播放 | 亚洲爆乳大丰满无码专区 | 亚洲熟女一区二区三区 | 九九久久精品国产免费看小说 | 55夜色66夜色国产精品视频 | 中文字幕人成乱码熟女app | www一区二区www免费 | 黑人巨大精品欧美黑寡妇 | 欧美变态另类xxxx | 国产成人无码一二三区视频 | 亚洲精品久久久久久久久久久 | 日本又色又爽又黄的a片18禁 | 国产色xx群视频射精 | 国产免费久久精品国产传媒 | 国精品人妻无码一区二区三区蜜柚 | 欧美xxxx黑人又粗又长 | 国产人妻精品午夜福利免费 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 97无码免费人妻超级碰碰夜夜 | 玩弄中年熟妇正在播放 | 无遮挡啪啪摇乳动态图 | 久久久无码中文字幕久... | 午夜无码区在线观看 | 无码人妻丰满熟妇区五十路百度 | 国产两女互慰高潮视频在线观看 | 无码乱肉视频免费大全合集 | 日韩精品无码一区二区中文字幕 | 亚洲国产午夜精品理论片 | 色婷婷香蕉在线一区二区 | 国产午夜视频在线观看 | 午夜福利试看120秒体验区 | 99久久久无码国产精品免费 | 99精品视频在线观看免费 | a国产一区二区免费入口 | 女人被男人爽到呻吟的视频 | 精品成人av一区二区三区 | 精品国产精品久久一区免费式 | 日日夜夜撸啊撸 | 初尝人妻少妇中文字幕 | 窝窝午夜理论片影院 | 亚洲国产精品美女久久久久 | 成人免费视频视频在线观看 免费 | 亚洲精品美女久久久久久久 | 无套内谢的新婚少妇国语播放 | 97久久国产亚洲精品超碰热 | 图片区 小说区 区 亚洲五月 | 亚洲成a人一区二区三区 | 欧美xxxx黑人又粗又长 | 99久久精品国产一区二区蜜芽 | 日日天日日夜日日摸 | 久久国产精品_国产精品 | 国产精品永久免费视频 | 久久久久久九九精品久 | 水蜜桃av无码 | 少妇性俱乐部纵欲狂欢电影 | 国产精品怡红院永久免费 | 99国产欧美久久久精品 | 欧美精品无码一区二区三区 | 国产黑色丝袜在线播放 | ass日本丰满熟妇pics | 国产成人无码一二三区视频 | 久久久精品人妻久久影视 | 无码人妻久久一区二区三区不卡 | 一本色道久久综合亚洲精品不卡 | 色偷偷人人澡人人爽人人模 | 久久精品中文字幕大胸 | 亚洲中文字幕在线观看 | 国内揄拍国内精品少妇国语 | 精品成人av一区二区三区 | 麻豆果冻传媒2021精品传媒一区下载 | 亚洲中文字幕成人无码 | 男女性色大片免费网站 | 成人av无码一区二区三区 | 国产无套内射久久久国产 | 亚洲人交乣女bbw | 国产精品无码久久av | 丰满人妻被黑人猛烈进入 | 欧美老妇交乱视频在线观看 | 精品国产精品久久一区免费式 | 高清国产亚洲精品自在久久 | 成人无码视频免费播放 | 国产精品久免费的黄网站 | 鲁大师影院在线观看 | 精品乱码久久久久久久 | 樱花草在线播放免费中文 | 性欧美熟妇videofreesex | 午夜嘿嘿嘿影院 | a在线观看免费网站大全 | 国产av剧情md精品麻豆 | 国产99久久精品一区二区 | 成人亚洲精品久久久久软件 | 成人无码视频在线观看网站 | 国产口爆吞精在线视频 | 国产人妻久久精品二区三区老狼 | 人妻插b视频一区二区三区 | 久久久久成人片免费观看蜜芽 | 亚洲熟妇色xxxxx欧美老妇y | 色婷婷综合激情综在线播放 | 男人扒开女人内裤强吻桶进去 | 久久综合久久自在自线精品自 | 国产xxx69麻豆国语对白 | 色一情一乱一伦一区二区三欧美 | 高清不卡一区二区三区 | 奇米影视7777久久精品人人爽 | 色婷婷欧美在线播放内射 | 亚洲精品一区三区三区在线观看 | 国产av一区二区精品久久凹凸 | 中文久久乱码一区二区 | 久久久www成人免费毛片 | 最近的中文字幕在线看视频 | 亚洲熟熟妇xxxx | 好爽又高潮了毛片免费下载 | 98国产精品综合一区二区三区 | 欧洲精品码一区二区三区免费看 | 波多野结衣高清一区二区三区 | av香港经典三级级 在线 | 亚洲日韩一区二区 | 麻豆成人精品国产免费 | 色欲久久久天天天综合网精品 | 久久国产自偷自偷免费一区调 | 欧美人妻一区二区三区 | 1000部夫妻午夜免费 | 国产av一区二区三区最新精品 | 亚洲人成网站在线播放942 | 99re在线播放 | 欧美 丝袜 自拍 制服 另类 | 欧美激情一区二区三区成人 | 午夜嘿嘿嘿影院 | 成 人影片 免费观看 | 国产激情一区二区三区 | 鲁大师影院在线观看 | 亚洲欧美综合区丁香五月小说 | 牲欲强的熟妇农村老妇女视频 | 无码午夜成人1000部免费视频 | 国产日产欧产精品精品app | 国产精品a成v人在线播放 | 亚洲国产高清在线观看视频 | 国产亚洲日韩欧美另类第八页 | 欧美高清在线精品一区 | 久久综合给合久久狠狠狠97色 | 欧美日韩一区二区三区自拍 | 日韩 欧美 动漫 国产 制服 | 中文字幕无码av波多野吉衣 | 男女猛烈xx00免费视频试看 | 99久久精品无码一区二区毛片 | 黑人巨大精品欧美一区二区 | 亚洲a无码综合a国产av中文 | 亚洲色在线无码国产精品不卡 | 免费无码肉片在线观看 | 国产精品无码一区二区桃花视频 | 色婷婷av一区二区三区之红樱桃 | 76少妇精品导航 | 学生妹亚洲一区二区 | 狠狠躁日日躁夜夜躁2020 | 国产亚洲精品久久久久久久久动漫 | 性色欲网站人妻丰满中文久久不卡 | 日本高清一区免费中文视频 | 亚洲精品成人福利网站 | 欧美日韩综合一区二区三区 | 少妇人妻偷人精品无码视频 | 久久综合网欧美色妞网 | 精品久久8x国产免费观看 | 国产99久久精品一区二区 | 亚洲精品久久久久中文第一幕 | 天天av天天av天天透 | 亚洲va中文字幕无码久久不卡 | 国产超碰人人爽人人做人人添 | 波多野结衣乳巨码无在线观看 | 亚洲国产综合无码一区 | 强伦人妻一区二区三区视频18 | www国产精品内射老师 | 成人无码视频在线观看网站 | 人人澡人人透人人爽 | 无码精品国产va在线观看dvd | 亚洲国产欧美国产综合一区 | 国产人妻人伦精品 | 300部国产真实乱 | 久久国内精品自在自线 | 国产精品手机免费 | 18黄暴禁片在线观看 | 国内丰满熟女出轨videos | 中文字幕乱码人妻无码久久 | 99久久人妻精品免费二区 | 国产无av码在线观看 | 双乳奶水饱满少妇呻吟 | 蜜臀av无码人妻精品 | 国产综合色产在线精品 | 中文字幕无码乱人伦 | 亚洲熟妇色xxxxx欧美老妇y | 国产午夜无码视频在线观看 | 成人女人看片免费视频放人 | 天下第一社区视频www日本 | 亚洲日韩一区二区 | 久久无码专区国产精品s | 中文无码精品a∨在线观看不卡 | 亚洲色偷偷偷综合网 | 亚洲精品国偷拍自产在线观看蜜桃 | 午夜精品久久久久久久 | 色一情一乱一伦一区二区三欧美 | 夜夜夜高潮夜夜爽夜夜爰爰 | 亚洲国产av精品一区二区蜜芽 | 国产后入清纯学生妹 | 久久这里只有精品视频9 | 荡女精品导航 | 欧美大屁股xxxxhd黑色 | 中文字幕无码人妻少妇免费 | 天堂久久天堂av色综合 | 国产乱人无码伦av在线a | 日本护士xxxxhd少妇 | 噜噜噜亚洲色成人网站 | 日韩少妇内射免费播放 | 人人爽人人澡人人高潮 | 亚洲欧洲无卡二区视頻 | 免费国产成人高清在线观看网站 | 亚洲欧美国产精品专区久久 | 久久午夜夜伦鲁鲁片无码免费 | 男人的天堂2018无码 | 婷婷五月综合激情中文字幕 | 人妻少妇被猛烈进入中文字幕 | 大肉大捧一进一出好爽视频 | 无码av岛国片在线播放 | 欧美性色19p | 性生交大片免费看女人按摩摩 | 亚洲成熟女人毛毛耸耸多 | 色偷偷人人澡人人爽人人模 | 51国偷自产一区二区三区 | 日韩人妻少妇一区二区三区 | 男女下面进入的视频免费午夜 | 男人的天堂2018无码 | 97无码免费人妻超级碰碰夜夜 | 乱码午夜-极国产极内射 | 成人精品视频一区二区三区尤物 | 亚洲天堂2017无码 | 久久国产精品偷任你爽任你 | 久久国产精品二国产精品 | 国产成人av免费观看 | 国产福利视频一区二区 | 男女下面进入的视频免费午夜 | 久久精品丝袜高跟鞋 | 性生交片免费无码看人 | 国产精品资源一区二区 | 国内少妇偷人精品视频免费 | 青草视频在线播放 | 久久99精品国产麻豆 | 亚洲国产成人a精品不卡在线 | 久久人人爽人人爽人人片av高清 | 亚洲另类伦春色综合小说 | 熟妇激情内射com | 图片区 小说区 区 亚洲五月 | 久久久久成人片免费观看蜜芽 | 亚洲大尺度无码无码专区 | 国产人妻大战黑人第1集 | 亚洲一区二区三区国产精华液 | 国产疯狂伦交大片 | 免费乱码人妻系列无码专区 | 亚洲日韩精品欧美一区二区 | 久久久精品456亚洲影院 | 久久亚洲精品成人无码 | 狠狠色噜噜狠狠狠7777奇米 | 久久人人爽人人爽人人片ⅴ | 真人与拘做受免费视频 | 成人女人看片免费视频放人 | 粗大的内捧猛烈进出视频 | 自拍偷自拍亚洲精品被多人伦好爽 | а√资源新版在线天堂 | 成人性做爰aaa片免费看不忠 | 99麻豆久久久国产精品免费 | 国产美女极度色诱视频www | 巨爆乳无码视频在线观看 | 亚洲啪av永久无码精品放毛片 | 97精品人妻一区二区三区香蕉 | 日本va欧美va欧美va精品 | 久久精品人妻少妇一区二区三区 | 丰满护士巨好爽好大乳 | 少妇无套内谢久久久久 | 国产午夜无码精品免费看 | 最近免费中文字幕中文高清百度 | 天天爽夜夜爽夜夜爽 | 国产精品福利视频导航 | 久久这里只有精品视频9 | 成在人线av无码免观看麻豆 | 日本爽爽爽爽爽爽在线观看免 | 国产人妻人伦精品 | 奇米影视888欧美在线观看 | 377p欧洲日本亚洲大胆 | 欧美老人巨大xxxx做受 | 樱花草在线播放免费中文 | 牲欲强的熟妇农村老妇女视频 | 久久国产精品_国产精品 | 日本饥渴人妻欲求不满 | 在线看片无码永久免费视频 | 国产精品高潮呻吟av久久4虎 | 成人欧美一区二区三区 | 55夜色66夜色国产精品视频 | √天堂资源地址中文在线 | 免费播放一区二区三区 | av人摸人人人澡人人超碰下载 | 国产区女主播在线观看 | ass日本丰满熟妇pics | 日日鲁鲁鲁夜夜爽爽狠狠 | 青草视频在线播放 | 久久97精品久久久久久久不卡 | 午夜精品久久久久久久久 | 天堂久久天堂av色综合 | 欧美人与动性行为视频 | 亚洲精品成a人在线观看 | 国产精品亚洲专区无码不卡 | 国产精品.xx视频.xxtv | 初尝人妻少妇中文字幕 | 亚洲国产精品无码久久久久高潮 | 免费无码的av片在线观看 | 巨爆乳无码视频在线观看 | 18无码粉嫩小泬无套在线观看 | 欧美午夜特黄aaaaaa片 | 亚洲午夜久久久影院 | 国产香蕉尹人视频在线 | 狠狠噜狠狠狠狠丁香五月 | 精品国偷自产在线视频 | 国产精品久久久久久无码 | 偷窥村妇洗澡毛毛多 | 99久久人妻精品免费二区 | 无码乱肉视频免费大全合集 | 国产午夜福利亚洲第一 | 一本色道久久综合狠狠躁 | 高潮毛片无遮挡高清免费 | 成人免费视频在线观看 | 亚洲の无码国产の无码步美 | √8天堂资源地址中文在线 | 人妻无码αv中文字幕久久琪琪布 | 无码任你躁久久久久久久 | 成人无码视频免费播放 | 国产人妖乱国产精品人妖 | 成人影院yy111111在线观看 | 5858s亚洲色大成网站www | 国产一精品一av一免费 | 领导边摸边吃奶边做爽在线观看 | 在教室伦流澡到高潮hnp视频 | 日本免费一区二区三区最新 | 丰满人妻被黑人猛烈进入 | 国产在线aaa片一区二区99 | 久久99久久99精品中文字幕 | 国产成人精品无码播放 | 丰满少妇熟乱xxxxx视频 | 永久免费观看国产裸体美女 | 欧美第一黄网免费网站 | 波多野结衣av在线观看 | 98国产精品综合一区二区三区 | 欧洲精品码一区二区三区免费看 | 国产av久久久久精东av | 色狠狠av一区二区三区 | 女人被男人躁得好爽免费视频 | 东京热男人av天堂 | 好男人www社区 | 黑森林福利视频导航 | 国产精品二区一区二区aⅴ污介绍 | 俄罗斯老熟妇色xxxx | 亚洲国产成人a精品不卡在线 | 无码精品国产va在线观看dvd | 国产真人无遮挡作爱免费视频 | 精品国产aⅴ无码一区二区 | 亚洲男人av天堂午夜在 | 最近免费中文字幕中文高清百度 | 日日摸天天摸爽爽狠狠97 | 亚洲日本va午夜在线电影 | aⅴ亚洲 日韩 色 图网站 播放 | 国产香蕉尹人综合在线观看 | 亚洲性无码av中文字幕 | 国产精品视频免费播放 | 亚洲日本va午夜在线电影 | 人妻无码αv中文字幕久久琪琪布 | 精品午夜福利在线观看 | 精品国产av色一区二区深夜久久 | 欧美成人高清在线播放 | 国产成人精品久久亚洲高清不卡 | 久久国语露脸国产精品电影 | 成人aaa片一区国产精品 | 国产av人人夜夜澡人人爽麻豆 | 国产欧美熟妇另类久久久 | 国产内射老熟女aaaa | 久久精品国产99久久6动漫 | 初尝人妻少妇中文字幕 | 国产乡下妇女做爰 | 亚洲欧洲日本综合aⅴ在线 | 国产欧美亚洲精品a | 国产尤物精品视频 | 成人性做爰aaa片免费看不忠 | 久久综合给合久久狠狠狠97色 | 樱花草在线播放免费中文 | 一个人免费观看的www视频 | 欧美日韩一区二区三区自拍 | 漂亮人妻洗澡被公强 日日躁 | 久9re热视频这里只有精品 | 无人区乱码一区二区三区 | 日产精品99久久久久久 | 亚洲一区二区三区 | 国产免费久久精品国产传媒 | 日日天干夜夜狠狠爱 | 99国产欧美久久久精品 | 无码任你躁久久久久久久 | 欧美一区二区三区视频在线观看 | 曰韩少妇内射免费播放 | 精品无码一区二区三区爱欲 | 免费无码一区二区三区蜜桃大 | 久久久精品欧美一区二区免费 | 丰满护士巨好爽好大乳 | 欧美日本精品一区二区三区 | 亚洲中文字幕在线无码一区二区 | 久久久久人妻一区精品色欧美 | 久久综合给合久久狠狠狠97色 | 国产凸凹视频一区二区 | 巨爆乳无码视频在线观看 | 国产av无码专区亚洲awww | 人人澡人人妻人人爽人人蜜桃 | 精品亚洲成av人在线观看 | 又大又硬又爽免费视频 | 国产精品va在线播放 | 久久亚洲中文字幕无码 | 老熟妇乱子伦牲交视频 | 女人高潮内射99精品 | 日本欧美一区二区三区乱码 | 亚洲国产欧美日韩精品一区二区三区 | 国产精品免费大片 | 国产成人无码av在线影院 | 沈阳熟女露脸对白视频 | 色欲人妻aaaaaaa无码 | 亚洲欧美日韩成人高清在线一区 | 欧美熟妇另类久久久久久不卡 | 亚洲色在线无码国产精品不卡 | 18精品久久久无码午夜福利 | 人人妻人人澡人人爽欧美一区九九 | 国产肉丝袜在线观看 | 67194成是人免费无码 | 欧美 日韩 人妻 高清 中文 | 三级4级全黄60分钟 | 国产欧美亚洲精品a | 久久久久99精品国产片 | 国产无遮挡吃胸膜奶免费看 | 国产精品久久国产三级国 | 久久zyz资源站无码中文动漫 | 欧美性生交xxxxx久久久 | 西西人体www44rt大胆高清 | 无码精品国产va在线观看dvd | 亚洲 高清 成人 动漫 | 77777熟女视频在线观看 а天堂中文在线官网 | 天天拍夜夜添久久精品大 | 久久精品国产一区二区三区肥胖 | 黑人巨大精品欧美一区二区 | 欧美亚洲日韩国产人成在线播放 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 精品欧美一区二区三区久久久 | 天天拍夜夜添久久精品大 | 欧美一区二区三区视频在线观看 | 国产农村乱对白刺激视频 | 少妇无码一区二区二三区 | 纯爱无遮挡h肉动漫在线播放 | 欧美激情一区二区三区成人 | 久久无码人妻影院 | 中文字幕乱码中文乱码51精品 | 亚洲国产精品一区二区第一页 | 在线观看国产午夜福利片 | 欧洲熟妇色 欧美 | 国产极品美女高潮无套在线观看 | 亚洲va中文字幕无码久久不卡 | 俄罗斯老熟妇色xxxx | 一本大道伊人av久久综合 | 精品成人av一区二区三区 | 国产精品对白交换视频 | 婷婷六月久久综合丁香 | 亚洲精品美女久久久久久久 | 无码免费一区二区三区 | 亚洲人成网站色7799 | 无码人妻出轨黑人中文字幕 | 国产亚洲欧美日韩亚洲中文色 | 中文字幕亚洲情99在线 | 内射后入在线观看一区 | 波多野结衣 黑人 | 亚洲码国产精品高潮在线 | 亚洲中文字幕无码一久久区 | 日日摸夜夜摸狠狠摸婷婷 | 久久久久久久女国产乱让韩 | 国产在线无码精品电影网 | 麻豆国产丝袜白领秘书在线观看 | 中文字幕人妻无码一区二区三区 | 最新版天堂资源中文官网 | 国产成人无码av一区二区 | 午夜理论片yy44880影院 | 亚洲gv猛男gv无码男同 | 精品一区二区不卡无码av | 一本色道久久综合狠狠躁 | 日本精品人妻无码77777 天堂一区人妻无码 | 中文字幕无码免费久久9一区9 | 国产两女互慰高潮视频在线观看 | 中文字幕无码人妻少妇免费 | 2019午夜福利不卡片在线 | 国产精品人妻一区二区三区四 | 免费人成在线视频无码 | 欧美性猛交内射兽交老熟妇 | 中文毛片无遮挡高清免费 | 人妻少妇精品无码专区二区 | 精品欧美一区二区三区久久久 | 超碰97人人做人人爱少妇 | 欧美老妇交乱视频在线观看 | 亚洲精品国产精品乱码视色 | 人人妻人人澡人人爽欧美精品 | 久久精品丝袜高跟鞋 | 亚洲 另类 在线 欧美 制服 | 性做久久久久久久久 | 亚洲综合精品香蕉久久网 | 久久成人a毛片免费观看网站 | 国产综合色产在线精品 | 青青青手机频在线观看 | 色狠狠av一区二区三区 | 中文字幕人妻无码一夲道 | 蜜桃臀无码内射一区二区三区 | 香蕉久久久久久av成人 | 国产在线精品一区二区高清不卡 | 欧美日韩视频无码一区二区三 | 伊人久久大香线蕉午夜 | 成人动漫在线观看 | 一个人看的www免费视频在线观看 | 亚洲天堂2017无码中文 | 好爽又高潮了毛片免费下载 | 国产亚洲日韩欧美另类第八页 | 精品人妻中文字幕有码在线 | 国产色xx群视频射精 | 巨爆乳无码视频在线观看 | 久久精品99久久香蕉国产色戒 | а√天堂www在线天堂小说 | 日韩欧美成人免费观看 | 亚洲自偷自拍另类第1页 | 性生交大片免费看女人按摩摩 | 野狼第一精品社区 | 国产精品美女久久久久av爽李琼 | 国产精品久久精品三级 | 成人无码精品1区2区3区免费看 | 色婷婷综合激情综在线播放 | 波多野结衣高清一区二区三区 | 精品久久久久香蕉网 | 无码成人精品区在线观看 | 装睡被陌生人摸出水好爽 | 啦啦啦www在线观看免费视频 | 极品嫩模高潮叫床 | 国内丰满熟女出轨videos | 无遮挡国产高潮视频免费观看 | 久久伊人色av天堂九九小黄鸭 | 国产亚洲精品久久久ai换 | 骚片av蜜桃精品一区 | aⅴ亚洲 日韩 色 图网站 播放 | 欧美 丝袜 自拍 制服 另类 | 一区二区传媒有限公司 | a国产一区二区免费入口 | 亚洲综合精品香蕉久久网 | 初尝人妻少妇中文字幕 | 日本熟妇人妻xxxxx人hd | 色一情一乱一伦一视频免费看 | 国产成人一区二区三区在线观看 | 国产内射爽爽大片视频社区在线 | 成人欧美一区二区三区黑人免费 | 一个人免费观看的www视频 | 色偷偷人人澡人人爽人人模 | 高清国产亚洲精品自在久久 | 国产亚洲精品久久久ai换 | 日本www一道久久久免费榴莲 | 亚洲一区二区三区无码久久 | 亚洲精品久久久久久久久久久 | 久久综合久久自在自线精品自 | 成人三级无码视频在线观看 | 国产艳妇av在线观看果冻传媒 | 成年美女黄网站色大免费全看 | 成 人影片 免费观看 | 久久无码中文字幕免费影院蜜桃 | 大胆欧美熟妇xx | 国产亚洲日韩欧美另类第八页 | ass日本丰满熟妇pics | 久久久久免费看成人影片 | 国产成人av免费观看 | 婷婷丁香五月天综合东京热 | 亚洲熟悉妇女xxx妇女av | 国产福利视频一区二区 | 国产亚洲精品精品国产亚洲综合 | 国产高清不卡无码视频 | 精品国偷自产在线视频 | 亚洲自偷精品视频自拍 | 久久国产精品偷任你爽任你 | 高潮喷水的毛片 | 免费播放一区二区三区 | 无码av岛国片在线播放 | 18无码粉嫩小泬无套在线观看 | 性生交片免费无码看人 | 久久久久人妻一区精品色欧美 | 亚洲精品无码国产 | 蜜臀aⅴ国产精品久久久国产老师 | 四虎影视成人永久免费观看视频 | 国产精品无码一区二区桃花视频 | 男女爱爱好爽视频免费看 | 中文字幕无码日韩欧毛 | 亚洲色在线无码国产精品不卡 | 男人的天堂2018无码 | 日韩av无码中文无码电影 | 国产色在线 | 国产 | a在线观看免费网站大全 | 亚洲色在线无码国产精品不卡 | 好男人www社区 | 久久久国产精品无码免费专区 | 女人被男人爽到呻吟的视频 | 日韩精品无码免费一区二区三区 | 熟女俱乐部五十路六十路av | 亚洲国产欧美在线成人 | 天天拍夜夜添久久精品 | 午夜福利不卡在线视频 | 大地资源网第二页免费观看 | 无码国内精品人妻少妇 | 无码人妻出轨黑人中文字幕 | 亚洲无人区午夜福利码高清完整版 | 六月丁香婷婷色狠狠久久 | 99久久无码一区人妻 | 乱人伦人妻中文字幕无码 | 天堂а√在线中文在线 | 中文字幕色婷婷在线视频 | 无码播放一区二区三区 | 久久无码中文字幕免费影院蜜桃 | 99精品久久毛片a片 | 国产精品怡红院永久免费 | 又大又黄又粗又爽的免费视频 | 国语精品一区二区三区 | 国产精华av午夜在线观看 | 亚洲国产欧美国产综合一区 | 欧美日韩色另类综合 | 老头边吃奶边弄进去呻吟 | 青草视频在线播放 | 在线精品国产一区二区三区 | 亚洲毛片av日韩av无码 | 久久午夜夜伦鲁鲁片无码免费 | 国精产品一品二品国精品69xx | 久久亚洲精品成人无码 | 中文字幕无线码免费人妻 | 国产一区二区三区精品视频 | 一本加勒比波多野结衣 | 熟女少妇人妻中文字幕 | 99国产精品白浆在线观看免费 | 国产乱人伦偷精品视频 | 熟妇人妻无乱码中文字幕 | 国产精品亚洲综合色区韩国 | 丰满岳乱妇在线观看中字无码 | 精品午夜福利在线观看 | 欧美日韩在线亚洲综合国产人 | 日本护士毛茸茸高潮 | 丁香花在线影院观看在线播放 | 无码纯肉视频在线观看 | 亚洲国产精品一区二区美利坚 | 亚洲国产高清在线观看视频 | 欧美性猛交xxxx富婆 | 国产av无码专区亚洲awww | 久久精品国产一区二区三区肥胖 | 午夜精品一区二区三区在线观看 | 老司机亚洲精品影院无码 | 天堂亚洲免费视频 | 在线 国产 欧美 亚洲 天堂 | 熟女少妇人妻中文字幕 | 日韩人妻无码中文字幕视频 | 精品国产乱码久久久久乱码 | 国产熟妇高潮叫床视频播放 | 亚洲精品综合五月久久小说 | 国产成人无码午夜视频在线观看 | 亚洲人交乣女bbw | a国产一区二区免费入口 | 亚洲人成网站色7799 | 国产精品福利视频导航 | a国产一区二区免费入口 | 欧美真人作爱免费视频 | 久久精品成人欧美大片 | 成年美女黄网站色大免费全看 | 日日碰狠狠躁久久躁蜜桃 | 国产尤物精品视频 | 美女极度色诱视频国产 | 国产肉丝袜在线观看 | 人妻人人添人妻人人爱 | 曰本女人与公拘交酡免费视频 | 九月婷婷人人澡人人添人人爽 | 18禁黄网站男男禁片免费观看 | 日韩少妇白浆无码系列 | 国产精品毛片一区二区 | 特大黑人娇小亚洲女 | 久久精品中文闷骚内射 | 国产内射爽爽大片视频社区在线 | 日日摸天天摸爽爽狠狠97 | 精品一区二区三区无码免费视频 | 亚洲欧美综合区丁香五月小说 | 国产美女精品一区二区三区 | 人妻少妇精品久久 | 樱花草在线社区www | 三上悠亚人妻中文字幕在线 | 免费观看的无遮挡av | 久久伊人色av天堂九九小黄鸭 | 好男人社区资源 | 天堂а√在线中文在线 | 亚洲成a人片在线观看日本 | 亚洲熟妇色xxxxx亚洲 | 激情综合激情五月俺也去 | 欧美老人巨大xxxx做受 | 99国产欧美久久久精品 | 国产精品久久久av久久久 | 国产免费久久久久久无码 | 国产人妻人伦精品 | 18黄暴禁片在线观看 | 欧美xxxx黑人又粗又长 | 自拍偷自拍亚洲精品10p | 中文字幕av伊人av无码av | 免费无码午夜福利片69 | 欧美人与牲动交xxxx | 国产偷国产偷精品高清尤物 | 免费看少妇作爱视频 | 白嫩日本少妇做爰 | 麻花豆传媒剧国产免费mv在线 | 天天躁日日躁狠狠躁免费麻豆 | 99久久久国产精品无码免费 | 亚洲综合伊人久久大杳蕉 | 鲁鲁鲁爽爽爽在线视频观看 | 精品一区二区三区无码免费视频 | 一本精品99久久精品77 | 欧美丰满熟妇xxxx性ppx人交 | 日日摸天天摸爽爽狠狠97 | 久久国产精品萌白酱免费 | 狠狠色欧美亚洲狠狠色www | 色老头在线一区二区三区 | 无码av中文字幕免费放 | 国产亚av手机在线观看 | 人人妻人人澡人人爽欧美一区九九 | 中文字幕乱码中文乱码51精品 | 麻豆国产人妻欲求不满谁演的 | 最近免费中文字幕中文高清百度 | 欧美 亚洲 国产 另类 | 成在人线av无码免观看麻豆 | 亚洲中文无码av永久不收费 | 久久综合狠狠综合久久综合88 | 无套内射视频囯产 | 欧美第一黄网免费网站 | 成人无码精品1区2区3区免费看 | 国产做国产爱免费视频 | 日本在线高清不卡免费播放 | 双乳奶水饱满少妇呻吟 | 国产亲子乱弄免费视频 | 久久久精品成人免费观看 | 久久 国产 尿 小便 嘘嘘 | 性欧美熟妇videofreesex | 亚洲中文字幕在线无码一区二区 | 国产亚洲精品精品国产亚洲综合 | 精品欧洲av无码一区二区三区 | 奇米影视7777久久精品 | 午夜男女很黄的视频 | 亚洲国产精品无码一区二区三区 | 日产国产精品亚洲系列 | 国产精品久久久久久无码 | 国产精品爱久久久久久久 | 中文字幕乱妇无码av在线 | 亚洲日韩精品欧美一区二区 | 国产成人久久精品流白浆 | 国内少妇偷人精品视频免费 | 国产婷婷色一区二区三区在线 | 精品国产麻豆免费人成网站 | 无码人妻出轨黑人中文字幕 | 国产精品亚洲一区二区三区喷水 | 国产色视频一区二区三区 | 欧美丰满老熟妇xxxxx性 | 秋霞成人午夜鲁丝一区二区三区 | 青青草原综合久久大伊人精品 | 宝宝好涨水快流出来免费视频 | 国产在线aaa片一区二区99 | 未满小14洗澡无码视频网站 | 无码人妻丰满熟妇区毛片18 | 玩弄人妻少妇500系列视频 | 疯狂三人交性欧美 | 亚洲欧美综合区丁香五月小说 | 亚洲精品中文字幕久久久久 | 国产成人人人97超碰超爽8 | 成人亚洲精品久久久久软件 | 久青草影院在线观看国产 | 夜精品a片一区二区三区无码白浆 | 亚洲 日韩 欧美 成人 在线观看 | 精品无人国产偷自产在线 | 强奷人妻日本中文字幕 | 老太婆性杂交欧美肥老太 | 丰满人妻一区二区三区免费视频 | 中文字幕无码av激情不卡 | 日本精品久久久久中文字幕 | 动漫av网站免费观看 | 成人aaa片一区国产精品 | 精品亚洲成av人在线观看 | 野狼第一精品社区 | 成人欧美一区二区三区黑人免费 | 久久这里只有精品视频9 | 亚洲欧美中文字幕5发布 | 熟妇人妻无乱码中文字幕 | 对白脏话肉麻粗话av | 一本色道久久综合亚洲精品不卡 | 国产内射爽爽大片视频社区在线 | 亚洲一区二区三区四区 | 国产另类ts人妖一区二区 | 老熟妇乱子伦牲交视频 | 亚洲综合另类小说色区 | 国产舌乚八伦偷品w中 | 西西人体www44rt大胆高清 | 欧美35页视频在线观看 | 亚洲日本va午夜在线电影 | 国产偷抇久久精品a片69 | 全黄性性激高免费视频 | 99国产精品白浆在线观看免费 | 无码av中文字幕免费放 | 欧美性生交xxxxx久久久 | 爱做久久久久久 | 欧美肥老太牲交大战 | 骚片av蜜桃精品一区 | 蜜桃av抽搐高潮一区二区 | 亚洲成a人片在线观看无码 | 亚洲国产日韩a在线播放 | 纯爱无遮挡h肉动漫在线播放 | 帮老师解开蕾丝奶罩吸乳网站 | 国产两女互慰高潮视频在线观看 | 无码午夜成人1000部免费视频 | 亚洲中文无码av永久不收费 | 欧美日本日韩 | 永久免费精品精品永久-夜色 | 亚洲精品久久久久久一区二区 | 精品偷自拍另类在线观看 | 精品亚洲韩国一区二区三区 | 正在播放东北夫妻内射 | 欧美一区二区三区视频在线观看 | 六月丁香婷婷色狠狠久久 | 中文字幕精品av一区二区五区 | 99麻豆久久久国产精品免费 | 国产真实伦对白全集 | 久久久精品成人免费观看 | 水蜜桃色314在线观看 | 日韩人妻无码一区二区三区久久99 | 国产精品无套呻吟在线 | 国产高清av在线播放 | 美女毛片一区二区三区四区 | 亚洲精品久久久久久一区二区 | 国产艳妇av在线观看果冻传媒 | 日本www一道久久久免费榴莲 | 男人扒开女人内裤强吻桶进去 | 久久国产精品精品国产色婷婷 | 国产又爽又黄又刺激的视频 | 亚洲人成影院在线无码按摩店 | 日韩无码专区 | 性色欲情网站iwww九文堂 | 久久www免费人成人片 | 国产内射老熟女aaaa | 无码午夜成人1000部免费视频 | 99视频精品全部免费免费观看 | 国内丰满熟女出轨videos | 欧洲精品码一区二区三区免费看 | 成人无码精品一区二区三区 | 亚洲精品一区三区三区在线观看 | 国产亚洲人成在线播放 | 亚洲精品中文字幕乱码 | 老司机亚洲精品影院无码 | 人妻体内射精一区二区三四 | 国产成人综合色在线观看网站 | 久青草影院在线观看国产 | 最新国产乱人伦偷精品免费网站 | 久青草影院在线观看国产 | 曰韩少妇内射免费播放 | 成人无码视频免费播放 | 欧美日韩亚洲国产精品 | 国产尤物精品视频 | 男人和女人高潮免费网站 | 国产麻豆精品一区二区三区v视界 | 午夜福利试看120秒体验区 | 亚洲成a人一区二区三区 | 国产激情无码一区二区 | 丁香啪啪综合成人亚洲 | 奇米影视7777久久精品人人爽 | 狠狠色欧美亚洲狠狠色www | 蜜桃av抽搐高潮一区二区 | 狠狠躁日日躁夜夜躁2020 | 无码国模国产在线观看 | 无码av免费一区二区三区试看 | 色婷婷香蕉在线一区二区 | 亚洲精品综合一区二区三区在线 | 久久午夜无码鲁丝片 | 无码人妻精品一区二区三区下载 | 男人的天堂2018无码 | 婷婷五月综合缴情在线视频 | 人人澡人摸人人添 | 欧美一区二区三区 | 亚洲aⅴ无码成人网站国产app | 香港三级日本三级妇三级 | 风流少妇按摩来高潮 | 丰满少妇弄高潮了www | 国产无遮挡又黄又爽免费视频 | 国产成人精品久久亚洲高清不卡 | 亚洲第一无码av无码专区 | a片免费视频在线观看 | 无码精品人妻一区二区三区av | 亚洲色无码一区二区三区 | 亚洲中文无码av永久不收费 | 中文字幕色婷婷在线视频 | 中文字幕 亚洲精品 第1页 | 免费观看黄网站 | 精品久久久无码中文字幕 | 久久久久久久久888 | 正在播放东北夫妻内射 | 2020久久香蕉国产线看观看 | 熟妇人妻中文av无码 | 国产精品毛多多水多 | 久久久久久久久888 | 日韩少妇内射免费播放 | 秋霞成人午夜鲁丝一区二区三区 | 日韩在线不卡免费视频一区 | 国产精品亚洲五月天高清 | 4hu四虎永久在线观看 | 国产猛烈高潮尖叫视频免费 | 九九在线中文字幕无码 | 欧洲熟妇精品视频 | 无码一区二区三区在线观看 | 国产熟妇另类久久久久 | 免费无码的av片在线观看 | 色五月丁香五月综合五月 | 中文毛片无遮挡高清免费 | 亚洲色在线无码国产精品不卡 | 成人动漫在线观看 | 久久久久免费看成人影片 | 人人妻人人澡人人爽欧美一区九九 | 玩弄人妻少妇500系列视频 | 任你躁国产自任一区二区三区 | 97夜夜澡人人爽人人喊中国片 | 99精品视频在线观看免费 | 精品无人区无码乱码毛片国产 | 欧美放荡的少妇 | 麻豆国产人妻欲求不满谁演的 | 国产深夜福利视频在线 | 漂亮人妻洗澡被公强 日日躁 | 5858s亚洲色大成网站www | 中文字幕无码乱人伦 | 国产精品久久久久7777 | 永久免费精品精品永久-夜色 | 色一情一乱一伦一视频免费看 | 国产一区二区三区精品视频 | 97夜夜澡人人双人人人喊 | 日本精品少妇一区二区三区 | 色狠狠av一区二区三区 | 一二三四在线观看免费视频 | 东京热男人av天堂 | 国产成人无码午夜视频在线观看 | 免费国产黄网站在线观看 | 免费观看的无遮挡av | 久久久久成人片免费观看蜜芽 | 亚洲欧美日韩综合久久久 | 亚洲理论电影在线观看 | 中文字幕亚洲情99在线 | 久久五月精品中文字幕 | 日韩人妻少妇一区二区三区 | 亚洲区小说区激情区图片区 | 国产乱人偷精品人妻a片 | 狠狠色噜噜狠狠狠狠7777米奇 | 精品熟女少妇av免费观看 | 极品嫩模高潮叫床 | 成年女人永久免费看片 | 国产精品亚洲а∨无码播放麻豆 | 国产成人午夜福利在线播放 | 国产成人无码av片在线观看不卡 | 人人澡人人透人人爽 | 少妇性荡欲午夜性开放视频剧场 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 纯爱无遮挡h肉动漫在线播放 | 亚洲国产欧美在线成人 | 国産精品久久久久久久 | 欧美精品国产综合久久 | 亚洲欧洲日本无在线码 | 国产精品久久久久久久9999 | 亚洲精品国产精品乱码视色 | 97夜夜澡人人双人人人喊 | 日本成熟视频免费视频 | 波多野结衣av在线观看 | 野外少妇愉情中文字幕 | 久久久久久a亚洲欧洲av冫 | 色一情一乱一伦 | 又粗又大又硬又长又爽 | 国产性生大片免费观看性 | 国产特级毛片aaaaaa高潮流水 | 久久亚洲日韩精品一区二区三区 | 欧美高清在线精品一区 | 精品人妻中文字幕有码在线 | 亚洲小说图区综合在线 | 精品一区二区三区无码免费视频 | 中文字幕无码免费久久99 | 丰满少妇人妻久久久久久 | 亚洲va欧美va天堂v国产综合 | 午夜福利一区二区三区在线观看 | 狠狠噜狠狠狠狠丁香五月 | 1000部夫妻午夜免费 | 人妻尝试又大又粗久久 | 一本久道久久综合狠狠爱 | 国产情侣作爱视频免费观看 | 精品人妻中文字幕有码在线 | 久久久精品国产sm最大网站 | 精品久久久久久人妻无码中文字幕 | 无码av岛国片在线播放 | 粗大的内捧猛烈进出视频 | 丰满诱人的人妻3 | 精品国产国产综合精品 | 日韩欧美中文字幕在线三区 | 亚洲熟妇自偷自拍另类 | 亚洲欧洲中文日韩av乱码 | 老熟女乱子伦 | 55夜色66夜色国产精品视频 | 久久人人爽人人人人片 | 午夜嘿嘿嘿影院 | 日韩欧美中文字幕公布 | 亚洲日本va中文字幕 | 国内老熟妇对白xxxxhd | 中文字幕无码免费久久9一区9 | 亚洲 日韩 欧美 成人 在线观看 | 男女超爽视频免费播放 | 国产av剧情md精品麻豆 | 国产精品资源一区二区 | 国产精品成人av在线观看 | 色综合视频一区二区三区 | 丰满人妻精品国产99aⅴ | 国内老熟妇对白xxxxhd | 亚洲精品国产精品乱码视色 | 亚洲国产成人a精品不卡在线 | 亚洲欧美日韩综合久久久 | 狠狠色噜噜狠狠狠狠7777米奇 | 激情内射亚州一区二区三区爱妻 | 亚洲aⅴ无码成人网站国产app | 国产九九九九九九九a片 | 国产成人精品一区二区在线小狼 | 黑人巨大精品欧美一区二区 | 99re在线播放 | 国产综合久久久久鬼色 | 中文字幕中文有码在线 | 日本熟妇乱子伦xxxx | 呦交小u女精品视频 | 99久久精品无码一区二区毛片 | 性史性农村dvd毛片 | 久久久www成人免费毛片 | 国产后入清纯学生妹 | 久久 国产 尿 小便 嘘嘘 | 中文字幕日韩精品一区二区三区 | 中文字幕中文有码在线 | 国产成人精品久久亚洲高清不卡 | 十八禁视频网站在线观看 | 窝窝午夜理论片影院 | 国产精品久久久av久久久 | 国产午夜福利100集发布 | 亚洲欧美国产精品久久 | 亚洲欧美日韩国产精品一区二区 | 亚洲区欧美区综合区自拍区 | 亚洲欧美综合区丁香五月小说 | 久久精品国产一区二区三区 | 亚洲码国产精品高潮在线 | 日本熟妇大屁股人妻 | 在教室伦流澡到高潮hnp视频 | 欧美激情综合亚洲一二区 | 亚洲综合精品香蕉久久网 | 兔费看少妇性l交大片免费 | аⅴ资源天堂资源库在线 | 欧美日韩综合一区二区三区 | 国产色xx群视频射精 | 国产亚洲精品精品国产亚洲综合 | 成在人线av无码免费 | 国产精品二区一区二区aⅴ污介绍 | 国产成人综合美国十次 | 国产 精品 自在自线 | 中文字幕无码免费久久99 | 色欲人妻aaaaaaa无码 | a国产一区二区免费入口 | 国产特级毛片aaaaaa高潮流水 | 综合人妻久久一区二区精品 | 未满小14洗澡无码视频网站 | 亚洲成a人片在线观看无码3d | 美女扒开屁股让男人桶 | 大地资源中文第3页 | 樱花草在线播放免费中文 | 国产欧美亚洲精品a | 欧美变态另类xxxx | 麻豆成人精品国产免费 | 色婷婷综合激情综在线播放 | 国产人妻人伦精品1国产丝袜 | 亚洲精品午夜国产va久久成人 | 三上悠亚人妻中文字幕在线 | 色一情一乱一伦 | 亚洲va中文字幕无码久久不卡 | 国产特级毛片aaaaaa高潮流水 | 在线看片无码永久免费视频 | 在线成人www免费观看视频 | 性生交片免费无码看人 | 老司机亚洲精品影院无码 | 粗大的内捧猛烈进出视频 | 少妇邻居内射在线 | 人人妻人人澡人人爽欧美一区九九 | 性欧美videos高清精品 | 亚洲无人区午夜福利码高清完整版 | 性色欲网站人妻丰满中文久久不卡 | 未满成年国产在线观看 | 亚洲精品国偷拍自产在线麻豆 | 国内老熟妇对白xxxxhd | 2020久久香蕉国产线看观看 | 中文字幕人成乱码熟女app | 久久熟妇人妻午夜寂寞影院 | 欧美日韩一区二区免费视频 | 特级做a爰片毛片免费69 | 中文字幕av无码一区二区三区电影 | 一本无码人妻在中文字幕免费 | 中文字幕无线码 | 国产后入清纯学生妹 | 国产色精品久久人妻 | 无码av免费一区二区三区试看 | 乱人伦人妻中文字幕无码久久网 | 久久 国产 尿 小便 嘘嘘 | 久久99精品久久久久婷婷 | 秋霞特色aa大片 | 久久久无码中文字幕久... | 狠狠色噜噜狠狠狠7777奇米 | 国产激情一区二区三区 | 97夜夜澡人人双人人人喊 | 国产精品久久久久影院嫩草 | 人人澡人人妻人人爽人人蜜桃 | 荫蒂被男人添的好舒服爽免费视频 | 亚洲国精产品一二二线 | 中文字幕乱码中文乱码51精品 | 欧美熟妇另类久久久久久多毛 | 精品久久8x国产免费观看 | 久久伊人色av天堂九九小黄鸭 | 国产97人人超碰caoprom | 露脸叫床粗话东北少妇 | 国产成人一区二区三区别 | 成年女人永久免费看片 | 久久99精品久久久久久动态图 | 亚洲va中文字幕无码久久不卡 | av无码电影一区二区三区 | 亚洲色大成网站www国产 | 玩弄人妻少妇500系列视频 | 亚洲区欧美区综合区自拍区 | 欧美阿v高清资源不卡在线播放 | 欧美丰满老熟妇xxxxx性 | 亚洲熟悉妇女xxx妇女av | 中文毛片无遮挡高清免费 | 男女下面进入的视频免费午夜 | 色婷婷综合激情综在线播放 | 日本精品久久久久中文字幕 | 自拍偷自拍亚洲精品被多人伦好爽 | 久久综合给久久狠狠97色 | 欧美人与禽zoz0性伦交 | 四十如虎的丰满熟妇啪啪 | 偷窥日本少妇撒尿chinese | 久9re热视频这里只有精品 | 牲欲强的熟妇农村老妇女视频 | 国产三级精品三级男人的天堂 | 中文无码成人免费视频在线观看 | 玩弄少妇高潮ⅹxxxyw | 丁香啪啪综合成人亚洲 | 成人动漫在线观看 | 欧美35页视频在线观看 | 日韩人妻无码中文字幕视频 | 国产欧美熟妇另类久久久 | 色五月丁香五月综合五月 | 国产97在线 | 亚洲 | 久久99精品国产麻豆蜜芽 | 日产精品高潮呻吟av久久 | 99久久99久久免费精品蜜桃 | 国产精品va在线观看无码 | 女人被男人爽到呻吟的视频 | 久久无码专区国产精品s | 久久精品中文闷骚内射 | 女高中生第一次破苞av | 玩弄少妇高潮ⅹxxxyw | 久久成人a毛片免费观看网站 | 大色综合色综合网站 | 国产亚洲精品久久久久久久久动漫 | 黑森林福利视频导航 | 国产精品亚洲一区二区三区喷水 | 国产精品高潮呻吟av久久4虎 | 日本护士毛茸茸高潮 | 国产人妻大战黑人第1集 | 国产在线一区二区三区四区五区 | 亚洲 a v无 码免 费 成 人 a v | 国产肉丝袜在线观看 | 日韩精品一区二区av在线 | 久久亚洲a片com人成 | 性欧美牲交xxxxx视频 | 婷婷丁香五月天综合东京热 | 日韩人妻无码中文字幕视频 | 少妇的肉体aa片免费 | 麻豆国产人妻欲求不满谁演的 | 精品国偷自产在线 | 色婷婷久久一区二区三区麻豆 | 色窝窝无码一区二区三区色欲 | 狂野欧美性猛xxxx乱大交 | 亚洲 激情 小说 另类 欧美 | 乱码午夜-极国产极内射 | 国产人成高清在线视频99最全资源 | 精品一区二区不卡无码av | 性生交大片免费看l | 亚洲男人av天堂午夜在 | 无码精品人妻一区二区三区av | 日韩精品无码一本二本三本色 | 久久久中文久久久无码 | 娇妻被黑人粗大高潮白浆 | 国产亚洲精品久久久闺蜜 | 成人一在线视频日韩国产 | 精品亚洲成av人在线观看 | 老熟妇仑乱视频一区二区 | 最近免费中文字幕中文高清百度 | 青青久在线视频免费观看 | 熟妇激情内射com | 久久亚洲精品成人无码 | 亚洲日本va午夜在线电影 | 精品欧洲av无码一区二区三区 | 人妻互换免费中文字幕 | 无码帝国www无码专区色综合 | 露脸叫床粗话东北少妇 | 国产精品理论片在线观看 | 亚洲人成网站在线播放942 | 漂亮人妻洗澡被公强 日日躁 | 熟女体下毛毛黑森林 | 少妇被黑人到高潮喷出白浆 | 国产激情综合五月久久 | 我要看www免费看插插视频 | 亚洲va欧美va天堂v国产综合 | 久久国语露脸国产精品电影 | 精品久久8x国产免费观看 | 国产精品成人av在线观看 | 成人动漫在线观看 | 色诱久久久久综合网ywww | 免费人成在线观看网站 | 在线欧美精品一区二区三区 | 色一情一乱一伦一视频免费看 | 亚洲狠狠色丁香婷婷综合 | 草草网站影院白丝内射 | 综合网日日天干夜夜久久 | 中文字幕人成乱码熟女app | 无码吃奶揉捏奶头高潮视频 | 欧洲极品少妇 | 亚洲欧洲日本综合aⅴ在线 | 日本大香伊一区二区三区 | 亚洲一区二区三区 | 日本乱人伦片中文三区 | 日韩亚洲欧美精品综合 | 日韩少妇内射免费播放 | 亚洲综合在线一区二区三区 | 亚洲呦女专区 | 伊人久久大香线蕉av一区二区 | 伊人久久大香线蕉亚洲 | 小sao货水好多真紧h无码视频 | 国产真人无遮挡作爱免费视频 | 亚洲国产精品美女久久久久 | 一本色道久久综合狠狠躁 | 国内精品人妻无码久久久影院蜜桃 | 九九在线中文字幕无码 | 中文字幕乱码人妻二区三区 | 又湿又紧又大又爽a视频国产 | 7777奇米四色成人眼影 | 欧美丰满老熟妇xxxxx性 | 国内精品九九久久久精品 | 中文字幕人妻无码一区二区三区 | 水蜜桃av无码 | 国产精品高潮呻吟av久久 | 欧美 日韩 人妻 高清 中文 | 亚洲男人av天堂午夜在 | 又黄又爽又色的视频 | 亚洲综合另类小说色区 | 久久这里只有精品视频9 | 国产激情无码一区二区app | 最新版天堂资源中文官网 | 亚洲国产欧美日韩精品一区二区三区 | 九九热爱视频精品 | 国产电影无码午夜在线播放 | 又湿又紧又大又爽a视频国产 | 大色综合色综合网站 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 久久国产劲爆∧v内射 | 亚洲七七久久桃花影院 | 波多野结衣一区二区三区av免费 | 女人高潮内射99精品 | 扒开双腿吃奶呻吟做受视频 | 强伦人妻一区二区三区视频18 | 日韩精品a片一区二区三区妖精 | 国产一区二区三区四区五区加勒比 | 久久久成人毛片无码 | 永久免费观看国产裸体美女 | 国产精品理论片在线观看 | 亚洲国产欧美在线成人 | 性生交片免费无码看人 | 国产办公室秘书无码精品99 | 久久久久久九九精品久 | 色欲av亚洲一区无码少妇 | 欧美激情内射喷水高潮 | 精品国产精品久久一区免费式 | 影音先锋中文字幕无码 | 色五月五月丁香亚洲综合网 | 成人亚洲精品久久久久 | 精品国产麻豆免费人成网站 | 任你躁国产自任一区二区三区 | 成人无码视频免费播放 | 99久久婷婷国产综合精品青草免费 | 福利一区二区三区视频在线观看 | 精品国产一区二区三区四区 | 久久久久亚洲精品中文字幕 | 国产精品办公室沙发 | 东京热无码av男人的天堂 | 红桃av一区二区三区在线无码av | 国产精品丝袜黑色高跟鞋 | 国产成人精品无码播放 | 国产精品第一区揄拍无码 | 最新国产麻豆aⅴ精品无码 | 久久国产精品二国产精品 | 国产无套粉嫩白浆在线 | 国内揄拍国内精品少妇国语 | av人摸人人人澡人人超碰下载 | 国产精品久久久 | 国产欧美精品一区二区三区 | 欧美激情内射喷水高潮 | 97se亚洲精品一区 | 欧美国产亚洲日韩在线二区 | av无码电影一区二区三区 | 国产 精品 自在自线 | 国产性生交xxxxx无码 | 性欧美牲交在线视频 | 精品亚洲韩国一区二区三区 | 国产精品二区一区二区aⅴ污介绍 | 国产成人无码区免费内射一片色欲 | 人人妻人人澡人人爽欧美一区 | 狠狠躁日日躁夜夜躁2020 | 亚洲乱码国产乱码精品精 | 国产精品久久精品三级 | 国产亚洲精品久久久久久 | 久久久精品欧美一区二区免费 | 日韩成人一区二区三区在线观看 | 国产精品永久免费视频 | 免费观看的无遮挡av | 露脸叫床粗话东北少妇 | 国产激情精品一区二区三区 | 疯狂三人交性欧美 | 国产农村妇女高潮大叫 | 久久人人爽人人人人片 | 国产真实伦对白全集 | 大乳丰满人妻中文字幕日本 | 秋霞特色aa大片 | 又大又黄又粗又爽的免费视频 | 老熟妇仑乱视频一区二区 | 亚洲无人区一区二区三区 | 亚洲春色在线视频 | 久久精品丝袜高跟鞋 | 国产区女主播在线观看 | 波多野结衣高清一区二区三区 | 国产sm调教视频在线观看 | 丰满人妻一区二区三区免费视频 | 国产又爽又黄又刺激的视频 | 国产精品人人爽人人做我的可爱 | 中文毛片无遮挡高清免费 | 亚洲综合精品香蕉久久网 | 免费视频欧美无人区码 | 国产人妻精品一区二区三区 | 日韩成人一区二区三区在线观看 | 秋霞成人午夜鲁丝一区二区三区 | 51国偷自产一区二区三区 | 男女超爽视频免费播放 | 国产一区二区三区精品视频 | 亚洲国产精品无码久久久久高潮 | 精品国产aⅴ无码一区二区 | 最新版天堂资源中文官网 | 国产九九九九九九九a片 | 无人区乱码一区二区三区 | 亚洲人成无码网www | 99久久久无码国产精品免费 | 福利一区二区三区视频在线观看 | 久久久久se色偷偷亚洲精品av | 日本在线高清不卡免费播放 | 领导边摸边吃奶边做爽在线观看 | 人妻体内射精一区二区三四 | 亚洲爆乳无码专区 | 精品国产一区二区三区av 性色 | 三级4级全黄60分钟 | 国产精品久久久久久久9999 | 亚洲综合伊人久久大杳蕉 | 久久久久久久女国产乱让韩 | 色综合视频一区二区三区 | 国产舌乚八伦偷品w中 | 国产精品欧美成人 | 无码av最新清无码专区吞精 | 国产人妻精品一区二区三区不卡 | 午夜时刻免费入口 | 久久人人爽人人爽人人片ⅴ | 久久精品视频在线看15 | 欧美三级不卡在线观看 | 久久精品国产99精品亚洲 | 久久精品女人天堂av免费观看 | 少妇人妻大乳在线视频 | 小泽玛莉亚一区二区视频在线 | 日韩亚洲欧美中文高清在线 | 午夜丰满少妇性开放视频 | 在线成人www免费观看视频 | 中文字幕无码热在线视频 | 国产精品久久国产精品99 | 草草网站影院白丝内射 | 人人妻人人澡人人爽人人精品浪潮 | 撕开奶罩揉吮奶头视频 | 动漫av一区二区在线观看 | 精品无码国产一区二区三区av | 俄罗斯老熟妇色xxxx | 欧美人与牲动交xxxx | 国产成人一区二区三区在线观看 | 日本一区二区三区免费高清 | 国产无遮挡又黄又爽又色 | 亚洲人交乣女bbw | 亚洲日本va中文字幕 | 国内揄拍国内精品少妇国语 | 狠狠cao日日穞夜夜穞av | 亚洲 欧美 激情 小说 另类 | 亚洲日本va中文字幕 | 2019nv天堂香蕉在线观看 | 国产精品鲁鲁鲁 | 欧美喷潮久久久xxxxx | 精品国偷自产在线 | 国产成人人人97超碰超爽8 | 真人与拘做受免费视频一 | 中文字幕久久久久人妻 | 熟妇人妻无乱码中文字幕 | 日日碰狠狠丁香久燥 | 麻豆精品国产精华精华液好用吗 | aⅴ在线视频男人的天堂 | 国产精品亚洲综合色区韩国 | 亚洲精品午夜无码电影网 | 中文字幕av无码一区二区三区电影 | 蜜桃视频插满18在线观看 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 极品嫩模高潮叫床 | 少妇的肉体aa片免费 | 人人妻人人澡人人爽精品欧美 | 国产黄在线观看免费观看不卡 | 1000部啪啪未满十八勿入下载 | 暴力强奷在线播放无码 | 久久精品人妻少妇一区二区三区 | 欧美乱妇无乱码大黄a片 | 日韩欧美成人免费观看 | www成人国产高清内射 | 福利一区二区三区视频在线观看 | 亚洲国产精品无码一区二区三区 | 亚洲欧洲日本无在线码 | 午夜男女很黄的视频 | 99久久人妻精品免费二区 | 极品尤物被啪到呻吟喷水 | 久久97精品久久久久久久不卡 | 大屁股大乳丰满人妻 | 国产午夜无码精品免费看 | 免费乱码人妻系列无码专区 | 国产区女主播在线观看 | 欧美国产亚洲日韩在线二区 | 精品午夜福利在线观看 | 国产精品久久久久9999小说 | 成人影院yy111111在线观看 | 麻豆果冻传媒2021精品传媒一区下载 | 婷婷五月综合缴情在线视频 | 欧美放荡的少妇 | 极品嫩模高潮叫床 | 女人被爽到呻吟gif动态图视看 | 亚洲春色在线视频 | 欧美阿v高清资源不卡在线播放 | 网友自拍区视频精品 | 丁香啪啪综合成人亚洲 | 在线天堂新版最新版在线8 | 人妻体内射精一区二区三四 | 一本久道久久综合狠狠爱 | 欧美老熟妇乱xxxxx | 色综合久久网 | 国内综合精品午夜久久资源 | 日本成熟视频免费视频 | 一本无码人妻在中文字幕免费 | 国产免费观看黄av片 | 精品少妇爆乳无码av无码专区 | 97色伦图片97综合影院 | 日产国产精品亚洲系列 | 亚洲中文无码av永久不收费 | 免费国产黄网站在线观看 | 国产成人综合色在线观看网站 | 国产精品久久国产精品99 | 最新国产麻豆aⅴ精品无码 | 久久久久av无码免费网 | 久久天天躁狠狠躁夜夜免费观看 | 激情国产av做激情国产爱 | 亚洲国产成人a精品不卡在线 | 精品久久久中文字幕人妻 | 久久99精品国产.久久久久 | 国色天香社区在线视频 | 欧美亚洲国产一区二区三区 | 日韩少妇白浆无码系列 | 国产97人人超碰caoprom | 18黄暴禁片在线观看 | 婷婷五月综合缴情在线视频 | 麻豆av传媒蜜桃天美传媒 | 好男人社区资源 | 色婷婷久久一区二区三区麻豆 | 国产亚洲人成a在线v网站 | 无码国产色欲xxxxx视频 | 日韩精品a片一区二区三区妖精 | 午夜精品久久久久久久 | 99riav国产精品视频 | √天堂中文官网8在线 | 久久久久久a亚洲欧洲av冫 | 色综合久久久久综合一本到桃花网 | 麻豆人妻少妇精品无码专区 | 无码免费一区二区三区 | 国产精品亚洲一区二区三区喷水 | 亚洲欧美色中文字幕在线 | 久久99精品国产麻豆 | av无码久久久久不卡免费网站 | 久久久久成人片免费观看蜜芽 | 久久久久免费精品国产 | 国产99久久精品一区二区 | 久久99精品国产麻豆 | 久久精品视频在线看15 | 中文字幕无码视频专区 | 性欧美牲交xxxxx视频 | 波多野结衣高清一区二区三区 | 欧美日韩在线亚洲综合国产人 | 老熟女重囗味hdxx69 | 国产成人无码av在线影院 | 国产日产欧产精品精品app | 一本一道久久综合久久 | 免费无码一区二区三区蜜桃大 | 久久99热只有频精品8 | 人妻天天爽夜夜爽一区二区 | 精品乱码久久久久久久 | 午夜不卡av免费 一本久久a久久精品vr综合 | 97夜夜澡人人双人人人喊 | 久久精品国产一区二区三区 | 国产又粗又硬又大爽黄老大爷视 | 性欧美牲交在线视频 | 男女下面进入的视频免费午夜 | 国产精品久久久久9999小说 | 日韩精品无码一区二区中文字幕 | 国精品人妻无码一区二区三区蜜柚 | 亚洲精品成人av在线 | 国产成人无码av片在线观看不卡 | 亚洲啪av永久无码精品放毛片 | 久久亚洲精品中文字幕无男同 | 国产香蕉97碰碰久久人人 | 色欲av亚洲一区无码少妇 | 99久久人妻精品免费一区 | 男人的天堂av网站 | 高中生自慰www网站 | 熟女少妇人妻中文字幕 | 精品一二三区久久aaa片 | 精品久久久中文字幕人妻 | 亚洲欧美日韩成人高清在线一区 | 精品久久综合1区2区3区激情 | 久精品国产欧美亚洲色aⅴ大片 | 色欲综合久久中文字幕网 | 国产成人久久精品流白浆 | 国产精品无码永久免费888 | 国产精品资源一区二区 | 久久亚洲精品成人无码 | 国产精品永久免费视频 | 麻豆国产人妻欲求不满 | 国产精品高潮呻吟av久久4虎 | 精品水蜜桃久久久久久久 | 国产精品二区一区二区aⅴ污介绍 | 婷婷五月综合缴情在线视频 | 国产精品久久久久影院嫩草 | 国产精品美女久久久网av | 精品国偷自产在线视频 | 欧美熟妇另类久久久久久不卡 | 亚洲日韩中文字幕在线播放 | 少妇无码av无码专区在线观看 | 2019nv天堂香蕉在线观看 | 无码人妻av免费一区二区三区 | 一区二区三区高清视频一 | 男女性色大片免费网站 | 亚洲熟妇色xxxxx亚洲 | 国产人妻精品午夜福利免费 | 亚洲中文字幕成人无码 | 中文字幕无码热在线视频 | 无码人妻av免费一区二区三区 | 激情亚洲一区国产精品 | 欧美三级不卡在线观看 | 国产成人精品优优av | 亚洲娇小与黑人巨大交 | 噜噜噜亚洲色成人网站 | 奇米影视7777久久精品 | 色综合久久久无码中文字幕 | 国产办公室秘书无码精品99 | 国产人妻精品一区二区三区 | 999久久久国产精品消防器材 | 欧美成人午夜精品久久久 | 最近的中文字幕在线看视频 | 天天爽夜夜爽夜夜爽 | 日本欧美一区二区三区乱码 |