IO进程(二)

目录

一、进程

1.什么是进程

2.进程和程序有什么区别

3.进程的组成

4.进程的种类

5.进程的PID

1.什么是进程的PID

2.特殊的PID进程

3.进程的相关命令

4.pidof

4.进程的状态

5.进程状态切换实例

 二、进程的创建

1.进程创建原理

2.进程创建函数

3.进程创建实例

 (1)不关注返回值

(2) 关注返回值 

(3)父子进程执行的顺序问题 

(4)父子进程内存空间的问题

4.getpid和getppid

5.孤儿进程

6.僵尸进程 

7.进程退出exit/_exit函数

 exit

_exit

8.回收进程资源wait/waitpid函数

wait

waitpid

 9.进程代码替换system函数的使用

10.守护进程

        1.概念

        2.创建守护进程的流程      

        3.守护进程使用实例

-------------------------------------多线程----------------------------------------

一、线程

1.概念       

2.创建线程的函数

3.多线程执行的顺序

4.多线程内存空间的问题

5. pthread_self函数的使用

 6. pthread_exit函数说明

7. pthread_join函数的使用

8. pthread_detach函数的使用

9. pthread_cancel函数的使用

二、线程的互斥

1.互斥锁相关API

2.互斥锁使用实例

3.线程互斥锁死锁问题

三、线程的同步

1.文明信号量

1.1无名信号量相关API

1.2无名信号量使用实例

2.条件变量

2.1条件变量API

2.2 条件变量使用实例

2.3 条件变量适用场景

----------------------------------进程间通信-------------------------------------

一、进程间通信种类

1.传统进程间通信

2.IPC进程间通信

3.BSD(伯克利分校)

二、传统进程间通信

1.无名管道

 1.1 无名管道原理

1.2 无名管道API

1.3 无名管道使用实例

1.4 无名管道的特点 

2.有名管道

2.1 有名管道的原理

2.2 有名管道API

2.3 有名管道使用实例

2.4 有名管道的特点

3.信号

3.1 信号的概念

3.2 系统中信号的查看

3.3 常用的信号

3.4 发送信号的命令

3.5 信号处理函数API

3.6 信号处理函数使用实例

3.7 发信号的函数

3.10 alarm函数

三、IPC进程间通信

1.IPC进程间通信相关的命令

2.IPC进程间通信获取键值的函数

3.消息队列

3.1 消息队列的原理

3.2 消息队列相关API

3.3 消息队列使用实例

3.4使用msgctl获取消息队列的属性

4.共享内存

 4.1 共享内存原理

4.2 共享内存相关API

4.3 共享内存使用实例

4.4 共享内存属性设置

5.信号灯集

 5.1 信号灯集原理

5.2 信号灯集相关API

5.3信号灯集函数封装

5.3 信号灯集使用实例

5.4 信号灯集属性获

  面试题 

IO部分

进程部分


一、进程

1.什么是进程

进程:程序的一次执行过程就会产生一个进程。

进程是资源分配的基本单位(0-3G)

进程就是一个正在执行的任务。

进程是一个动态的过程,他有生命周期:随着程序的开始而开始,随着程序的结束而结束。

每个进程都有自己独立的内存空间,比如:

每个进程拥有自己的文件描述符,拥有自己的缓冲区。

只要用户执行了一个程序,在内核中就会产生一个 task_struct 的结构体,

这个结构体就代表这个进程,进程运行的过程中,产生的所有信息都在这个结构体中保存。

linux系统是多任务的系统,执行的过程是:时间片轮转 上下文切换

2.进程和程序有什么区别

程序:程序是经过编译器生成的二进制文件,程序在硬盘上存储。

           程序是静态的,没有生命周期的概念,程序本身也不会分配内存空间。

进程:程序的一次执行过程就会产生一个进程,进程是动态的,有生命周期。

           程序运行的时候会分配(0-3G)的用户空间,进程在内存上存储。

3.进程的组成

        进程由三部分组成:进程的PCB(task_struct)、数据段、代码段。

4.进程的种类

交互进程:

        这种进程一般都会维护一个终端,用来和用户交互。如:文本编辑器

批处理进程:

        这种进程的优先级一般都比较低,运行的时候会把他们放在一个队列中。

        随着队列的执行,而逐渐执行。如:gcc编译器编译.c文件时 就是一个批处理进程

守护进程:

        守护进程就是后台运行的服务,随着系统的启动而启动,随着系统的终止而终止。

        如:windows系统后台的各种服务

5.进程的PID

1.什么是进程的PID

        PID就是操作系统给进程分配的编号,他是进程的唯一的标识。

        linux系统中,PID的值是一个大于0的值。

        linux系统中,创建的进程的个数是有限的(可以使用下面的命令查看) 

 系统中正在运行的进程,可以在 /proc 目录下查看到(以数字命名的每个编号都是一个进程)

2.特殊的PID进程

0:idel,linux系统启动的时候运行的第一个进程

      如果没有其他进程在执行,就运行这个进程

1:init,由0号进程在内核中调用kernel_thread函数产生的第一个进程

      会初始化系统中所有硬件。当初始化工作完成后,也一直执行,比如给孤儿进程回收资源

2:kthread,调度器进程,主要负责进程的调度工作

3.进程的相关命令

1.ps   -ef 

        linux@ubuntu:~/HQYJ/day27$ ps -ef
        UID         PID   PPID     C   STIME   TTY       TIME         CMD
        root          1        0          0    4月23      ?         00:00:05     /sbin/init auto nopro
        root          2        0          0    4月23      ?         00:00:00     [kthreadd]
        root          3        2          0    4月23      ?         00:00:00     [rcu_gp]
        root          4        2          0    4月23      ?         00:00:00     [rcu_par_gp]
        gdm        1853   1467    0    4月23    tty1       00:00:00     ibus-daemon --xim --p
        gdm        1856   1853    0    4月23    tty1       00:00:00     /usr/lib/ibus/ibus-dc
        gdm        1859      1       0     4月23   tty1       00:00:00     /usr/lib/ibus/ibus-x1
        linux      2389   2109      0     4月23   tty2       00:00:00    /usr/lib/gnome-settin
        linux      2390   2109      0     4月23   tty2       00:00:01    /usr/lib/gnome-settin
        linux      2391   2109      0     4月23   tty2       00:00:00    /usr/lib/gnome-settin

        linux     14961  13598    0     04:50    pts/0     00:00:00    bash
        linux     14969  14961    0     04:50     pts/0    00:00:00    ps -ef

UID       进程所属用户

PID       进程号

PPID    父进程号

C          当前进程对CPU的占用率  

STIME  进程启动时间

TTY      关联终端  ?表示没有关联终端 

TIME     进程占用CPU的时间

CMD      执行进程的命令

可以和grep配合使用,检索指定命令

ps    -ef    | grep   ./a.out

2.ps    -ajx 

linux@ubuntu:~/HQYJ/day27$ ps -ajx
  PPID    PID   PGID    SID TTY       TPGID  STAT   UID   TIME   COMMAND
     0          1       1          1     ?            -1          Ss       0       0:06   /sbin/ini
     0          2       0          0     ?            -1           S        0      0:00    [kthreadd
     2          3       0          0     ?            -1           I<       0       0:00    [rcu_gp]
PPID                父进程号

PID                   进程号

PGID                组进程号

SID                   会话ID     打开一个终端,就产生一个会话

                                         一个会话中有一个前台进程组和多个后台进程组

TTY                  是否关联终端

TPGID              该字段为-1,是守护进程

STAT                 进程的状态

UID                   进程所属用户

TIME                 进程占用CPU时间

COMMAND       执行进程的命令,就是进程名

 3.动态的显示进程的状况  top或htop,没有就sudo apt-get安装

按q退出

top

 htop

4.pidof

        pidof 进程名 //查看进程对应的PID

5.kill

        kill   -l      查看kill信号

        kill   -2     PID     给进程发送一个终止的信号 (ctrl + c)

        kill   -9     PID      杀死进程

        kill   -19   PID      暂停进程

        kill   -18   PID      恢复暂停的进程

        killall    a.out        杀死所有名为a.out的进程

        ./a.out      &          后台运行

4.进程的状态

        D      不可中断的休眠态

        S      可中断的休眠态

        R      运行态和就绪态(在就绪队列里)

        T       停止(暂停)态

        X       死亡态(不会被看到)

        Z       僵尸态(进程结束后,没有被父进程回收资源)

附加状态

        +      前台运行

        <       高优先级

        N       低优先级

        s        会话组组长

        L        有内存页在内存中锁住

        l         进程中有多线程

关于进程优先级:nice值

[-20, +19] 值越小 优先级越高

sudo nice -n 值 ./a.out //将a.out进程按照指定优先级的值来执行

sudo renice 新的值 pid //可以修改进程的优先级

5.进程状态切换实例

拥有cpu以后也有可能会变成可中断的休眠态

例1.

#include <head.h>

int main(int argc,const char * argv[])
{
    while(1){
        sleep(1);
        printf("hello..\n");
    }
    return 0;
}

ps -ajx |grep a.out

kill   -19  (其它终端)  pid    或者   ctrl   +  z (当前终端)    暂停进程

 jobs    -l  (当前终端)    查看作业号

 bg  作业号 (在任务终端)  或者kill   -18   pid (另一个终端)    后台继续运行

 fg  作业号   前台继续运行

 例2.

#include <head.h>
int main(int argc,const char * argv[])
{
    while(1){}
    return 0;
}

ps -ajx |grep a.out

kill   -19    pid (其它终端)   或者   ctrl   +  z (当前终端)    暂停进程

 ctrl   +  z (当前终端)

 kill   -19   pid

bg  作业号 (在任务终端)  或者kill   -18   pid (另一个终端)    后台继续运行

 fg  作业号   前台继续运行

 二、进程的创建

1.进程创建原理

        进程创建的过程是通过拷贝父进程来完成的。子进程的所有资源都完全拷贝于父进程。

        拷贝生成子进程之后,父子进程相互独立。

2.进程创建函数

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);
功能:拷贝父进程 创建子进程

参数:无

返回值:成功:子进程返回0,子进程PID返回给父进程

              失败:返回-1给父进程 不会创建子进程,重置错误码

3.进程创建实例

 (1)不关注返回值

#include <head.h>
int main(int argc,const char * argv[])
{
    fork();
    while(1);//防止进程结束 方便看现象的
    return 0;
}

 思考1:会有几个进程

#include <head.h>
int main(int argc,const char * argv[])
{
    fork();
    fork();
    while(1);//防止进程结束 方便看现象的
    return 0;
}

      fork以后会有自身和其子进程两个进程,这两个进程还会在产生两个进程  

注意:如果不考虑返回值的情况 n次fork 会产生 2^n 个进程

思考2:会有几个#

#include <head.h>
int main(int argc,const char * argv[])
{
    for(int i = 0; i < 2; i++){
        fork();
        printf("#");
    }
    return 0;
}

思考3:有几个#

#include <head.h>
int main(int argc,const char * argv[])
{
    for(int i = 0; i < 2; i++){
        fork();
        printf("#\n");
    }
    return 0;
}

思考4:会有几个#

#include <head.h>
int main(int argc,const char * argv[])
{
    for(int i = 0; i < 2; i++){
        printf("#");
        fork();
    }
    return 0;
}

 思考5:有几个#

#include <head.h>
int main(int argc,const char * argv[])
{
    for(int i = 0; i < 2; i++){
        printf("#\n");
        fork();
    }
    return 0;
}

(2) 关注返回值 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc, const char *argv[])
{
    printf("开始\n");
    pid_t pid = 0;
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 == pid){
        //子进程
        printf("我是子进程..\n");
    }else if(0 < pid){
        //父进程
        printf("我是父进程..\n");
    }
    printf("结束\n");
    return 0;
}

执行结果

 执行逻辑

(3)父子进程执行的顺序问题 

int main(int argc, const char *argv[])
{
    pid_t pid = 0;
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 == pid){
        //子进程
        while(1){
            printf("我是子进程..\n");
            sleep(1);
        }
    }else if(0 < pid){
        //父进程
        while(1){
            printf("我是父进程..\n");
            sleep(1);
        }
    }
    return 0;
}

父子进程执行没有先后顺序,时间片轮转,上下文切换,谁先抢到时间片谁先执行。

(4)父子进程内存空间的问题

父子进程的内存空间是独立的,

父进程fork产生子进程的时候,使用的是写时拷贝的原则

int main(int argc, const char *argv[])
{
	int a = 1314;
	pid_t pid = 0;
	if(-1 == (pid = fork())){
		ERRLOG("fork error");
	}else if(0 == pid){
		//子进程
		printf("子进程1 a = %d\n", a);//1314
		sleep(2);
		a = 520;//写时拷贝 映射到不同的物理内存
		printf("子进程2 a = %d\n", a);//520
	}else if(0 < pid){
		//父进程
		printf("父进程1 a = %d\n", a);//1314
		sleep(3);
		printf("父进程2 a = %d\n", a);//1314
	}
	return 0;
}

4.getpid和getppid

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);

功能:返回当前进程的进程号

pid pid_t getppid(void);

功能:返回当前进程的父进程的pid:

int main(int argc,const char * argv[])
{
    pid_t pid= 0;
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 == pid){
        printf("我是子进程 pid = %d  ppid = %d\n", getpid(), getppid());
    }else if(0 < pid){//父进程中 变量pid保存的就是子进程的pid
        printf("我是父进程 pid = %d  ppid = %d 子进程的pid = %d\n", 
            getpid(), getppid(), pid);
    }
    return 0;
}

练习1:使用fork产生3个进程

            A--->fork--->B B--->fork--->C

           并输出三个进程的pid和ppid

int main(int argc,const char * argv[])
{ 
    /*pid_t p=0;
    if(-1==(p=fork())){
        perror("fork error");
    }else if(p==0){
        fork();
        printf("%d %d\n",getpid(),getppid());
    }else if(p>0){
        sleep(1);
        printf("A: %d %d\n",getpid(),getppid());
    }*/
        pid_t p=0;
    if(-1==(p=fork())){
        perror("fork error");
    }else if(p==0){
        if(-1==(p=fork())){
            perror("fork error");
        }else if(p==0){
            printf("C: %d %d\n",getpid(),getppid());
        }else if(p>0){
            sleep(1);
            printf("B: %d %d\n",getpid(),getppid());
        }
    }else if(p>0){
        sleep(2);
        printf("A: %d %d\n",getpid(),getppid());
    }
    return 0;
}

 执行结果,两种写法都对

 加sleep是为了保证子进程先结束,父进程在结束,因为父子进程没有先后顺序

比如下面这个,即使父进程先结束,子进程交由init进程的子进程回收,

 可以用 ps -ef |grep 2080查看,可以发现2080是1号进程的子进程

 练习2 使用两个进程实现文件的拷贝

           父进程拷贝 前半部分

           子进程拷贝 后半部分

注意:fork之前open打开的文件 父子进程中是共享光标(文件偏移量)的,如下面的例子

int main(int argc,const char * argv[])
{
    //hello.txt  -->12345678
    int fd = open("hello.txt", O_RDONLY);
    if(-1 == fd)
        ERRLOG("open error");
    pid_t pid = 0;
    char buff[4] = {0};
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 == pid){//子进程
        read(fd, buff, 4);
        printf("child buff = [%s]\n", buff);//1234
    }else if(0 < pid){//父进程
        sleep(1);
        //注意 fork之前open打开的文件 父子进程中是共享光标的
        read(fd, buff, 4);
        printf("parent buff = [%s]\n", buff);//5678
    }

    return 0;
}

练习代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//统计文件大小,单数则父进程多读一个,利用ftell,返回光标位置
long filelen(const char *filename){
    FILE *p=NULL;
    if(NULL==(p=(fopen(filename,"r")))){
        perror("fopen error");
    }
    fseek(p,0,SEEK_END);
    long len=ftell(p);
    return len;
}
int main(int argc, char const *argv[])
{
    if(argc!=3){
        printf("Usage: %s srcfilename destfilename\n",argv[0]);
        exit(-1);
    }
    pid_t p=0;
    int fd1=0;
    int fd2=0;
    char redbuf[128]={0};
    long len=filelen(argv[1]);
    if(-1==(p=fork())){
        perror("fork error");
        exit(-1);
    }
    //parent
    else if(p==0){
        if(-1==(fd1=open(argv[1],O_RDONLY))){
            perror("fd1 open error");
            exit(-1);
        }
        if(-1==(fd2=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664))){
            perror("fd2 open error");
            exit(-1);
        }
        int ret=0;
        if((len%2)!=0){
            //双数读到(len/2)位置,利用ret计数
            while(0<(ret=read(fd1,redbuf,sizeof(redbuf)))){
                write(fd2,redbuf,ret);
                ret+=ret;
                if(ret==(len/2))  
                    break;
            }
            close(fd1);
            close(fd2);
        }else{//单数读到(len/2)+1位置
            while(0<(ret=read(fd1,redbuf,sizeof(redbuf)))){
                write(fd2,redbuf,ret);
                ret+=ret;
                if(ret==(len/2)+1)  
                    break;
            }
            close(fd1);
            close(fd2);
        }
    }//child
    else if(p>0){
        if(-1==(fd1=open(argv[1],O_RDONLY))){
            perror("fd2 open error");
            exit(-1);
        }
        if(-1==(fd2=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664))){
            perror("fd2 open error");
            exit(-1);
        }
        //定位,单数到一半+1的位置
        if((len%2)!=0){
            lseek(fd1,(len/2)+1,SEEK_SET);   
        }else{//双数就到一半的位置
            lseek(fd1,(len/2),SEEK_SET); 
        }
        int ret=0;
        //写入文件
        while(0<(ret=read(fd1,redbuf,sizeof(redbuf)))){
            write(fd2,redbuf,ret);
        }
        close(fd1);
        close(fd2);
    }
    return 0;
}

5.孤儿进程

孤儿进程:父进程退出之后,子进程就变成孤儿进程了,孤儿进程会被init进程"收养"。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,const char * argv[])
{
    pid_t pid = 0;
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 < pid){//父进程
        printf("我是父进程 pid = %d 我在10s后退出\n", getpid());
        sleep(10);
    }else if(0 == pid){//子进程
        while(1){
            printf("我是子进程 pid = %d ppid = %d\n",
                getpid(), getppid());
            sleep(1);
        }
    }
    return 0;
}

6.僵尸进程 

僵尸进程:子进程退出的时候,父进程在忙别的事儿,没有给子进程回收资源,

子进程就变成了僵尸进程,init不会回收僵尸进程,僵尸进程对系统是有害的。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,const char * argv[])
{
    pid_t pid = 0;
    if(-1 == (pid = fork())){
        ERRLOG("fork error");
    }else if(0 < pid){//父进程
        printf("我是父进程 pid = %d \n", getpid()); 
        while(1){
            printf("hello\n");
            sleep(1);
        }
    }else if(0 == pid){//子进程
        printf("我是子进程 pid = %d ppid = %d\n", 
            getpid(), getppid());
    }
    return 0;
}

 父进程结束的时候会给子进程收尸

7.进程退出exit/_exit函数

 exit

#include <stdlib.h>

void exit(int status);

功能:exit是一个库函数,用来退出一个进程,在退出进程时会刷新缓冲区

参数:在子进程结束的时候,用来将退出的状态返回给父进程

            EXIT_SUCCESS 成功  EXIT_FAILURE 失败(非0就表示失败)

返回值:无
 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,const char * argv[])
{
    printf("hello");//会输出 因为 exit 会刷新缓冲区
    //#define   EXIT_FAILURE    1
    //#define   EXIT_SUCCESS    0
    exit(EXIT_SUCCESS);
    printf("beijing");//不会输出 因为 exit已经将进程结束了
    return 0;
}

_exit

#include <unistd.h>
void exit(int status);

功能:_exit是一个系统调用,用来退出一个进程,在退出进程时不会刷新缓冲区

参数:在子进程结束的时候,用来将退出的状态返回给父进程

            EXIT_SUCCESS 成功   EXIT_FAILURE 失败

返回值:无

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,const char * argv[])
{
    printf("hello");//不会输出 因为 _exit 不会刷新缓冲区
    _exit(EXIT_SUCCESS);
    printf("beijing");//不会输出 因为 _exit已经将进程结束了
    return 0;
}

问:在main遇到return之后还能够执行其他的函数?

答:可以,可以使用atexit函数取注册一个进程结束后调用的函数

当进程结束的时候这个注册的函数就会被回调。

#include <head.h>
int add(int a,int b)
{
    return (a+b);
}
void fin_invok_func(void)
{
    printf("当前进程执行了这个函数后退出了...\n");
    printf("sum = %d\n",add(100,200));
}
int main(int argc,const char * argv[])
{
    atexit(fin_invok_func); //注册进程退出时候执行的函数
    printf("hello world\n");
    return 0;
}

8.回收进程资源wait/waitpid函数

wait

#include <sys/types.h> 

#include <sys/wait.h>

pid_t wait(int *wstatus);
 

功能:

         wait在父进程中使用,用来回收子进程的资源。

         这个函数会阻塞等待子进程的退出,

         子进程exit或者_exit退出的状态,可以被父进程的wait收到

         wait函数可以回收任意一个子进程的资源

         0-6: 7bit位 子进程被信号中断时信号的编号

         8-15:8bit位 进程退出时的值

         man手册中有说明

         如果 WIFEXITED(wstatus) 为真说明是进程正常退出的

                 正常退出 _exit exit 或者 main函数 return 也算

                 正常退出时可以使用WEXITSTATUS(wstatus)来获取exit返回的值

         如果 WIFSIGNALED(wstatus) 说明是由于被信号中断的

                 被信号中断的使用 WTERMSIG(wstatus) 可以获取信号的编号

参数:wstatus:子进程退出时返回的值。如果为NULL 表示不关心子进程结束的状态

返回值:

                成功 退出的子进程的进程号

                失败 -1 重置错误码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    pid_t p=0;
    if(-1==(p=fork())){
        perror("fork error");
    }else if(p==0){
        printf("子进程pid:%d ppid:%d\n",getpid(),getppid());
        sleep(5);
        exit(123);
        //while (1);
        
    }else if(p>0){
        sleep(20);//由于子进程先结束 在父进程给他回收资源之前 子进程都是僵尸态
        wait(NULL);//NULL 表示不关心子进程退出的状态
        printf("wait end..\n");//wait之后 子进程的资源的就回收了 就不是僵尸进程了
        //如果想获取子进程退出的状态 需要使用下面的用法
        /*int value=0;
        wait(&value);
        printf("父进程: pid:%d ppid:%d 子进程pid: %d\n",getpid(),getppid(),p);
        if(WIFEXITED(value)){
            printf("正常退出  value:%d\n",WEXITSTATUS(value));
        }
        else if(WIFSIGNALED(value)){
            printf("被中断的信号是:%d\n",WTERMSIG(value));
        }*/
        sleep(10);
        printf("parent end..\n");
    }
    return 0;
}

 加了三个sleep产生的现象:

第一个sleep:让子进程晚5s结束,处于休眠态

第二个sleep:子进程结束退出后,父进程20s以后才给子进程回收资源,这之前子进程都是僵尸态

第三个sleep:当父进程走到wait以后,给子进程回收资源,子进程不再是僵尸态

waitpid

#include <sys/types.h> 

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *wstatus, int options);
 

功能:回收编号为pid的进程的资源

参数:pid

                >0     要回收资源的子进程的pid

                -1      和wait用法一样   wait(NULL)<==>waitpid(-1, NULL, 0);

                0       组id相同的组进程退出(回收和父进程组id相同的子进程)

                <-1    等待任何组id等于pid绝对值的子进程退出

           wstatus:pid进程退出时返回的值。如果为NULL 表示不关心子进程结束的状态

           options:回收子进程的方式
                            0:阻塞回收子进程资源
           WNOHANG:非阻塞回收子进程资源

返回值:

                成功 退出的子进程的进程号

                如果设置了WNOHANG 没有子进程退出 返回0

                失败 -1 重置错误码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    pid_t p=0;
    if(-1==(p=fork())){
        perror("fork error");
    }else if(p==0){
        printf("子进程pid:%d ppid:%d\n",getpid(),getppid());
        sleep(10);
        exit(257);
    }else if(p>0){
        //waitpid(p,NULL,0);<==>wait(NULL);
        int value=0;
        waitpid(p,&value,0);
        if(WIFEXITED(value)){
            printf("正常退出的value:%d\n",WEXITSTATUS(value));
        }
        if(WIFSIGNALED(value)){
            printf("被中断的信号是:%d\n",WTERMSIG(value));
        }
    }
    return 0;
}

 9.进程代码替换system函数的使用

#include <stdlib.h>

int system(const char *command);

功能:使用fork创建一个子进程,在子进程中执行shell命令

参数:命令字符串 如 "ls -l" "./hqyj"

返回值:如果command为NULL 终端可用返回非0 终端不可用返回0

                                                  (关联终端返回非0,没关联终端,返回0)

               如果fork失败 返回 -1

               如果执行命令成功了 返回的就是子进程exit或者_exit退出的状态

 system执行命令:

int main(int argc,const char * argv[])
{
    printf("start..\n");
#if 0
    //在进程中执行shell命令
    if(-1 == system("ls -l")){
        perror("system error");
    }
#endif
	//在一个进程中启动另一个进程
	if(-1 == system("./hqyj")){
		perror("system error");
	}
    printf("end..\n");
    return 0;
}

 system调用shell脚本:

#!/bin/bash

echo $#  #命令行参数的个数
echo $@  #命令行所有的参数
#include <head.h>

int main(int argc,const char * argv[])
{
    if(system("./myshell.sh 111 222 333")){
        printf("system error\n");
        return -1;
    }
    return 0;
}

system调用用户可执行程序:

#include <head.h>

int main(int argc,const char * argv[])
{
    if(system("./b.out")){
        printf("system error\n");
        return -1;
    }
    return 0;
}

10.守护进程

        1.概念

                相当于服务,随着系统的启动而启动,随着系统的关闭而终止,脱离终端的。

        2.创建守护进程的流程      

                1.创建孤儿进程

                2.调用setsid产生一个新的会话和一个新的进程组(脱离终端又叫脱离会话,一个终端界面就是一个会话,关闭终端,会话就结束了,该孤儿进程就是这个会话组的组长,此时这个终端就和这个孤儿进程没有关系了)

#include <unistd.h>

pid_t setsid(void);

功能:如果调用该函数的进程不是进程组的组长,

           则会产生一个新的会话和一个新的进程组

           新的会话id 和 新的组id都被设置成调用的进程pid

参数:无

返回值:成功 新的会话id

              失败 -1 重置错误码

                 3.修改工作目录为根目录

#include <unistd.h>

int chdir(const char *path);

功能:修改当前进程的工作目录为path指向的目录

返回值: 成功 0

                失败 -1 重置错误码

                 4.修改进程创建文件的掩码

#include <sys/types.h>

#include <sys/stat.h>

mode_t umask(mode_t mask);

功能:修改当前进程创建文件的掩码

        注意:不同功能的守护进程可以使用该函数设置不同的掩码

返回值: 总是会成功 返回的就是新设置的掩码

                 5.重定向标准输入、输出、出错到日志文件中

#include <unistd.h>

int dup(int oldfd);一般不使用

功能:根据oldfd产生新的文件描述符

           成功后新旧两个文件描述符都可以使用 

返回值: 成功 新的文件描述符

               失败 -1 重置错误码

int dup2(int oldfd, int newfd);

功能:将newfd重定向到oldfd中,会关闭newfd

返回值: 成功 新的文件描述符

               失败 -1 重置错误码

dup

#include <head.h>
int main(int argc, char const *argv[])
{
    int oldfd;
    char c;
    if(-1==(oldfd=open("t.txt",O_CREAT|O_RDWR|O_APPEND,0666))){
        EERLOG("open error");
    }
    close(0);
    close(1);//stdout->fileno
             //关闭标准输出的文件描述符,但是标准输出的文件指针还在
    close(2);
    dup(oldfd);//将标准输入定向到文件
    dup(oldfd);//将标准输出定向到文件
    dup(oldfd);//将标出出错定向到文件
    
    /*printf("T23456789\n");//需要刷新缓冲区,文件是全缓冲,\n不能刷新全缓冲的缓冲区
    fflush(stdout);*/
    write(1,"T23456789\n",10);//或者直接用能够刷新缓冲区的函数

    lseek(oldfd,0,SEEK_SET);
    scanf("%c",&c);
    printf("c = %c\n",c);
    return 0;
}

 dup2

                6.开启自己的服务即可

        3.守护进程使用实例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    //1.创建孤儿进程
    pid_t p=0;
    if(-1==(p=fork())){
        perror("fork error");
        exit(-1);
    }else if(p>0){
        exit(0);
    }else if(p==0){
    //2.调用setsit函数产生一个新的会话和一个新的组
        if(-1==setsid()){
            perror("setsid error");
            exit(-1);
        }
    //3.修改工作目录为根目录
        if(-1==chdir("/")){
            perror("chdir error");
            exit(-1);
        }
    //4.修改进程文件的掩码
        umask(0062);
    //5.打开日志文件
        int fd=open("pro.log",O_WRONLY|O_CREAT|O_TRUNC,0666);
        if(fd==-1){
            perror("open error");
            exit(-1);
        }
    //6.重定向标准输入、标准输出、标准出错至日志文件
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);
    //7.开启自己的服务
        while(1){
            printf("hello\n");
            fflush(stdout);
            sleep(1);
        }
    }
    return 0;
}

 执行结果

 因为修改了工作目录为根目录 所以执行时 需要加 sudo

-------------------------------------多线程----------------------------------------

一、线程

1.概念       

        线程(LWP)是轻量级的进程,进程是资源分配的最小单位,线程是系统调度的最小单位。

        线程不会分配内存空间,一个进程内至少有一个线程,叫做主线程,

        也可以有多个线程,这多个线程共用进程的内存空间(0-3G)。

        多线程没有对进程安全,多线程的效率比较高。

        多线程的函数是使用第三方的库函数

        编程时,需要包含头文件 #include<pthread.h>

        编译时,需要链接线程的函数库 -lpthread

2.创建线程的函数

#include <pthread.h>

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

功能:在当前进程产生一个新的线程

参数:thread:用来保存新线程ID的缓冲区的首地址

           attr:线程的属性 NULL 表示使用默认属性

           start_routine:线程体,函数指针,传一个函数

           arg:给start_routine传参的 NULL 表示不给线程传参

返回值:成功 返回0

              失败 返回错误码

例1:不传参 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
void *task_func(){
    printf("子线程\n");
}
int main(int argc, char const *argv[])
{
    pthread_t t=0;
    int ret=0;
    if(0!=(ret=pthread_create(&t,NULL,task_func,NULL))){
        printf("errno:%d  errstr:%s\n",ret,strerror(ret));
        exit(-1);
    }
    sleep(1);
    printf("主线程  新线程tid是:%lu\n",t);
    return 0;
}

例2:传参

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
void *task_func1(void *arg){
    //采用指针的形式,直接指向主线程传的参数的地址,当利用指针改变内部变量
    //的值的时候吗,主线程里的值也会变
    //int *p=(int *)arg;
    //printf("子线程1 %d\n",*p);
    int num=*(int *)arg;
    sleep(1);//在这延时,会先被线程2改变value的值
    printf("子线程1 %d\n",num);
}
void *task_func2(void *arg){
    int *p=(int *)arg;
    //sleep(1);当在这延时以后,会先执行线程1,在到此线程,value的值会被改变
    *p=17;
    printf("子线程2 %d\n",*p);
}
int main(int argc, char const *argv[])
{
    pthread_t t[2];
    int ret=0;
    int value=1526;
    if(0!=(ret=pthread_create(&t[0],NULL,task_func1,&value))){
        printf("errno=%d errstr=%s\n",ret,strerror(ret));
        exit(-1);
    }
    if(0!=(ret=pthread_create(&t[1],NULL,task_func2,&value))){
        printf("errno=%d errstr=%s\n",ret,strerror(ret));
        exit(-1);
    }
    sleep(2);
    printf("主线程\n");
    return 0;
}

 当线程里用指针时,指向的就是主线程里参数的地址,所以在线程里改变指针变量的值也会使主线程里的值改变,其他线程在访问也是改变后的值了

如果想给线程传多个数据,可以使用结构体封装,如下面的例子:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
typedef struct Student{
    int id;
    char name[32];
    char sex;
}stu_t;

//线程处理函数
void *task_func(void *arg){
    stu_t num = *(stu_t *)arg;
    printf("我是子线程1..\n");
    printf("id = [%d] name = [%s] sex = [%c]\n",
        num.id, num.name, num.sex);
}

int main(int argc,const char * argv[])
{
    printf("我是主线程..\n"); 
    stu_t t1 = {1001, "zhangsan", 'M'};
    pthread_t tid = 0;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid, NULL, task_func, (void *)&t1))){
        printf("pthread_create error:errno = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(-1);
    }
    sleep(100);
    return 0;
}

3.多线程执行的顺序

多线程执行也没有先后顺序,也是时间片轮转、上下文切换。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
//线程处理函数
void *task_func(void *arg){
    while(1){
        printf("hello\n");
        sleep(1);
    }
}

int main(int argc,const char * argv[])
{
    printf("我是主线程..\n"); 
    int value = 1314;
    pthread_t tid = 0;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid, NULL, task_func, NULL))){
        printf("pthread_create error:errno = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(-1);
    }
    while(1){
        printf("beijing\n");
        sleep(1);
    }
    return 0;
}

 执行结果

4.多线程内存空间的问题

多线程共用进程的资源,所以多线程间如果需要传递数据,使用全局变量即可。

(线程间通信简单,但是不安全)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
int num = 1314;//线程间通信 通过全局变量即可 但是不安全

//线程处理函数
void *task_func(void *arg){
    sleep(5);
    num = 520;
}

int main(int argc,const char * argv[])
{
    printf("我是主线程..\n"); 
    pthread_t tid = 0;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid, NULL, task_func, NULL))){
        printf("pthread_create error:errno = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(-1);
    }
    while(1){
        printf("num = %d\n", num);
        sleep(1);
    }
    //子线程休眠5秒 这5秒内 此处输出 1314
    //5秒后 子线程把num修改成 520
    //后面再输出 就都是520了
    //也就是说,全局变量 多线程都能访问
    return 0;
}

5. pthread_self函数的使用

#include <pthread.h>

pthread_t pthread_self(void);

功能:获取调用线程的线程id (tid)

参数:无

返回值:总是会成功 返回调用者tid

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
//多线程执行的顺序
void *task1(void *argc){
        printf("子线程  tid:%lu\n",pthread_self());
}
int main(int argc, char const *argv[])
{
    pthread_t t=0;
    int ret=0;
    if(0!=(ret=pthread_create(&t,NULL,task1,NULL))){
        printf("errno:%d  errstr:%s\n",ret,strerror(ret));
        exit(-1);
    }
        printf("主线程id:%ld   子线程id:%lu\n",pthread_self(),t);
        sleep(100);
    return 0;
}

执行结果

 6. pthread_exit函数说明

#include <pthread.h>

void pthread_exit(void *retval);

功能:退出线程

参数:retval 用来返回线程退出状态的

           如果使用 可以用 pthread_join函数来接收

           如果不关心线程退出的状态 传 NULL 即可

返回值:无

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
void *hqyj(void *arg){
    printf("child thread, tid = [%lu]\n", pthread_self());
    //exit(0);//如果线程中调用了exit,会导致整个进程退出
    #if 0
    sleep(10);
    pthread_exit(NULL);//专门退出线程的函数
    #endif

    #if 1
    sleep(10);
    pthread_exit(NULL);//专门退出线程的函数
    #endif
}

int main(int argc,const char * argv[])
{
    printf("parent thread, tid = [%lu]\n", pthread_self());
    pthread_t tid;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid, NULL, hqyj, NULL))){
        printf("pthread_create error:errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    #if 0
    while(1){
        printf("hello\n");
        sleep(1);
    }
    #endif
    
    #if 1
    pthread_exit(NULL);//即使主线程调用 pthread_exit 也不会导致整个进程退出
    #endif
    return 0;
}

7. pthread_join函数的使用

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

功能:阻塞回收结合态的线程的资源

           不是只有主线程才能回收资源(同一个进程中的其他线程也可以给线程回收资源)

参数:thread:线程的tid

           retval:用来保存退出的线程调用pthread_exit时传的参数的

           如果不关心线程的退出状态 可以传 NULL

返回值:成功 0

              失败 返回错误码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>    
void *hqyj(void *arg){
    printf("child thread, tid = [%lu]\n", pthread_self());
    sleep(5);
    #if 0
    //一般调用pthread_exit都传NULL
    //pthread_exit(NULL);
    #endif
    #if 1
    pthread_exit((void *)1234);
    #endif
}

int main(int argc,const char * argv[])
{
    printf("parent thread, tid = [%lu]\n", pthread_self());
    pthread_t tid;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid, NULL, hqyj, NULL))){
        printf("pthread_create error:errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    #if 0
    //阻塞等待线程号是 tid 的线程退出 给退出的线程回收资源
    if(0 != (ret = pthread_join(tid, NULL))){
        printf("pthread_join error:errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    #endif

    #if 1
    int num = 0;
    pthread_join(tid, &num);//获取子线程退出是的值 ---一般不使用
    printf("子线程退出时传的值是 %d\n", num);//1234
    #endif
    printf("parent end..\n");

    return 0;
}

 传参的下面这种参数都是二级指针,上面都是一级指针

#include <head.h>

void* thread(void* arg)
{
    static int num = 1234;
    sleep(1);
    printf("111111111111\n");
    pthread_exit((void *)&num);
    printf("222222222222\n");
}
int main(int argc, const char* argv[])
{
    pthread_t tid;
    if ((errno = pthread_create(&tid, NULL, thread, NULL)) != 0)
        PRINT_ERR("pthread_create error");

    int *retval;
    pthread_join(tid,(void **)&retval);
    printf("*retval = %d\n",*retval);
    return 0;
}

8. pthread_detach函数的使用

线程有两种状态,分别是结合态和分离态,默认线程的状态是结合态。

如果将线程设置成分离态,则退出时,由操作系统负责回收线程的资源

如果是结合态,则必须使用pthread_join来回收线程的资源

如果线程的资源没有被回收,进程结束的时候,会回收所有的资源。

#include <pthread.h>

int pthread_detach(pthread_t thread);

功能:将线程标记成分离态,结束时由操作系统负责回收线程的资源

参数:thread:线程的id

返回值:成功 0

              失败 返回错误码

lude <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h> 
void *hqyj(void *arg){
    //在这里标记也可以
    //pthread_detach(pthread_self());
    printf("child thread, tid = [%lu]\n", pthread_self());
    sleep(5);
    pthread_exit(NULL);
}

int main(int argc,const char * argv[])
{
    printf("parent thread, tid = [%lu]\n", pthread_self());
    pthread_t tid;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid, NULL, hqyj, NULL))){
        printf("pthread_create error:errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    //标记线程为分离态
    if(0 != (ret = pthread_detach(tid))){
        printf("pthread_detach error:errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    sleep(10);
    printf("parent end..\n");
    return 0;
}

练习:

使用多线程实现拷贝文件的功能。

和多进程实现 思路一样:

1.获取源文件的大小

2.创建n个线程(如果想让系统回收资源,标记成分离态)

3.给每个线程分配要拷贝的任务(src_file dest_file start_pos copy_len)

注意:线程处理函数中不能使用我们的ERRLOG 因为里面有 exit 会导致整个进程退出。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>
struct fileinfo{
    const char *src_file; 
    const char *dest_file;
    //const char src_file[64];
    //const char dest_file[64];//数组名是常量,不能赋值
};
//定位函数,当文件为单数时,定位到文件1/2+1处,当文件为偶数时,定位到文件一半处
long filelen(const char *filename){
    FILE *fp=NULL;
    if(NULL==(fp=fopen(filename,"r"))){
        perror("fopen error");
    }
    if(-1==fseek(fp,0,SEEK_END)){
        perror("fseek error");
    }
    long ret=0;
    if(-1==(ret=ftell(fp))){
        perror("ftell error");
    }
    if(ret%2==0){
        return ret/2;
    }else{
        return ret/2+1;
    }
    
}
//子线程:前半段
void *task(void *arg){
    //将子线程设置为分离态,由操作系统回收线程资源,不需要在主线程里调用pthread_join函数单独回收了
    int detachret=0;
    if(-1==(detachret=pthread_detach(pthread_self()))){
        printf("pthread_detach error:errno = [%d] errstr = [%s]\n",detachret, strerror(detachret));
        exit(-1);
    }
    struct fileinfo file=*(struct fileinfo *)arg;//记得强转指针类型,否则操作空间不一样,会有问题,
                                                 //产生错误open error:bad address,需要让子线程的源文件和目标文件指向主线程里的argv[1],argv[2]
                                                 //也就是主线程里pthread_create里的第四个参数,这个参数是void *类型的,不强转为结构体类型,
                                                 //一是操作空间不一样,二是void *类型无法取*操作,不能操作指向空间的内容
                                                 //这样相当于把主线程里的也拷贝一份到子线程独立的栈区里了,就不会出现指针非法访问的问题了
    //以只读的方式打开源文件
    int fd1=open(file.src_file,O_RDONLY);
    if(fd1==-1){
        perror("zi1open error");
        exit(-1);
    }
    //以只写的方式打开目标文件
    int fd2=open(file.dest_file,O_CREAT|O_WRONLY|O_TRUNC,0664);
    if(fd2==-1){
        perror("zi2open error");
        exit(-1);
    }
    //调用定位函数
    long len=filelen(file.src_file);
    //将光标移动到文件开头,因为次线程只复制文件前半部分
    if(-1==lseek(fd1,0,SEEK_SET)){
        perror("lseek error");
        exit(-1);
    }
    int redret=0;
    int sum=0;
    char redbuf[32]={0};
    //循环读写,直到读到文件长度等于len时,说明,已经复制了一半了
    while(0<(redret=read(fd1,redbuf,sizeof(redbuf)))){
        write(fd2,redbuf,redret);
        sum+=redret;
        if(sum=len){
            break;
        }
    }
    //关闭文件
    close(fd1);
    close(fd2);
    //结束线程
    pthread_exit(NULL);
}
//主线程:后半段
int main(int argc, char const *argv[])
{
    pthread_t t=0;
    int ret=0;
    struct fileinfo file;
    file.src_file=argv[1];
    file.dest_file=argv[2];//数组名是常量,不能赋值
    if(0!=(ret=pthread_create(&t,NULL,task,&file))){
        printf("pthread_create error : errno[%d] errstr[%s]\n",errno,strerror(errno));
    }
    int fd1=open(file.src_file,O_RDONLY);
    if(fd1==-1){
        perror("zhu1open error");
        exit(-1);
    }
    int fd2=open(file.dest_file,O_CREAT|O_WRONLY|O_TRUNC,0664);
    if(fd2==-1){
        perror("zhu2open error");
        exit(-1);
    }
    long len=filelen(file.src_file);
    char redbuf[32]={0};
    int redret=0;
    while(0<(redret=read(fd1,redbuf,sizeof(redbuf)))){
        write(fd2,redbuf,redret);/切记都多少写多少,上一行read的返回值是用来给write的size参数用的,read的返回值是很有必要接的,一定不能sizeof(redbuf),会多东西
    }
    close(fd1);
    close(fd2);
    return 0;
}

9. pthread_cancel函数的使用

#include <pthread.h>

int pthread_cancel(pthread_t thread);

功能:一个线程向另一个线程发送一个取消的信号

           如果线程想响应这个信号,必须处于enable状态

           如果线程内是死循环, 线程的类型必须设置成                                 PTHREAD_CANCEL_ASYNCHRONOUS 才可以被立即取消

参数:thread:线程的id

返回值:成功 0

              失败 错误码

int pthread_setcancelstate(int state, int *oldstate);

        PTHREAD_CANCEL_ENABLE 可以被取消(默认)

        PTHREAD_CANCEL_DISABLE 不可被取消

int pthread_setcanceltype(int type, int *oldtype);

        PTHREAD_CANCEL_ASYNCHRONOUS 可以立即被取消         PTHREAD_CANCEL_DEFERRED 延时被取消(默认)

        延时到下一次调用可以作为取消点的函数时取消,

        一般涉及到用户空间和内核空间切换的函数都是可以作为取消点的函数

        我们的线程处理函数中,一般情况下都会有一个或多个可以作为取消点的函数

        如 sleep printf read open write ..

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>

void *task_func1(void *arg){
    pthread_detach(pthread_self());//设置分离态
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);//可以被取消
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);//可以被立即取消
    //pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);//不可以被立即取消
    //如果线程设置了不可以被立即取消的类型,线程中又没有调用可以作为取消点的函数
    //那么线程就不会被取消了
    int x = 0;
    while(1){
        //printf("我是线程1 %lu\n", pthread_self());
        //sleep(1);
        x = 1;
    }
    pthread_exit(NULL);
}

void *task_func2(void *arg){
    pthread_detach(pthread_self());//设置分离态
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//不可以被取消
    while(1){
        printf("我是线程2 %lu\n", pthread_self());
        sleep(1);
    }
    pthread_exit(NULL);
}

int main(int argc,const char * argv[])
{
    pthread_t tid1, tid2;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, task_func1, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, task_func2, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    sleep(5);
    //5秒后给线程发送取消的信号
    pthread_cancel(tid1);
    pthread_cancel(tid2);

    sleep(100);
    return 0;
}

二、线程的互斥

在多线程中,如果多线程访问同一个全局变量,

就会出现多个线程中在获取变量的值的时候获取的是同一个值,

而在线程内部操作之后,可能会导致冲突的情况。

下面的例子就可以看到对应的现象,

可以使用互斥锁来解决。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>

int money = 1000;

//张三取钱
void *task_func1(void *arg){
    pthread_detach(pthread_self());//设置分离态
    while(1){
        sleep(1);
        if(money >= 100){
            money -= 100;
            printf("张三取走 100 元, 剩余 %d 元\n", money);
        }else{
            printf("没钱了 张三取钱失败\n");
            pthread_exit(NULL);
        }
    }
}

//李四取钱
void *task_func2(void *arg){
    pthread_detach(pthread_self());//设置分离态
    pthread_detach(pthread_self());//设置分离态
    while(1){
        sleep(1);
        if(money >= 50){
            money -= 50;
            printf("李四取走 50 元, 剩余 %d 元\n", money);
        }else{
            printf("没钱了 李四取钱失败\n");
            pthread_exit(NULL);
        }
    }
}

int main(int argc,const char * argv[])
{
    pthread_t tid1, tid2;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, task_func1, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, task_func2, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    sleep(100);
    return 0;
}

执行结果:

1.互斥锁相关API

 #include <pthread.h>

1.定义互斥锁

   pthread_mutex_t lock;

   pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//静态初始化 

2.初始化锁

        int pthread_mutex_init(pthread_mutex_t *mutex,

                                const pthread_mutexattr_t *mutexattr);

        功能:动态初始化互斥锁

        参数:mutex:要初始化的锁

                   mutexattr:属性 一般传 NULL

        返回值:总是会成功 返回0

3.上锁

       int pthread_mutex_lock(pthread_mutex_t *mutex);

        功能:上锁(如果没有锁可以上锁,线程将被阻塞)

        参数:mutex 互斥锁指针

        返回值:成功 0

                      失败 错误码

4.解锁

        int pthread_mutex_unlock(pthread_mutex_t *mutex);

        功能:解锁(一般使用时,只有加锁的线程可以解锁)(退出线程前也要解锁,不然会产生死锁)

        参数:mutex 互斥锁指针

        返回值:成功 0

                      失败 错误码

5.销毁锁

        int pthread_mutex_destroy(pthread_mutex_t *mutex);

        功能:在不需要使用互斥锁的时候销毁互斥锁

        参数:mutex 互斥锁指针

        返回值:成功 0

                      失败 错误码

2.互斥锁使用实例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>

int money = 1000;
pthread_mutex_t lock;
//pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//使用静态初始化也可以
//张三取钱
void *task_func1(void *arg){
    pthread_detach(pthread_self());//设置分离态
    while(1){
        sleep(1);
        pthread_mutex_lock(&lock);//加锁
        if(money >= 100){
            money -= 100;
            printf("张三取走 100 元, 剩余 %d 元\n", money);
        }else{
            printf("没钱了 张三取钱失败\n");
            pthread_mutex_unlock(&lock);//解锁
            pthread_exit(NULL);
        }
        pthread_mutex_unlock(&lock);//解锁
    }
}

//李四取钱
void *task_func2(void *arg){
    pthread_detach(pthread_self());//设置分离态
    while(1){
        sleep(1);
        pthread_mutex_lock(&lock);//加锁
        if(money >= 50){
            money -= 50;
            printf("李四取走 50 元, 剩余 %d 元\n", money);
        }else{
            printf("没钱了 李四取钱失败\n");
            pthread_mutex_unlock(&lock);//解锁
            pthread_exit(NULL);
        }
        pthread_mutex_unlock(&lock);//解锁
    }
}

int main(int argc,const char * argv[])
{
    //动态初始化互斥锁
    pthread_mutex_init(&lock, NULL);
    pthread_t tid1, tid2;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, task_func1, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, task_func2, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    sleep(100);
    //销毁互斥锁
    pthread_mutex_destroy(&lock);

    return 0;
}

3.线程互斥锁死锁问题

  1. 产生死锁的四个必要条件

    互斥,请求保持,不可剥夺,循环等待

  2. 死锁的规避方法

    1.指定线程获取锁的顺序

    2.尽量避免锁的嵌套使用

    3.给线程上锁指定超时时间(pthread_mutex_timedlock)

    4.在全局位置指定锁是否被使用的状态,如果被使用就不在获取

三、线程的同步

        线程的同步:指已经提前知道了线程应该要有的执行顺序,控制线程按顺序执行。

        典型的使用场景:生产者消费者模型

1.文明信号量

1.1无名信号量相关API

#include <semaphore.h>

1.定义无名信号量

        sem_t  sem;

2.初始化无名信号量

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

        功能:初始化无名信号量

        参数: sem:无名信号量指针

                    pshared: 0:两个线程间同步

                                     非0:两个进程间同步

                    value:信号量的初始值

                                如果是1表示可以获取信号量

                                如果是0表示不可以获取信号量

        返回值: 成功 0

                       失败 -1 重置错误码

无名信号量是荷兰计算器科学家发明的,PV操作的PV来自荷兰语

3.获取信号量(P操作)

        int sem_wait(sem_t *sem);

        功能:获取信号量 (将信号量的值-1)

                如果信号量的值已经是0了,则sem_wait会阻塞,等到能执行减1操作为止

        参数:sem:无名信号量指针

        返回值: 成功 0

                       失败 不会改变信号量的值 返回 -1 重置错误码 4.释放信号量(V操作)

4.释放信号量(V操作)

        int sem_post(sem_t *sem);

        功能:释放信号量(将信号量的值+1)

        参数:sem:无名信号量指针

        返回值: 成功 0

                       失败 不会改变信号量的值 返回 -1 重置错误码

5.销毁无名信号量

        int sem_destroy(sem_t *sem);

        功能:销毁无名信号量

        参数:sem:无名信号量指针

        返回值: 成功 0

                       失败  -1 重置错误码

1.2无名信号量使用实例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>

sem_t sem;//定义无名信号量

//生产者线程函数
void *task_func1(void *arg){
    while(1){
        sleep(2);
        printf("我是生产者线程,我生产了一台 联想电脑..\n");
        sem_post(&sem);
    }
}

//消费者线程函数
void *task_func2(void *arg){
    while(1){
        sem_wait(&sem);
        sleep(1);
        printf("我是消费者线程,我购买了一台 联想电脑..\n");
    }
}

int main(int argc,const char * argv[])
{
    //初始化无名信号量
    sem_init(&sem, 0, 0);
    pthread_t tid1, tid2;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, task_func1, NULL))){
        printf("pthread_create error: errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, task_func2, NULL))){
        printf("pthread_create error: errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    //销毁无名信号量
    sem_destroy(&sem);

    return 0;
}

两个信号量配合使用的例子

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/wait.h>


sem_t sem;//定义无名信号量
sem_t sem2;

//生产者线程函数
void *task_func1(void *arg){
    while(1){
        sem_wait(&sem2);//sem2初始值就是1,所以会先执行生产者
        sleep(1);
        printf("我是生产者线程,我生产了一台 联想电脑..\n");
        sem_post(&sem);//只使用一个信号量,如果消费者线程执行的慢 
                    //就可能导致 生产者线程多次执行 信号量的值可能被+到 大于1
                    //可以再使用一个信号量解决
    }
}

//消费者线程函数
void *task_func2(void *arg){
    while(1){
        sem_wait(&sem);
        sleep(5);
        printf("我是消费者线程,我购买了一台 联想电脑..\n");
        sem_post(&sem2);
    }
}

int main(int argc,const char * argv[])
{
    //初始化无名信号量
    sem_init(&sem, 0, 0);
    sem_init(&sem2, 0, 1);
    pthread_t tid1, tid2;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, task_func1, NULL))){
        printf("pthread_create error: errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, task_func2, NULL))){
        printf("pthread_create error: errno = [%d] errstr = [%s]\n",
            ret, strerror(ret));
        exit(-1);
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    //销毁无名信号量
    sem_destroy(&sem);
    sem_destroy(&sem2);

    return 0;
}

练习:

使用主线程创建3个子线程1,2,3;

子线程1,隔1秒打印一个A

子线程2,隔1秒打印一个B

子线程3,隔1秒打印一个C

要求实现输出: ABCABCABCABC...

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <semaphore.h>

sem_t sem1,sem2,sem3;
void *task1(void *arg){
    while(1){
        sem_wait(&sem3);
        sleep(1);
        printf("A");
        fflush(stdout);//记得刷新缓冲区,不刷新终端没东西
        sem_post(&sem1);
    }
}
    
void *task2(void *arg){
    while(1){
        sem_wait(&sem1);
        sleep(1);
        printf("B");
        fflush(stdout);
        sem_post(&sem2);
    }
}
void *task3(void *arg){
    while(1){
        sem_wait(&sem2);
        sleep(1);
        printf("C");
        fflush(stdout);
        sem_post(&sem3);
    }
}

int main(int argc, char const *argv[])
{
    sem_init(&sem1,0,0);
    sem_init(&sem2,0,0);
    sem_init(&sem3,0,1);
    pthread_t t1,t2,t3;
    int ret=0;
    if(0!=(ret=pthread_create(&t1,NULL,task1,NULL))){
        printf("errno:[%d] errstr:[%s]",ret,strerror(ret));
    }
    if(0!=(ret=pthread_create(&t2,NULL,task2,NULL))){
        printf("errno:[%d] errstr:[%s]",ret,strerror(ret));
    }
    if(0!=(ret=pthread_create(&t3,NULL,task3,NULL))){
        printf("errno:[%d] errstr:[%s]",ret,strerror(ret));
    }
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);

    sem_destroy(&sem1);
    sem_destroy(&sem2);
    sem_destroy(&sem3);
    return 0;
}

2.条件变量

2.1条件变量API

#include <semaphore.h>

1.定义条件变量

        pthread_cond_t cond;

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//静态初始化

2.初始化条件变量

       int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

        功能:动态初始化条件变量

        参数:cond:条件变量指针

                   cond_attr:条件变量的属性 NULL 表示使用默认属性

        返回值: 成功 0

                       失败 错误码

3.获取条件变量

        int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

        功能:获取条件变量

        参数:cond:条件变量指针

                   mutex:互斥锁指针

        返回值:成功  0

                       失败 错误码

         使用流程:

               1.先获取到互斥锁 (可以先完成一些任务初始化的工作)

               2.调用pthread_cond_wait

                          2.1 将当前线程添加到队列中

                          2.2 解锁

                          2.3 在队列中休眠(等待的条件没发生)

                          2.4 重新获取锁

                                这时,如果等待的条件没有发生,就将其他的线程加入队列

                                如果等待的条件发生了,会将此线程在队列中移除

                3.执行后续的任务

                4.解锁

4.释放条件变量

        int pthread_cond_signal(pthread_cond_t *cond);

        功能:释放一个条件变量,唤醒至少一个等待条件的线程

        参数:cond:条件变量指针

        返回值:成功 0

                       失败 错误码

       int pthread_cond_broadcast(pthread_cond_t *cond);

        功能:释放所有的资源,唤醒所有等待条件的线程

        参数:cond:条件变量指针

        返回值:成功 0

                       失败 错误码

5.销毁条件变量

        int pthread_cond_destroy(pthread_cond_t *cond);

        功能:销毁条件变量

        参数:cond:条件变量指针

        返回值: 成功 0

                       失败  错误码

2.2 条件变量使用实例

两个线程的例子(生产者和消费者)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <semaphore.h>

pthread_mutex_t lock;//互斥锁
pthread_cond_t cond;//条件变量

//生产者线程
void *task_func1(void *arg){
    while(1){
        sleep(2);
        printf("生产者线程,生产了一个发动机..\n");
        pthread_cond_signal(&cond);//释放条件变量
    }
}

//消费者线程
void *task_func2(void *arg){
    while(1){
        pthread_mutex_lock(&lock);
        printf("消费者线程:汽车其他零件准备好,等待生产发动机...\n");
        pthread_cond_wait(&cond, &lock);//获取条件变量
        printf("消费者线程:获取到指示,开始购买发动机,进行后续的组装操作..\n");
        pthread_mutex_unlock(&lock);
    }
}

int main(int argc,const char * argv[])
{
    //初始化互斥锁
    pthread_mutex_init(&lock, NULL);
    //初始化条件变量
    pthread_cond_init(&cond, NULL);
    pthread_t tid1, tid2;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid1, NULL, task_func1, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    if(0 != (ret = pthread_create(&tid2, NULL, task_func2, NULL))){
        printf("pthread_create error:errno=[%d] errstr=[%s]\n",
            ret, strerror(ret));
        exit(-1);
    }
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    //销毁互斥锁
    pthread_mutex_destroy(&lock);
    //销毁条件变量
    pthread_cond_destroy(&cond);
    
    return 0;
}

多个线程的例子(1生产者和4消费者)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <pthread.h>
#include <semaphore.h>

pthread_mutex_t lock;//互斥锁
pthread_cond_t cond;//条件变量

//生产者线程
void *task_func1(void *arg){
    while(1){
        #if 0
        sleep(5);
        printf("生产者线程,生产了一个发动机..\n");
        pthread_cond_signal(&cond);//释放一个条件变量
        //pthread_cond_signal会唤醒一个等待条件线程 具体哪一个不确定
        #endif
        sleep(5);
        printf("生产者线程,生产了4个发动机..\n");
        pthread_cond_broadcast(&cond);//释放所有条件变量
        //pthread_cond_broadcast会唤醒所有等待条件线程
        //谁先抢到时间片谁先执行,顺序不一定
    }
}

//消费者线程
void *task_func2(void *arg){
    while(1){
        pthread_mutex_lock(&lock);
        printf("消费者线程[%lu]:汽车零件准备好,等待生产发动机...\n", pthread_self());
        pthread_cond_wait(&cond, &lock);//获取条件变量
        printf("消费者线程[%lu]:获取到指示,开始购买发送机,进行后续的组装操作..\n", pthread_self());
        pthread_mutex_unlock(&lock);
    }
}

int main(int argc,const char * argv[])
{
    //初始化互斥锁
    pthread_mutex_init(&lock, NULL);
    //初始化条件变量
    pthread_cond_init(&cond, NULL);
    pthread_t tid1, tid2, tid3, tid4, tid5;
    //1个生产者线程
    pthread_create(&tid1, NULL, task_func1, NULL);
    //4个消费者线程
    pthread_create(&tid2, NULL, task_func2, NULL);
    pthread_create(&tid3, NULL, task_func2, NULL);
    pthread_create(&tid4, NULL, task_func2, NULL);
    pthread_create(&tid5, NULL, task_func2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    pthread_join(tid5, NULL);

    //销毁互斥锁
    pthread_mutex_destroy(&lock);
    //销毁条件变量
    pthread_cond_destroy(&cond);
    
    return 0;
}

2.3 条件变量适用场景

无名信号量适合线程数比较少的线程中实现同步过程,条件变量适合在大量线程实现同步过程。

例如条件变量的使用场景如下:比如你要编写一个12306买票的服务器

当客户端访问服务器的时候,服务器会创建一个线程服务于这个用户。

如果有多个用户同时想买票,此时服务需要在瞬间创建一堆线程,

这个时间比较长,对用户的体验感不好。

所以12306服务是在启动的时候都已经创建好一堆线程,先完成必要的初始化操作,

调用pthread_cond_wait让这些线程休眠,当有客户端请求买票的时候,

只需要唤醒这些休眠的线程即可,

由于省去了创建线程的时间,所以这种方式的效率非常的高。

练习:

使用条件变量实现两个线程的同步,生产者线程和消费者线程交替执行。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
//条件变量
pthread_cond_t cond;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int flag = 0;
void *task_func1(void *arg){
    while(1){
        pthread_mutex_lock(&lock);
        while(flag != 0)  //因为man手册里写的pthread_cond_signal至少唤醒一个线程,这个while1防止误唤醒
            pthread_cond_wait(&cond, &lock);
        printf("我是生产者线程 [%lu],生产了一辆汽车..\n", pthread_self());
        flag = 1;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
}

void *task_func2(void *arg){
    while(1){
        pthread_mutex_lock(&lock);
        while(flag == 0) //因为man手册里写的pthread_cond_signal至少唤醒一个线程,这个while1防止误唤醒
            pthread_cond_wait(&cond, &lock);//获取条件变量
        flag = 0;
        printf("我是消费者线程 [%lu],买了一辆车..\n", pthread_self());
        sleep(1);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;
    int ret = 0;
    //初始化条件变量
    pthread_cond_init(&cond, NULL);
    //创建线程 1个生产者  1个消费者
    if(-1 == (ret = pthread_create(&tid1, NULL, task_func1, NULL))){
        printf("errcode = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(-1);
    }
    if(-1 == (ret = pthread_create(&tid2, NULL, task_func2, NULL))){
        printf("errcode = [%d] errstr = [%s]\n", ret, strerror(ret));
        exit(-1);
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    //销毁条件变量
    pthread_cond_destroy(&cond);
    //销毁互斥锁
    pthread_mutex_destroy(&lock);

    return 0;
}

----------------------------------进程间通信-------------------------------------

一、进程间通信种类

1.传统进程间通信

        无名管道

        有名管道

        信号

2.IPC进程间通信

        共享内存

        消息队列

        信号灯集

3.BSD(伯克利分校)

        socket进程间通信

二、传统进程间通信

通信方式

单工 : A -------------->B

半双工 : 同一时刻 A----->B B------>A

全双工 : 同一时刻 AB

1.无名管道

 1.1 无名管道原理

无名管道是内核空间实现的机制,只能用于亲缘进程间通信,无名管道的大小是64K

1.2 无名管道API

#include <unistd.h>

int pipe(int pipefd[2]);

功能: 创建一个管道,是一个单向的数据通道,可用于进程间通信

            数组pipefd返回两个指向管道的文件描述符,

            pipefd[0]指向读端 pipefd[1] 指向管道的写端

            写入管道的数据被内核缓冲(64K),直到从管道中读走

参数: 操作管道的文件描述符数组

            pipefd[0]指向读端

            pipefd[1] 指向管道的写端

返回值: 成功 0 失败 -1 重置错误码

1.3 无名管道使用实例

#include <head.h>
int main(int argc, char const *argv[])
{
    int pipefd[2];//由于是亲缘进程间,在fork之前创建子进程会将父进程的文件描述符拷贝走
    pid_t pid;
    char buf[32]={0};
    if(-1==pipe(pipefd)){
        ERRLOG("pipefd error");
    }
    if(-1==(pid=fork())){
        ERRLOG("fork error");
    }else if(pid>0){//父,读
        close(pipefd[1]);
        while(1){
            memset(buf,0,32);
            read(pipefd[0],buf,sizeof(buf));
            if(strncmp(buf,"quit",4)==0){
                break;
            }
            printf("buf:[%s]\n",buf); 
        }
        close(pipefd[0]);
    }else if(pid==0){//子,写
        close(pipefd[0]);
        while(1){
            fgets(buf,sizeof(buf),stdin);
            buf[strlen(buf)-1]='\0';
            write(pipefd[1],buf,sizeof(buf));
            if(strncmp(buf,"quit",4)==0){
                break;
            }
        }
        close(pipefd[1]);
    }
    return 0;
}

1.4 无名管道的特点 

        1.只能用于亲缘间进程的通信

        2.无名管道数据半双工的通信的方式

        3.无名管道的大小是64K

        4.无名管道不能够使用lseek函数(调用会出错 返回 -1)

        5.读写的特点

                读端存在,向管道中写数据:有多少写多少,直到写满为止(64k)写阻塞,

                直到管道中腾出新的位置,以一页(4k)为单位,写操作解除阻塞

                读端不存,向管道中写数据,管道破裂(SIGPIPE)

                写端存在,从管道中读数据:有多少读多少,没有数据的时候阻塞等待

                写端不存在,从管道中读数据:有多少读多少,没有数据的时候立即返回(非阻塞)

2.有名管道

2.1 有名管道的原理

        可以用于非亲缘进程间通信,也可以用于亲缘进程间通信

        有名管道会创建一个管道文件,只需要打开这个文件,进行读写操作即可

        管道文件本质是在内存上的,在硬盘上的只是一个标识。

        有名管道的大小也是64K,也不能使用lseek函数

2.2 有名管道API

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

功能:创建管道文件

参数: pathname:管道路径和名字 mode:管道文件的权限

返回值: 成功 0 失败 -1 重置错误码

2.3 有名管道使用实例

fifo.c

#include <head.h>
int main(int argc, char const *argv[])
{
    if(-1==mkfifo("./t.txt",0666)){
        ERRLOG("mkfifo error");
    }
    return 0;
}

read.c

#include <head.h>
int main(int argc, char const *argv[])
{
    char buf[32]={0};
    int fd;
    if(-1==(fd=open("t.txt",O_RDONLY))){
        ERRLOG("open error");
    }
    printf("管道文件打开成功..\n");//打印验证阻塞位置
    while(1){
        if(strncmp(buf,"quit",4)==0){
            exit(0);
        }
        memset(buf,0,sizeof(buf));
        read(fd,buf,sizeof(buf));
        printf("buf:[%s]\n",buf);
    }
    return 0;
}

write.c 

#include <head.h>
int main(int argc, char const *argv[])
{
    char buf[32]={0};
    int fd;
    if(-1==(fd=open("t.txt",O_RDWR|O_TRUNC|O_CREAT))){
        ERRLOG("open error");
    }
    printf("管道文件打开成功..\n");//打印验证阻塞位置
    while(1){
        if(strncmp(buf,"quit",4)==0){
            exit(0);
        }
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1]='\0';
        write(fd,buf,sizeof(buf));
    }
    return 0;
}

2.4 有名管道的特点

        1.可以用于任意进程间的通信,不仅限亲缘进程

        2.有名管道数据是半双工的通信方式

        3.有名管道的大小是64K

        4.有名管道不能够使用lseek函数(调用会失败 返回 -1)

        5.读写的特点

                如果读端存在写管道:有多少写多少,直到写满为止(64k)写阻塞

                如果读端不存写管道

                        1.读端没有打开,写端在open的位置阻塞

                        2.读端打开后关闭,管道破裂(SIGPIPE)

                如果写端存在读管道:有多少读多少,没有数据的时候阻塞等待

                如果写端不存在读管道

                        1.写端没有打开,读端在open的位置阻塞

                        2.写端打开后关闭,有多少读多少,没有数据的时候立即返回

3.信号

3.1 信号的概念

        信号是中断的一种软件模拟,中断是基于硬件的概念,信号是基于linux内核实现的。

        用户可以给进程发信号,进程可以给进程发信号,linux内核也可以给进程发信号。

        信号的处理方式有三种:默认、忽略、捕捉

3.2 系统中信号的查看

3.3 常用的信号

SIGCHLD 当子进程退出的时候,会给父进程发送该信号。

上述信号中只有 SIGKILL 和 SIGSTOP 不能被忽略和捕捉

3.4 发送信号的命令

        kill -信号的编号 PID

3.5 信号处理函数API

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

功能:在进程中注册一个信号和信号处理函数的关系

参数: signum:信号的编号

            handler:指向信号处理函数的指针

                忽略:SIG_IGN

                默认:SIG_DFL

                捕捉: void sig_func(int signum){ //捕捉信号后的自定义的行为 }

返回值: 成功:handler

               失败:SIG_ERR 重置错误码

注意:signal函数只是注册了信号和信号处理函数的对应关系

           进程并不会在signal函数处等待信号产生

3.6 信号处理函数使用实例

#include <head.h>
void handle(int signo)
{
    printf("我捕捉到了一个ctrl+c信号\n");
}
int main(int argc, const char* argv[])
{
    // 1.尝试忽略SIGINT信号
    // if (SIG_ERR == signal(SIGINT, SIG_IGN))
    //     PRINT_ERR("signal error");

    // 2.尝试默认SIGINT信号
    // if (SIG_ERR == signal(SIGINT, SIG_DFL))
    //     PRINT_ERR("signal error");

    // 3.尝试捕捉SIGINT信号
    if (SIG_ERR == signal(SIGINT, handle))
        PRINT_ERR("signal error");
    while (1);

    return 0;
}

用户自定义的信号 SIGUSR1(10) 和 SIGUSR2(12号)

可以使用 kill -10或-12 PID 命令发信号

练习1:在进程中使用捕获的方式处理2 10 12 号信号

        收到 2信号时,打印 "aaa"

        收到10信号时,打印 "bbb"

        收到12信号时,打印 "ccc"

#include <head.h>
void sig2(int signum){
    printf("aaaa\n");
}
void sig10(int signum){
    printf("bbb\n");
}
void sig12(int signum){
    printf("ccc\n");
}
int main(int argc, char const *argv[])
{
    if(SIG_ERR==signal(SIGINT,sig2)){
        perror("signal error");
        exit(-1);
    }
    if(SIG_ERR==signal(SIGUSR1,sig10)){
        perror("signal error");
        exit(-1);
    }
    if(SIG_ERR==signal(SIGUSR2,sig12)){
        perror("signal error");
        exit(-1);
    }
    while(1){
        sleep(2);
        printf("hello\n");
    }
    return 0;
}

练习2:借助信号处理函数捕捉管道破裂的信号

#include <head.h>
void func(){
    printf("管道破裂\n");
}
int main(int argc, char const *argv[])
{
    if(SIG_ERR==signal(SIGPIPE,func)){
        ERRLOG("signal error");
    }
    int pipefd[2];
    char buf[32]={0};
    if(-1==pipe(pipefd)){
        ERRLOG("pipe error");
    }
    fgets(buf,sizeof(buf),stdin);
    buf[strlen(buf)-1]='\0';
    close(pipefd[0]);
    write(pipefd[1],buf,sizeof(buf));
    while(1);
    close(pipefd[1]);
    return 0;
}

练习3:借助信号处理函数用非阻塞的方式给子进程回收资源

#include <head.h>
void func(int signum){
    if(SIGCHLD==signum){
        waitpid(-1,NULL,WNOHANG);
    printf("子进程资源已回收\n");
    }
    
}
int main(int argc, char const *argv[])
{
    pid_t pid;
    signal(SIGCHLD,func);//先注册,等到父进程里注册就晚了
    if(-1==(pid=fork())){
        ERRLOG("fork error");
    }else if(pid==0){
        printf("子进程\n");
        exit(0);
    }else if(pid>0){
        sleep(1);
        printf("父进程\n");
    }
    return 0;
}

3.7 发信号的函数

#include <signal.h>

int raise(int sig);
功能:给自己发信号
参数:信号的编号
返回值:成功 0  失败 非0

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:给指定pid的进程发信号
参数:
    pid 进程号
        >0 给指定的pid发信号,常用的用法
        0  给同组的进程发信号
        -1 给所有有权限操作的进程发信号,init除外
        <-1 如-100,给进程组id为100的所有进程发信号
    sig 信号的编号
返回值:
    成功 0
    失败 -1 重置错误码 

        3.9.1 raise函数使用实例

#include <head.h>
void func(int signum){
    if(SIGCHLD==signum){
        waitpid(-1,NULL,WNOHANG);
        printf("子进程资源已回收...\n");
        raise(SIGKILL);//父进的任务完成了 可以自杀了
    }
    
}
int main(int argc, char const *argv[])
{
    pid_t pid;
    signal(SIGCHLD,func);//先注册,等到父进程里注册就晚了
    if(-1==(pid=fork())){
        ERRLOG("fork error");
    }else if(pid==0){
        sleep(5);
        printf("子进程\n");
        exit(0);
    }else if(pid>0){
        while(1);
    }
    return 0;
}

        3.9.2 kill函数使用实例

#include <head.h>
int main(int argc, char const *argv[])
{
    //./my_kill  -编号  pid
    //argv本质是指针数组,里面存放的都是指针,atoi要的也是指针,直接让指针向后移动一位,跳过'-'读取后面的内容
    if(-1==kill(atoi(argv[2]),atoi(argv[1]+1))){
        ERRLOG("kill error");
    }
    return 0;
}

3.10 alarm函数

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

功能:在设置的seconds秒后给调用进程产生一个 SIGALRM信号

           如果seconds设置成0 则不产生信号

参数:倒计时的秒数

返回值: 如果前面设置了告警,返回上一次告警剩余的时间

                如果前面没有设置告警,返回0

 alarm函数使用实例1:

#include <head.h>

void sig_func(int signum){
    if(SIGALRM==signum)
        printf("倒计时结束..\n");
}
int main(int argc,const char * argv[])
{
    signal(SIGALRM, sig_func);
    int ret = alarm(5);
    printf("ret = %d\n", ret);//0
    sleep(3);
    ret = alarm(5);
    printf("ret = %d\n", ret);//2
    while(1){
        printf("hello\n");
        sleep(1);
    }
    return 0;
}

使用alarm模拟斗地主自动出牌机制

#include <head.h>
void func(int signum){
    //if(SIGALRM==signum)当只有一个信号的时候写不写都行,signum就是signal函数中信号的编号,当有多个信号使用同一个信号处理函数的时候,为了区分,需要加判断
    printf("自动出牌\n");
    alarm(3);
}
int main(int argc, char const *argv[])
{
    signal(SIGALRM,func);
    alarm(3);
    while (1)
    {
        getchar();
        printf("手动出牌\n");
        alarm(3);
    }
    
    return 0;
}

三、IPC进程间通信

1.IPC进程间通信相关的命令

查看IPC进程间通信实例的命令--ipcs

ipcs -q 查看消息队列

ipcs -m 查看共享内存

ipcs -s 查看信号灯集

删除IPC进程间通信实例的命令

ipcrm -q msqid 删除消息队列

ipcrm -m shmid 删除共享内存

ipcrm -s semid 删除信号灯集

2.IPC进程间通信获取键值的函数

在使用IPC进程间通信前需要获取key值,只要两个进程的key相同,

就可以找到同一个IPC进行通信,但是key不是唯一的。

#include <sys/types.h>

#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

功能: 获取IPC通信的键值

参数: pathname:路径和文件名(必须是已存在的)

            proj_id:填充的是任意一个字符

返回值: 成功 返回key_t类型的键值

                失败 -1 重置错误码

ftok函数使用实例

#include <head.h>

int main(int argc,const char * argv[])
{
    key_t my_key = 0;
    if(-1 == (my_key = ftok("/home", 'H'))){
        ERRLOG("ftok error");
    }
    printf("my_key = %d\n", my_key);
    printf("my_key = %#x\n", my_key);

    struct stat st;
    stat("/home", &st);
    printf("proj_id = %#x\n", 'H');
    printf("dev = %#lx\n", st.st_dev);
    printf("ino = %#lx\n", st.st_ino);

    return 0;
}

3.消息队列

3.1 消息队列的原理

        消息队列也是基于内核实现的,A进程将消息写入消息队列

        消息队列中的消息有类型和正文。

        B进程可以根据消息的类型从消息队列中将对应类型的消息取走。

        消息队列的大小,默认是 16K,

        如果消息队列满了,A进程还想向消息队列中写入消息,此时A进程将会阻塞。

3.2 消息队列相关API

msgget msgsnd msgrcv msgctl

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msgget(key_t key, int msgflg);

功能: 创建一个消息队列

参数: key:键值

                        key 通过ftok获取的

                        IPC_PRIVATE 表示只有亲缘进程间能只用

            msgflg:消息队列的标志位

                        IPC_CREAT|0666 或者 IPC_CREAT|IPC_EXCL|0666

返回值: 成功 消息队列的id

                失败 -1 重置错误码

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

功能:向消息队列中写入一条消息

参数: msqid:消息队列的id

            msgp: 要写入的数据的首地址

                struct msgbuf {

                              long mtype; /* 消息的类型 必须大于 0 */

                              char mtext[1]; /* 消息正文 可以自定义 */

                };

            msgsz:消息正文的大小

            msgflg:标志位

                                0 阻塞发送

                                IPC_NOWAIT 非阻塞发送

返回值: 成功  0

                失败  -1 重置错误码

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

功能:在消息队列中读取一条消息

参数: msqid:消息队列的id

            msgp: 用来保存接收的数据的缓冲区的首地址

                        struct msgbuf {

                                long mtype; /* 消息的类型 必须大于 0 */

                                char mtext[1]; /* 消息正文 可以自定义 */ };

             msgsz:消息正文的大小

             msgtyp:要接受的消息的类型

                                0 :接收消息队列中第一条效率

                                >0 : 接收指定类型的第一条消息

                                <0 :一般不使用,

                                      表示接收消息队列中第一条类型最小的小于msgtyp的绝对值的消息                                        3-2-5-500-200-8

                                       读取时,类型传 -200 读取的顺序 2-3-5

              msgflg:标志位 0 阻塞接收    IPC_NOWAIT 非阻塞接收

返回值: 成功 实际读到的正文的字节数

                失败 -1 重置错误码

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

功能:控制消息队列

参数: msqid:消息队列id

            cmd:指令

                        IPC_STAT:获取消息队列的属性

                        IPC_SET:设置消息队列的属性

                        IPC_RMID:立即删除消息队列

                                只有消息队列的创建者和所有者以及root用户可以删除消息队列

              buf:

                        当cmd选择PC_RMID时,此参数被忽略,传NULL即可

                        

                        struct msqid_ds {
                                struct ipc_perm             msg_perm; //权限结构体
                                __time_t                        msg_stime;  //最后一次发送消息的时间
                                __time_t                        msg_rtime;  //最后一次接收消息的时间
                                __syscall_ulong_t         __msg_cbytes;//当前消息队列中字节数
                                msgqnum_t                   msg_qnum;  //当前消息队列中消息的个数
                                msglen_t                       msg_qbytes; //消息队列中能够容纳的字节数                                                                                                  (16384)
                                __pid_t                           msg_lspid;  //最后一次发送消息的进程号
                                __pid_t                           msg_lrpid;  //最后一次接收消息的进程号
                         };
                         struct ipc_perm{
                                __key_t         __key;   //键值
                                __uid_t         uid;   //消息队列所属的uid
                                __gid_t         gid;   //消息队列所属的gid
                                __uid_t         cuid;   //创建消息队列的uid
                                __gid_t         cgid;   //创建消息队列的gid
                                unsigned short int mode; //消息队列的读写权限
                       };

返回值:成功  0,

               失败   -1,置位错误码

3.3 消息队列使用实例

send.c

#include <head.h>
typedef struct {
    long mtype;
    char mtext[32];
}msg_t;
int main(int argc, char const *argv[])
{
    key_t key;
    int msqid;
    msg_t msg={0};
    int ret;
    //创建键值
    if(-1==(key=ftok("/home",'S'))){//大小写都行
        ERRLOG("ftok error");
    }
    //创建消息队列
    if(-1==(msqid=msgget(key,IPC_CREAT|0666))){
        ERRLOG("msgget error");
    }
    while(1){
        //从终端获取消息
    retry:
        printf("input (type text) > ");
        ret=scanf("%ld %s",&msg.mtype,msg.mtext);
        //判断输入是否合法
        if(ret!=2){
            while (getchar() != '\n');//清理垃圾字符,不是回车就一直在循环,是回车才goto重新输入
            goto retry;
        }
            
        //fgets(msg.mtext,sizeof(msg.mtext),stdin);
        //msg.mtext[strlen(msg.mtext)-1]='\0';
        //发送信息
        if(-1==msgsnd(msqid,&msg,sizeof(msg)-sizeof(msg.mtype),0)){
            ERRLOG("msgsnd error");
        }
        //退出条件
        if(strcmp(msg.mtext,"quit")==0){
            break;
        }
        //清空消息
        memset(&msg,0,sizeof(msg));
    }
    //删除消息队列
    msgctl(msqid,IPC_RMID,NULL);
    return 0;
}

write.c

#include <head.h>
typedef struct msg{
    long mtype;
    char mtext[32];
}msg_t;
int main(int argc, char const *argv[])
{
    key_t key;
    int msqid;
    msg_t msg={0};
    int ret;
    long msgtype;
    //创建键值
    if(-1==(key=ftok("/home",'S'))){
        ERRLOG("ftok error");
    }
    //创建消息队列
    if(-1==(msqid=msgget(key,IPC_CREAT|0666))){
        ERRLOG("msgget error");
    }
    while(1){
    retry:
        printf("input (type) > ");
        ret=scanf("%ld",&msgtype);
        /*if(ret!=1){
            while(getchar()!='\n');
            goto retry;
        }*/
        if(-1==msgrcv(msqid,&msg,sizeof(msg)-sizeof(msg.mtype),msgtype,0)){
            ERRLOG("msgrcv error");
        }
        if(strncmp(msg.mtext,"quit",4)==0){
            break;
        }
        printf("msg:type:[%ld]  text:[%s]\n",msg.mtype,msg.mtext);
        memset(&msg,0,sizeof(msg));
    }
    //删除消息队列
    msgctl(msqid,IPC_RMID,NULL);
    return 0;
}

3.4使用msgctl获取消息队列的属性

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

typedef struct msgbuf {
    long mtype;
    int id;
    char sex;
    char name[32];
    int score;
}msg_t;

int main(){
    key_t key = 0;
    int msqid = 0;
    if(-1 == (key = ftok("/home", 'a'))){
        perror("ftok error");
        exit(-1);
    }
    printf("key = %#x\n", key);
    
    if(-1 == (msqid = msgget(key, IPC_CREAT | 0666))){
        perror("msgget error");
        exit(-1);
    }

    msg_t msg1 = {
        .mtype = 10,
        .id = 1001,
        .sex = 'm',
        .name = "zhangsan",
        .score = 98
    };
    msgsnd(msqid, &msg1, sizeof(msg1)-sizeof(long), 0);
    msg_t msg2 = {
        .mtype = 20,
        .id = 1002,
        .sex = 'w',
        .name = "lisi",
        .score = 99
    };
    msgsnd(msqid, &msg2, sizeof(msg2)-sizeof(long), 0);

    //获取消息队列属性
    struct msqid_ds mds;
    msgctl(msqid, IPC_STAT, &mds);
    printf("key = %#x\n", mds.msg_perm.__key);
    printf("msg num = %ld\n", mds.msg_qnum);
    printf("msg bytes = %ld\n", mds.__msg_cbytes);
    printf("max bytes= %ld\n", mds.msg_qbytes);
    printf("mode = %#o\n", mds.msg_perm.mode);
    printf("uid = %d\n", mds.msg_perm.uid);
    printf("gid = %d\n", mds.msg_perm.gid);
    
    puts("----------------------------");
    mds.msg_qbytes = 8 * 1024;//改小可以,改大需要特权 sudo  man手册有说明
    msgctl(msqid, IPC_SET, &mds);
    puts("----------------------------");

    //检验大小是否改变
    msgctl(msqid, IPC_STAT, &mds);
    printf("key = %#x\n", mds.msg_perm.__key);
    printf("msg num = %ld\n", mds.msg_qnum);
    printf("msg bytes = %ld\n", mds.__msg_cbytes);
    printf("max bytes= %ld\n", mds.msg_qbytes);
    printf("mode = %#o\n", mds.msg_perm.mode);
    printf("uid = %d\n", mds.msg_perm.uid);
    printf("gid = %d\n", mds.msg_perm.gid);

    //删除消息队列
    msgctl(msqid,IPC_RMID,NULL);

    return 0;
}

4.共享内存

 4.1 共享内存原理

        在内核中创建共享内存,让进程A和进程B都能够访问到,通过这段内存进行数据的传递。

        共享内存是所有进程间通信方式中效率最高的(不需要来回进行数据的拷贝)

4.2 共享内存相关API

#include <sys/ipc.h>

#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

功能: 创建共享内存

参数: key:键值

                key 通过ftok获取

                IPC_PRIVATE:只能用于亲缘进程间的通信

             size:共享内存的大小 PAGE_SIZE(4k)的整数倍

             shmflg:共享的标志位 IPC_CREAT|0666 或 IPC_CREAT|IPC_EXCL|0666

返回值: 成功 共享内存编号

                失败 -1 重置错误码

void *shmat(int shmid, const void *shmaddr, int shmflg);

功能: 映射共享内存到当前的进程空间

参数: shmid:共享内存编号

            shmaddr:NULL,让系统自动分配

            shmflg:共享内存操作方式

                        0   读写

                        SHM_RDONLY   只读

返回值: 成功 执行共享内存的地址

               失败 (void *)-1 重置错误码

int shmdt(const void *shmaddr);

功能: 取消地址映射

参数: shmaddr:指向共享内存的指针

返回值: 成功 0

               失败 -1 重置错误码

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能: 共享内存控制的函数

参数: shmid:共享内存编号

            cmd:操作的命令码

                        IPC_STAT:获取

                        IPC_SET:设置

                        IPC_RMID:删除共享内存

                                标记要销毁的段。实际上,只有在最后一个进程将其分离之后

                                (也就是说,关联结构shmid_ds的shm_nattch成员为零时),

                                段才会被销毁。

                                调用者必须是段的所有者或创建者,或具有特权。buf参数被忽略。              buf:共享内存属性结构体指针

                        struct shmid_ds{
                                struct ipc_perm shm_perm; //权限结构体
                                size_t shm_segsz;    //共享内存的大小,单位是字节
                                __time_t shm_atime;    //最后一次调用shmat的时间
                                __time_t shm_dtime;    //最后一次调用shmdt的时间
                                __time_t shm_ctime;    //最后一次调用shmctl改变属性的时间
                                __pid_t shm_cpid;    //创建共享内存的PID
                                __pid_t shm_lpid;    //最后一次操作共享内存的PID
                                shmatt_t shm_nattch;   //当前多少个进程关联共享内存
                            };
                            struct ipc_perm{
                                __key_t __key;   //键值
                                __uid_t uid;   //消息队列所属的uid
                                __gid_t gid;   //消息队列所属的gid
                                __uid_t cuid;   //创建消息队列的uid
                                __gid_t cgid;   //创建消息队列的gid
                                unsigned short int mode; //消息队列的读写权限
                            };

返回值: 成功 0

               失败 -1 重置错误码

4.3 共享内存使用实例

write.c

#include <head.h>
#define SIZE 4*1024
int main(int argc, char const *argv[])
{
    key_t key;
    //获取键值
    if(-1==(key=ftok("/home",'a'))){
        ERRLOG("ftok error");
    }
    //创建共享内存
    int shmid;
    if(-1==(shmid=shmget(key,SIZE,IPC_CREAT|0666))){//第二个参数,防止魔鬼数字,用宏定义定义4*1024
        ERRLOG("shmget error");
    }
    //映射
    char *buf;
    //映射出的地址需要接,以后读写俩端将通过这个地址访问物理内存
    if((void *)-1==(buf=shmat(shmid,NULL,0))){
        ERRLOG("shmat error");
    }
    //写
    while(1){
        fgets(buf,SIZE,stdin);
        buf[strlen(buf)-1]='\0';
        if(strncmp("quit",buf,4)==0){
            break;
        }
    }
    if(-1==shmdt(buf)){
        ERRLOG("shmdt error");
    }
    //一端删除就行,这边删除了,read端会报错
    // //删除
    // if(-1==shmctl(shmid,IPC_RMID,NULL)){
    //     ERRLOG("shmctl error");
    // }
    return 0;
}

read.c

#include <head.h>
#define SIZE 4*1024
int main(int argc, char const *argv[])
{
    key_t key;
    //获取键值
    if(-1==(key=ftok("/home",'a'))){
        ERRLOG("ftok error");
    }
    //创建共享内存
    int shmid;
    if(-1==(shmid=shmget(key,SIZE,IPC_CREAT|0666))){
        ERRLOG("shmget error");
    }
    //映射
    char *buf;
    if((void *)-1==(buf=shmat(shmid,NULL,0))){
        ERRLOG("shmat error");
    }
    //读
    while(1){
        getchar();
        //fputs(buf,stdout);也可以
        printf("buf=[%s]\n",buf);
        if(strncmp("quit",buf,4)==0){
            break;
        }
    }
    //取消映射
    if(-1==shmdt(buf)){
        ERRLOG("shmdt error");
    }
    //删除
    if(-1==shmctl(shmid,IPC_RMID,NULL)){
        ERRLOG("shmctl error");
    }
    return 0;
}

4.4 共享内存属性设置

struct shmid_ds{
                                struct ipc_perm shm_perm; //权限结构体
                                size_t shm_segsz;    //共享内存的大小,单位是字节
                                __time_t shm_atime;    //最后一次调用shmat的时间
                                __time_t shm_dtime;    //最后一次调用shmdt的时间
                                __time_t shm_ctime;    //最后一次调用shmctl改变属性的时间
                                __pid_t shm_cpid;    //创建共享内存的PID
                                __pid_t shm_lpid;    //最后一次操作共享内存的PID
                                shmatt_t shm_nattch;   //当前多少个进程关联共享内存
                            };
                            struct ipc_perm{
                                __key_t __key;   //键值
                                __uid_t uid;   //消息队列所属的uid
                                __gid_t gid;   //消息队列所属的gid
                                __uid_t cuid;   //创建消息队列的uid
                                __gid_t cgid;   //创建消息队列的gid
                                unsigned short int mode; //消息队列的读写权限
                            };

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>

//一个内存页的大小
#define PIGE_SIZE 4*1024

int main(int argc, const char *argv[])
{
    key_t my_key = 0;
    int shmid = 0;
    char *p_addr = NULL;
    //获取键值
    if(-1 == (my_key = ftok("/home", 'H'))){
        perror("ftok error");
        exit(-1);
    }

    //创建共享内存
    if(-1 == (shmid = shmget(my_key, PIGE_SIZE, IPC_CREAT|0664))){
        perror("shmget error");
        exit(-1);
    }

    //映射共享内存
    if((void *)-1 == (p_addr = shmat(shmid, NULL, 0))){
        perror("shmat error");
        exit(-1);
    }

    //向共享内存写数据
    fgets(p_addr, 1024, stdin);
    p_addr[strlen(p_addr) - 1] = '\0';

    //获取共享内存属性
    struct shmid_ds shd;
    shmctl(shmid,IPC_STAT,&shd);
    printf("size = %ld\n",shd.shm_segsz);
    printf("attach=%ld\n",shd.shm_nattch);
    printf("create pid = %d,pid = %d\n",shd.shm_cpid,getpid());
    printf("last pid = %d\n",shd.shm_lpid);
    printf("tim = %ld\n",shd.shm_ctime);
    struct tm *tm;
    tm = localtime(&shd.shm_ctime);
    printf("%d-%02d-%02d %02d:%02d:%02d\n",
            tm->tm_year+1900,tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);

    //取消映射
    if(-1 == shmdt(p_addr)){
        perror("shmdt error");
        exit(-1);
    }

    //删除共享内存 只要有进程还映射 此操作返回-1
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}

5.信号灯集

 5.1 信号灯集原理

信号灯集:又叫做信号量,他是实现进程间同步的机制

在一个信号灯集中可以有很多个信号灯,这些信号灯之间工作相互互不干扰。

信号灯集中的信号灯的编号是从0开始的。每个信号灯都相互独立,比如将0号灯设置为1代表有资源,将1号灯设置为0表示没有资源。比如A进程对0号灯进行P操作(申请资源-1)A进程能够获取资源A进行向下执行。B进程对1号灯进行P操作(申请资源-1),申请不到资源B进程阻塞休眠。如果A进行执行完之后释放1号灯的资源,B进程就可以执行。依次执行上述的流程A和B进程就可以实现同步工作。

一般使用时使用的都是二值信号灯

5.2 信号灯集相关API

semget semctl semop

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

        功能:创建一个信号灯集

        参数: key:键值

                                key 通过ftok获取的

                                IPC_PRIVATE 表示只有亲缘进程间能只用

                    nsems:信号灯集合中信号灯的个数

                    semflag:创建的标志位

                                IPC_CREAT|0666 或者 IPC_CREAT|IPC_EXCL|0666

        返回值: 成功 semid

                       失败 -1 重置错误码

int semctl(int semid, int semnum, int cmd, ...);

        功能:信号灯集的控制函数

        参数: semid信号灯集的ID

                    senum:信号灯的编号 从0开始                   

                    cmd:操作的命令码

                                SETVAL:设置信号灯的值 --->第四个

就参数val选项

                                GETVAL:获取信号灯的值 --->不需要第四个参数

                                IPC_STAT:获取信号灯集的属性--->第四个参数buf选项

                                IPC_SET :设置信号灯集的属性--->第四个参数buf选项

                                IPC_RMID:第二参数被忽略,第4个参数不用填写

                     @...:可变参

                      union semun{
                                int val; /* Value for SETVAL */

                                struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */

                      };

        返回值: 成功

                                GETVAL:成功返回信号灯的值

                                其余的命令码成功返回0

                        失败 -1 重置错误码

                      

int semop(int semid, struct sembuf *sops, size_t nsops);

        功能: 信号灯集中信号灯的操作函数

        参数: semid:信号灯集的编号      

                    sops:操作方式

                                struct sembuf{
                                        unsigned short sem_num; //信号灯的编号         

                                        short sem_op; //操作方式(PV)-1:P操作,申请资源

                                                                                             1:V操作,释放资源

                                        short sem_flg; //操作的标志位 0:阻塞

                                                                                         IPC_NOWAIT:非阻塞方式操作 }

                    nsops:本次操作信号灯的个数

        返回值: 成功 0

                       失败 -1 重置错误码

struct semid_ds {
    struct ipc_perm sem_perm;        //权限结构体
    __time_t sem_otime;             //最后一次调用semop的时间
    __time_t sem_ctime;             //最后一次调用semctl的时间
    __syscall_ulong_t sem_nsems;     //信号灯集中信号灯的个数
};
 
struct ipc_perm {
    __key_t __key;        //ftok获取的key
    __uid_t uid;        //用户的ID
    __gid_t gid;        //组ID
    __uid_t cuid;        //创建信号灯集的用户的ID
    __gid_t cgid;        //创建信号灯集的组的ID
    unsigned short int mode; //信号灯集的权限
};

5.3信号灯集函数封装

sem.h

#ifndef SEM_H
#define SEM_H

int mysem_init(int num);
void P(int semid,int slamp);
void V(int semid,int slamp);
void sem_del(int semid);

#endif /*SEM_H*/

 sem.c

#include "sem.h"
#include <head.h>
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
};
int mysem_init(int num){
    //获取键值
    key_t key=0;
    if(-1==(key=ftok("/home",'q'))){
        ERRLOG("ftok error");
    }
    //创建信号灯集
    int semid;
    if(-1==(semid=semget(key,num,IPC_CREAT|IPC_EXCL|0666))){
        if(errno==EEXIST){
            semid=semget(key,num,IPC_CREAT|0666);
            return semid;
        }
        else{
            ERRLOG("semget error");
        }
    }else{
        //设置信号灯的值
        union semun  mysem={.val=1};
        semctl(semid,0,SETVAL,mysem);
        mysem.val=0;
        for(int i=1;i<num;i++){
            semctl(semid,i,SETVAL,mysem);     
        }
        // for(int i=0;i<num;i++){
        //     mysem.val=!i;
        //     semctl(semid,i,SETVAL,mysem.val);
        // }
        return semid;
    }
    //return semid;
}

void P(int semid,int slamp){
    struct sembuf buf={.sem_num=slamp,
                        .sem_op=-1,
                        .sem_flg=0};
    semop(semid,&buf,1);
}
void V(int semid,int slamp){
    struct sembuf buf={.sem_num=slamp,
                        .sem_op=1,
                        .sem_flg=0};
    if(-1==semop(semid,&buf,1)){
        ERRLOG("semop error")
    }
}
void sem_del(int semid){
    if(-1==semctl(semid,0,IPC_RMID)){
        ERRLOG("semctl error");
    }
}

5.3 信号灯集使用实例

write.c

#include <head.h>
#include "sem.h"
#define SIZE 4*1024
int main(int argc, char const *argv[])
{
    key_t key;
    //获取键值
    if(-1==(key=ftok("/home",'a'))){
        ERRLOG("ftok error");
    }
    //创建共享内存
    int shmid;
    if(-1==(shmid=shmget(key,SIZE,IPC_CREAT|0666))){//第二个参数,防止魔鬼数字,用宏定义定义4*1024
        ERRLOG("shmget error");
    }
    //映射
    char *buf;
    //映射出的地址需要接,以后读写俩端将通过这个地址访问物理内存
    if((void *)-1==(buf=shmat(shmid,NULL,0))){
        ERRLOG("shmat error");
    }
    //写
    int semid=0;
    semid=mysem_init(2);
    while(1){
        fgets(buf,SIZE,stdin);
        buf[strlen(buf)-1]='\0';
        P(semid,0);
        if(strncmp("quit",buf,4)==0){
            break;
        }
        V(semid,1);
    }
    if(-1==shmdt(buf)){
        ERRLOG("shmdt error");
    }
    sem_del(semid);
    //一端删除就行,这边删除了,read端会报错
    // //删除
    // if(-1==shmctl(shmid,IPC_RMID,NULL)){
    //     ERRLOG("shmctl error");
    // }
    return 0;
}

read.c

#include <head.h>
#include "sem.h"
#define SIZE 4*1024
int main(int argc, char const *argv[])
{
    key_t key;
    //获取键值
    if(-1==(key=ftok("/home",'a'))){
        ERRLOG("ftok error");
    }
    //创建共享内存
    int shmid;
    if(-1==(shmid=shmget(key,SIZE,IPC_CREAT|0666))){
        ERRLOG("shmget error");
    }
    //映射
    char *buf;
    if((void *)-1==(buf=shmat(shmid,NULL,0))){
        ERRLOG("shmat error");
    }
    //读
    int semid=0;
    semid=mysem_init(2);
    while(1){
        P(semid,1);
        //fputs(buf,stdout);也可以
        printf("buf=[%s]\n",buf);
        if(strncmp("quit",buf,4)==0){
            break;
        }
        V(semid,0);
    }
    //取消映射
    if(-1==shmdt(buf)){
        ERRLOG("shmdt error");
    }
    //删除
    if(-1==shmctl(shmid,IPC_RMID,NULL)){
        ERRLOG("shmctl error");
    }
    return 0;
}

5.4 信号灯集属性获

 和共享内存相同

  面试题 

IO部分

1.标准IO,文件IO的区别

        文件IO是系统调用,标准IO是库函数,是系统调用+缓冲区。文件IO效率低。

 2.缓冲区的种类和大小

             行缓冲,1K(stdin->_IO_buf_end - stdin->_IO_buf_base)

             全缓冲,4K(fp->_IO_buf_end-fp->_IO_buf_base)

                                FILE *fp=fopen("t.c","w");

                                fputs("hello",fp);

                                fp->_IO_buf_end-fp->_IO_buf_base

                                fp需要定义,且需要使用一下

             无缓冲 0

进程部分

1.进程是怎么得到的

fork,子进程拷贝父进程,写时拷贝(子进程拷贝父进程时,如果变量没有改变,那子进程中变量的地址也和父进程一样,变量需要改变的时候才会重新分配新的地址)

2.进程的优先级

        nice值并不是进程的优先级,而是能够影响进程优先级的数据

        nice:[-20,19],nice值越小,优先级越高

        sudo nice -n 值 ./a.out //将a.out进程按照指定优先级的值来执行

        sudo renice 新的值 pid //可以修改进程的优先级

        进程的优先级分为

                静态优先级[100,139]        普通进程

                动态优先级[0,139]            普通进程

                实时优先级[0,99]              实时进程

3.进程线程的区别

i.(本质)

进程是资源分配的最小单位。

线程是轻量级的进程,是系统调度的最小单位

ii.(安全性)

线程没有进程安全(一个线程导致进程结束,其他所有的线程都不能执行)

iii.(并发性)

多线程的并发性比多进程的高(因为线程间切换比进程间切换时间短,进程切换需要先在内存上保存当前状态)

iv.(通讯)

线程共享进程资源,所以线程间通信要比进程间通信更为容易。

4.线程间通信方式

        无名信号量,条件变量,互斥锁

5.解决死锁的方法

        1.指定线程获取锁的顺序(解锁以后在获取其他锁)

        2.尽量避免锁的嵌套使用(嵌套:当前锁未解锁时获取其他锁)

        3.给线程上锁指定超时时间(pthread_mutex_timedlock,超时未获取到锁,解锁,立即返回)

        4.在全局位置指定锁是否被使用的状态,如果被使用就不在获取(flag)

6.进程间通信方式有哪些?哪种效率最高?

        无名管道,有名管道,信号,消息队列,共享内存,信号灯集,socket。共享内存(省去了用户向内核交互的过程,可以直接操作物理内存)

7.共享内存和消息队列有什么区别?

        消息队列是基于内核实现的,内核中留有空间供用户操作,大小是16K

        共享内存是用户直接操作物理内存,省去了用户和内核空间传输数据的过程

8.无名管道读写特点

                读端存在,向管道中写数据:有多少写多少,直到写满为止(64k)写阻塞,

                直到管道中腾出新的位置,以一页(4k)为单位,写操作解除阻塞

                读端不存,向管道中写数据,管道破裂(SIGPIPE)

                写端存在,从管道中读数据:有多少读多少,没有数据的时候阻塞等待

                写端不存在,从管道中读数据:有多少读多少,没有数据的时候立即返回(非阻塞)

9.有名管道读写特点

                如果读端存在写管道:有多少写多少,直到写满为止(64k)写阻塞

                如果读端不存写管道

                        1.读端没有打开,写端在open的位置阻塞

                        2.读端打开后关闭,管道破裂(SIGPIPE)

                如果写端存在读管道:有多少读多少,没有数据的时候阻塞等待

                如果写端不存在读管道

                        1.写端没有打开,读端在open的位置阻塞

                        2.写端打开后关闭,有多少读多少,没有数据的时候立即返回

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值