进程基本概念和练习

进程/线程

1.    linux多任务编程
    (1)    指的是用户可以同一时间运行多个应用程序(任务)。
        我们的window和linux操作系统都是同时运行多个任务的。
            windows----->任务管理器
            linux  ----->ps -ef
    
        int main()
        {
            printf("Hello,Process!\n");
            while(1)
                ;
        }
        gcc main.c -o main
        main---->可执行程序  死的
        ./main ---->进程     活的
        
        程序到底是什么?
        sum.c
        int main()
        {
            int a = 5,b = 6;
            int sum = a + b;
            printf("sum = %d\n",sum);
        }
        程序 = 算法 + 数据结构
        程序 = 指令 + 数据
        用来表示人们思维对象的抽象概念的物理表现叫做数据,比如:a b sum
        我们把数据处理的规则叫做操作(指令),如:+
        对某一有限数据集合所施行的、目的在于解决某一问题的一组有限的指令的集合,
        称之为一个计算(compute).
        计算机就是用指令来处理数据。所以我们可以称:
            程序是存放在磁盘上的可执行文件,是数据和指令的集合,一个程序的执行过程就是一个计算。
            
    (2)    一个任务可以包含一个或者多个独立功能的子任务
        这些独立功能的子任务称之为进程或线程,比如杀毒软件....

        任务和进程、线程之间的关系:
            
        现代操作系统为了提高CPU的利用率,特定的引入了并发的概念。
        
2.    进程
    进程是具有独立功能的某个数据集合上的一次动态的执行过程,简单的说,进程就是一个程序的
    一次执行过程,程序一旦执行,就变成了进程。
    
    在Linux系统中用task_struct来描述和记录进程的一切,这个结构体也称之为进程控制块(Process Control Block)
        该结构体的定义在如下文件中可以查看(637行左右)
        /usr/src/linux-headers-5.3.0-40/include/linux/sched.h
    小结:
        当一个程序执行时,系统就会创建/启动一个进程,这个进程用结构体来描述,其中记录和描述了
        这个进程所需要的所有资源,随着进程的运行,各种资源被分配和释放,是一个动态的过程。

3.    进程的组织形态
    在系统中每一个进程都有一个唯一的ID,称之为PID(Process ID)
    PID是重要的系统资源,是区分其他进程的基本依据。其本质上就是一个非负的整数(>=0)

    在主流的操作系统中,如果进程A启动了进程B,就把进程A称之为进程B的父进程,进程B就称之为
    进程A的子进程。
    
    linux中任何一个进程都有一个创建/启动它的“父母”,除了0号进程。
    
    linux中进程0启动了进程1和进程2,其它进程都是由进程1或进程2创建/启动,从而形成树状结构。
    
4.    进程状态
    在单CPU的计算机中,所谓的“同时”运行多个任务(并发),并不是真正的并发,
    系统把CPU的执行时间,切分为细小的单位,比如10ms,称之为时间片。
    在单个时间片内,时间片到,就执行下一个程序,如此循环。
    
    进程是程序的执行过程,有自己的生命周期,可分为如下状态:
        就绪态:进程具备执行的一切条件,正在等待CPU的处理
        运行态:进程正在运行,占用CPU
        阻塞态:因等待某件事件发生而休眠,如果等待的资源分配到位,就会被唤醒进行就绪态。
        
    那么我们刚提到要按某种规则挑选一个就绪的进程开始运行 ,怎么挑选呢?
        所有的就绪的进程会则组成一个“就绪队列”(Ready Queue)
        
        那么我们会有一个“调度程序”负责确定下一个进入"Running"状态的进程。
        
        “调度程序”是按某种“调度策略(调度算法)”来进行调度的,如:
            分时系统:
                调度策略是以“时间片轮转”为主要策略的系统
                “时间片轮转”:分时,每一个进程执行一段时间(时间片)。
                如:
                    大部分的桌面系统,如:Linux,Android,Windows,UINX...
            
            实时系统:
                调度策略是以“实时策略”为主要策略的系统
                “实时策略”:每次调度都优先取优先级最高的那个进程开始执行,直到这个进程
                执行完毕或它主动放弃CPU或其他更高优先级的进程抢占。
                如:
                    UCOS,FreeRTOS...

            无论是哪种系统都会有“抢占(插队)”的情况发生。

5.    linux进程地址空间布局
    我们知道程序一旦开始执行就会进行系统资源的分配,那么进程做的第一件事就是申请一块内存区域
    来存储程序的“数据”和“指令”,那么一个程序里面"数据"和“指令”他们的属性是各不一致的,所以
    要分段(区域)来存储程序中的“数据”和“指令”。
    
    linux对进程的数据进行分段管理,不同的属性的数据存储在不同的“内存段”中。
    不同的“内存段(内存区域)”的属性以及管理方法各不一样。
    
    .text 段
        主要用来存储代码。只读并且共享的,这段内存在程序运行期间(进程存活期间)不会被释放。
        “代码段”是随进程的持续性。
    
    .rodata段
        只读,这段内存在进程运行期间,一直存在,随进程的持续性。
    
    .data段:
        可读可写,这段内存在进程运行期间,一直存在,随进程的持续性。
        
    .bss段:
        可读可写,这段内存在进程运行期间,一直存在,随进程的持续性。
        在这个段中的数据,有可能会被全部初始化为0
    
    堆(heap) 动态内存空间
        主要是malloc/realloc/calloc动态分配的空间
        可读可写的,这段内存在进行运行期间,一旦分配,就会一直存在,直到你手动free或进程消亡。
        防止“内存泄露/垃圾内存”
    
    栈(stack)空间
        可读可写的,这段空间会自动释放,随代码块的持续性。
        
6.    Linux下进程相关的API函数
    1)    创建进程
        
        NAME
            fork - create a child process

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

            pid_t fork(void);
            通过复制调用进程来创建新的子进程,新的子进程几乎与原有进程一致,但是它有自己的PID。
            
            返回值:
                当fork调用成功,就创建出一个新的进程,把新的子进程的PID返回给父进程,子进程自己
            本身返回0.如果fork失败,返回-1,同时errno被设置。
    
            注意:
                1.    当创建子进程后,那么父进程和子进程到底谁先执行,不一定。
                2.    如果父进程先执行,执行完毕之后,子进程还没有结束的话,子进程会认其他进程
                    为新的父进程。
                3.    新的进程是用复制创建出来的,拥有父进程的所有资源。新的进程从fork之后开始执行
                    即fork之前的代码父进程执行一遍,fork之后的代码,父子同时执行。
            
    2)    获取PID
        #include <sys/types.h>
        #include <unistd.h>

        pid_t getpid(void);        //获取当前进程的ID
        pid_t getppid(void);    //获取当前进程的父进程的ID
        
        练习:
            写一个代码,创建4个进程:

    3)    进程退出
        3.1    正常退出(自杀)
            a.    在main函数中执行return
            b.    调用exit/_exit函数
                #include <stdlib.h>

                void exit(int status);
                    status:表示退出码,表示退出状态的。
                    退出码的具体含义,由程序猿来解释。
                    exit()正常退出,做一些清理工作(比如清理缓冲区)。
                
                #include <unistd.h>

                void _exit(int status);
                    status:表示退出码,表示退出状态的。
                    退出码的具体含义,由程序猿来解释。
                    _exit连夜坐火车走的,终止进程,来不及做清理工作。
                    
        3.2    非正常退出(他杀)
            在外力干涉下结束,如进程出现段错误,系统会自动结束这个进程。
            
    4)    注册终止函数
        即main函数正常结束后调用的函数。
        
        NAME
            atexit - register a function to be called at normal process termination

        SYNOPSIS
            #include <stdlib.h>

            int atexit(void (*function)(void));

            1.    atexit()注册的函数类型应为不接受任何参数的void类型
            2.    调用注册函数的顺序与他们注册时候的顺序是相反的,即先注册后执行,后注册先执行
                同一个函数若注册多次,则也会被调用多次。
            3.    如果main函数是在非正常退出的情况下,注册函数将不会被调用。
                
    5)    等待一个进程
        NAME
            wait, waitpid, waitid - wait for process to change state

        SYNOPSIS
            #include <sys/types.h>
            #include <sys/wait.h>

            pid_t wait(int *wstatus);
            pid_t waitpid(pid_t pid, int *wstatus, int options);
            
            一个进程退出,操作系统会释放这个退出进程的大部分资源,就是说有一小部分资源必须
            留给他的父进程去释放。如果一个进程退出了,但是它的父进程没有去释放这个子进程,
            那么这个子进程就会变成一个僵尸进程。
            僵尸进程:进程已经死掉了但是资源还没有被完全释放掉(还没死透)。
            那么僵尸进程的产生将会造成系统资源的浪费,我们应该要尽量的去避免这种情况的发生。
            
            通过wait/waitpid函数来释放子进程留下来的资源。
            
            这两个函数的作用是用来等待某个(些)子进程退出的,当子进程正常退出时,调用wait/waitpid
            可以释放子进程的资源啦。假如没有调用wait/waitpid,那么子进程退出后,就会变成僵尸进程
            
            pid_t wait(int *wstatus);
                父进程一旦调用了wait,就立即阻塞自己,由wait自动分析当前进程的某个子进程是否
                已经退出,如果让它找到一个已经变成僵尸进程的子进程,wait就是收集这个子进程的
                信息,并将子进程销毁掉之后返回。
                如果没有找到一个这样的子进程,wait就会一直阻塞,直到有一个符合条件的进程
                出现为止。
                
                其中有一个参数
                wstatus:它是一个int类型的指针,那么这个参数是用来收集子进程的退出信息的,
                所以它指向的空间是用来保存子进程的退出信息(子进程是正常退出还是非正常退出、
                子进程的退出码、who kill....)。如果我们并不在意子进程的退出信息的话,
                那么我们就可以设定这个参数为NULL。
            
            返回值:
                成功返回退出的那个子进程的进程ID,失败返回-1,同时errno被设置。
        
            需要注意的是:
                *wstatus是一个整数,用来保存退出的子进程的退出信息的,退出的信息比较多,
                这些退出信息全部保存在一个整数中。则意味着不同的退出信息要保存在不同的
                比特段中,比如:
                    我们的退出码就存储于第8bit到第15bit的段上。
                    
                注意:
                    因为这退出码是保存在wstatus指向的空间的第8bit到第15bit上,所以
                    退出码的范围不应该超过一个unsigned char的取值范围(<=255)。
                    
                如果我们要获取到进程的退出信息的话则必须要对status进行解析,解析通过如下宏:
                    WIFEXITED(wstatus)
                    如果子进程是正常退出的,则这个宏将会返回一个非零。
                    WEXITSTATUS(wstatus)
                    当WIFEXITED()返回一个非零值时,我们就可以利用这个宏来提取子进程的退出码啦。
                    例子:
                        exit(4)    WEXITSTATUS(wstatus)-->4
                        exit(257)  WEXITSTATUS(wstatus)-->1
                    如果WIFEXITED返回一个0,则这个宏毫无意义,因为WIFEXITED返回0表示
                    进程是非正常退出的,非正常退出的进程是没有退出码的。
                    
            pid_t waitpid(pid_t pid, int *wstatus, int options);    
                pid:指定要等待的进程或进程组
                    pid == -1        表示等待任意的子进程退出
                    pid == 0         表示等待与调用进程同组的任意子进程。
                        “进程组”:就是一组进程。
                        每一个进程必须会属于某一个进程组。并且每个进程组,都会有一个组长进程,
                        一般来说,创建这个进程组的进程为组长,进程组有一个组ID,这个组ID就是
                        组长进程的ID。
                    pid < -1        表示等待组ID等于pid绝对值的那个组的任意子进程
                                    如:    
                                        pid == -2398
                                        等待进程组ID为2398的那个组内的任意子进程
                    pid > 0            表示等待指定的子进程(其进程ID为pid的那个子进程)
                        
                wstatus:同上。
                
                options:等待选项。
                    0:表示阻塞等待
                    WNOHANG:非阻塞,假如没有子进程退出,则立即返回。
                    
            返回值:    
                成功返回退出的那个子进程的进程ID,失败返回-1,同时errno被设置。

            wait(&status) <----> waitpid(-1,&status,0);                
                
        作业:
            假设程序名为a.out,问程序执行后,总共出现过多少个a.out进程?
                int main()
                {
                    fork();
                    fork() && fork() || fork();
                    fork();
                    printf("Hello,World!\n");
                }
                
    6)    启动外部程序
        我们通过exec函数族来实现:
            #include <unistd.h>

            extern char **environ;

            int execl(const char *path, const char *arg, .../* (char  *) NULL */);
            int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
            int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
            int execv(const char *path, char *const argv[]);
            int execvp(const char *file, char *const argv[]);
            int execvpe(const char *file, char *const argv[],char *const envp[]);
            
            exec函数族是让一个进程去执行另外一个程序文件。
            
            a.    int execl(const char *path, const char *arg, .../* (char  *) NULL */);
                
                让进程去执行参数指定的程序文件
                
                参数:
                    path:程序文件的文件名(需要指定这个程序的路径的)
                    arg...:新程序执行需要的参数,参数的个数是可变的。
                        注意:
                            参数的个数可以有很多,但最后一定要以NULL结尾。
                            参数列表中的第一个参数应该是可执行文件(程序)的名字。
                
                l:(list)的意思就是参数是以列表的形式存在的。
                
            b.    int execv(const char *path, char *const argv[]);
                
                execv与execl作用、功能、返回值都是一样的。
                唯一的区别在于,指定的程序文件的带参数的方式不一样。
                    path:程序文件的文件名(需要指定这个程序的路径的)
                    argv:指定程序运行的参数,程序运行的第一个参数是程序名,最后一个为NULL
                    表示参数结束了。
                    
                带V(vector向量)的意思就是参数是以数组的形式存在的。
                
            c.    int execlp(const char *file, const char *arg, .../* (char  *) NULL */);    
                int execvp(const char *file, char *const argv[]);
                带p(PATH):意思就是指定的程序文件在标准的命令搜索路径(PATH)下
                    file:可执行文件的程序名(可以不带路径)
                    其他参数同上
                    
            d.    int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
                int execvpe(const char *file, char *const argv[],char *const envp[]);
                
                带e(environment):使用环境变量数组设置新执行的程序运行的环境,不使用
                进程原有的环境变量
                
                环境变量:
                    PATH
                    HOME
                    LD_LIBRARY_PATH
                    ....
                    环境变量中包含用户的主目录,命令的搜索路径,当前目录.....,他们包含了
                    用户的工作环境,所以我们称之为环境变量。
                    
                我们可以通过一个名称 env 的指令去查看系统环境变量。
                
                系统同时也预定义了一个environ全局变量显示各个环境变量的值。
                extern char **environ;
                
                我们可以通过execvpe函数修改程序所运行的环境:
                    
    7)    system
        
        NAME
            system - execute a shell command

        SYNOPSIS
            #include <stdlib.h>

            int system(const char *command);
                
                system用来执行command指定的命令或程序或sh脚本。
                
                    system("ls -l");--->fork--->exec("ls -l")
                
                system会等待命令或程序执行完毕,system实际上是新创建了一个进程去执行指定的
                命令或程序。
                
                返回值:
                    成功返回状态码,失败返回-1.
                    
        
        在linux下有一条mplayer或者madplay。
        
    作业:
        写一个程序,创建一个子进程,播放一个目录下的所有的mp3/mp4文件,实现自动循环/手动播放。
            
            mplayer :是一个开源的多媒体播放器,此款软件可在各主流作业系统上使用,因为
            linux下都是命令行的操作方式,所以对mplayer的各个操作都是用命令来实现的,
            此次项目主要是用到了mplayer它的slave(从模式)工作方式。
            
            默认mplayer是从键盘上获取控制信息,mplayer另外提供了一种更为灵活的控制方式,
            用来进行播放控制---slave模式。在slave模式,mplayer为后台运行其他程序,不再
            截获键盘事件,mplayer会从标准输入读取一个换行符分隔开的命令。
            
            查看mplayer所支持的所有slave模式的命令:
                mplayer -input cmdlist
                
            根据命令去操作mplayer的方式有两种:
            1.    从控制台输入控制命令(在终端上)
                在终端上输入指令:
                    mplayer -slave -quiet xxx.mp4

                    -slave   启动从模式
                    -quiet     不输出冗余的信息
                    
                启动mplayer之后,我们就可以在终端上输入指令来控制mplayer的运行啦:

                    loadfile string         //参数string为播放文件的名字
                    volume 100 1            //设置音量,中间的值为音量的大小
                    mute 1/0                //静音设置,1:静音   0:开启声音
                    pause                     //暂停/取消暂停
                    get_time_length         //返回值是播放文件的长度,以秒为单位
                    seek value                 //快进/后退,参数value为秒数
                                            //value为正,快进value秒
                                            //value为负,后退value秒
                    seek value 1            //意味着跳转到百分之value处                    
                    get_percent_pos         //按百分比输出当前的播放进度
                    get_time_pos             //把文件当前的播放的秒钟数以浮点型打印出来
                    get_file_name             //打印出当前文件的名字
                    quit                     //退出播放
            
            2.    从有名管道输入控制命令(在代码中使用)
                我们可以用指令指定mplayer从有名管道中获取指令,那么我们要做的就是在代码中
                往管道中写入指令就同样的可以控制mplayer。
                    
                    指定mplayer从哪一个管道中获取指令:
                        mplayer -slave -quiet -input file=xxx.fifo
                        
                        write(xxx.fifo,"pause\n",sizeof("pause\n"));
                
                    另外,我们还可以指定mplayer的播放界面的大小:
                        
                        mplayer -zoom -x 800 -y 480 xx.mp4
                    
                    
                    so,我们最终的mplayer的完整命令为:
                    
                    mplayer -slave -quiet -input file=xxx.fifo -zoom -x 800 -y 480 1.mp4
                    
                    
                    int c = getchar();

                    switch(c)
                    {
                        case 'a':
                            write(xxx.fifo,"seek -5\n",sizeof("seek -5\n"));
                            break;
                        case 'p':
                            write(xxx.fifo,"pause\n",sizeof("pause\n"));
                            break;
                            ......
                    }
            //搜索所有的MP3/MP4文件--->链表
            while(1)
            {
                //取下一首
                pid_t pid = fork();
                if(儿子)
                {
                    exec--->让儿子去播放视频
                }
                else if(老子)
                {
                    wait(儿子);
                }    
            }

7.    IPC:Internal Process Communication
    进程间通信,实质是:信息(数据)的交换
    
    如果两个进程之间要进行通信,就必须要把数据放在一个大家都能够访问到的地方。
    
    文件可以吗?当然可以,因为文件在文件系统中,大家都可以访问。
    
    但是这种方式有一个缺点,速度太慢了。
    
    我们讲内存的读写速度比文件块很多,那能不能在内存中创建一块空间,这块内存空间通信进程
    都是能够访问的,以此来解决问题呢?
    
    IPC方式:
        管道    :    pipe    无名管道
                    fifo    有名管道
                
        信号    :    signal
    
        消息队列:    System V消息队列 / POSIX消息队列---->淘汰啦
        信号量    :    System V信号量 / POSIX信号量
        共享内存:    System V共享内存 / POSIX共享内存
        Socket     :    Unix域协议
        
        
    在很久很久以前,其实我们进程间通信就是通过文件。    
    
    但是这种方式,有一个缺点,就是效率太低了。
    
    但是这种方式又有一个天大的好处就是简单,不需要额外提供API函数(直接利用文件系统的API函数)
    
    有人想,能不能改进一下?
        问题是文件内容是在外设(硬盘)上--->访问速度太低了。
        能不能把文件的内容放到内存上去呢?
        
        管道:管道文件,但是内容是在内存或内核中的。
        
    (1)    无名管道 pipe
        
        我们说无名管道虽然是一个文件,但是它在文件系统中没有名字(没有inode),他的内容在内核中,
        访问pipe的方式还是通过文件系统的API(open/read/write...),但是他又不能用open(因为没有名字)
        问题是read/write又需要用一个文件描述符才能对文件进行操作,所以在创建一个pipe的时候,
        就必须要返回文件描述符。
        
        pipe在创建时,在内核中开辟一块缓冲区,作为pipe文件内容的存储空间,同时返回两个
        文件描述符(一个用来读,一个用来写)
        
        并且有如下特点:
            1.    pipe有两端,一端是用来写,一端是用来读。
            2.    按顺序读,不支持lseek。
            3.    内容读走了,就没啦。
            4.    pipe(无名管道)随内核的持续性。
            
        函数接口:
            NAME
                pipe - create pipe

            SYNOPSIS
                #include <unistd.h>

                int pipe(int pipefd[2]);
                    pipe用来在内核中创建一个无名管道的。
                    pipefd:数组
                        pipefd[0]    保存读的文件描述符
                        pipefd[1]    保存写的文件描述符
        
                    返回值:成功返回0,失败返回-1,同时errno被设置。
        
                pipe创建的管道,默认是“阻塞方式”。
                意思就是:读一个管道,如果管道中没有数据,则阻塞,直到有数据可读为止
                          往一个管道中写数据,如果管道不可写,则阻塞,直到文件可写为止。
                          
                原则上来讲:只要两个进程能获取到同一个pipe的文件描述符,就可以用pipe进行通信。
                一般情况下无名管道用于有亲缘关系的进程间通信。
                
                pipe本身是一个全双工通信,但是两个进程用一个管道去实现全双工的哦通信可能会有
                问题,问题就是自己很有可能会读到自己写的数据,所以我们人为的把pipe当成
                半双工使用。
                
    (2)    有名管道
        pipe(无名管道)只能用于有亲缘关系的进程间通信,为什么会有这么一个限制呢?
        原因就是pipe没有名字
        
        假设他在文件系统中有一个名字(inode),它就可以用于任意进程间的通信。
        那么这个就是我们的有名管道fifo。
        
        fifo是在pipe的基础上,给fifo在文件系统中创建一个inode(它会在文件系统中有一个名字)
        但是fifo文件内容却是在内核中。
        
        fifo的文件名随文件系统的持续性。
        
        fifo同pipe一样,文件内容是存在于内核中的,随内核的持续性。
        
        那么操作fifo的一个步骤就是:
            open ---> read/wirte ---> close
            
        fifo的创建为:
            mkfifo
            
            #include <sys/types.h>
            #include <sys/stat.h>

            int mkfifo(const char *pathname, mode_t mode);
                mkfifo用来在文件系统中创建一个fifo(有名管道)
                
                pathname:要创建的有名管道在文件系统中的名字
                mode:创建的有名管道的权限,有两种方式指定:
                    a.    S_IRUSR....
                    b.    0660
            
                函数返回值:
                    成功返回0,失败返回-1,同时errno被设置。
            
            fifo的创建有两种不同的形式:
            1.    通过指令mkfifo
                mkfifo ~/test.fifo
                
                创建到linux的目录下去,不能创建到共享文件夹,因为window不支持fifo。
                
            2.    通过函数
                       
                       
            关于fifo的一段描述:           
            A  FIFO special file (a named pipe) is similar to a pipe, except that it is accessed
           as part of the filesystem.  It can be opened by multiple processes  for  reading  or
           writing.   When  processes  are  exchanging data via the FIFO, the kernel passes all
           data internally without writing it to the filesystem.  Thus, the FIFO  special  file
           has no contents on the filesystem; the filesystem entry merely serves as a reference
           point so that processes can access the pipe using a name in the filesystem.

            
            FIFO(有名管道)和PIPE(无名管道)类似,除了它在文件系统中有一个名字,它可以被多个进程打开
            或者读/写,当进程用FIFO来交换数据时,内核根本没有把数据写入到文件系统中去,
            而是保存在内核的内部,它仅作为文件系统的一个引用入口,提供一个文件名,给其他进程去
            open它。
            
            在数据交换前,FIFO的两端(read/write)必须都被打开。
            
            通常情况下,你打开FIFO的一端,会阻塞,直到另外一端也被打开。
            
            一个进程也可能以“非阻塞”(O_NONBLOCK)的方式打开
            在这种情况下,只读打开总会成功,即使是写端没有被打开
            只写打开总会失败,并且errno == EENXIO,除非读端已经被打开。
            
        阻塞方式:阻塞的读或写
            读的时候,如果没有数据,则read会阻塞
            写的时候,如果没有空间,则write会阻塞
            
        非阻塞方式:以非阻塞读或写
            读的时候,如果没有数据,立即返回,设置相应的错误码
            写的时候,如果没有空间,立即返回,设置相应的错误码
            
    (3)    信号
        信号是进程间通信的一种方式,这种方式没有传输数据。只是在内核中传递了一个信号(整数),
        信号表示的就是一个整数。
        
        比如如果我们有一个进程正在执行,突然之间它收到了一个信号2(SIGINT),在我们linux下面,
        一个进程收到信号2默认的动作就是被终止了(Terminate)。

        不同的信号值,代表不同的含义,当然用户也可以自定义信号。
        
        那么自定义的信号的含义和值由程序猿来定义和解释。
        
        那么信号的本质其实是软中断。

        信号会中断正在执行的某一个任务/某一段程序,进而去处理中断(处理函数),处理完中断后,
        再回来继续执行原有的程序。
        
        信号是异步的。
        
        注意:    
            如果用户没有显示的处理信号,系统的默认处理方式大多是终止进程。
            
        查看信号可以在终端上输入:
            man 7 signal
            
            
       Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
                                     
       SIGINT        2       Term    Interrupt from keyboard
                                     从键盘上收到的中断信号,按下ctrl + c就会产生这个信号
       
       SIGQUIT       3       Core    Quit from keyboard
                                     默认处理是输出信息然后终止进程,按下ctrl + /就会产生这个
                                     信号
       
       SIGILL        4       Core    Illegal Instruction
         
       SIGABRT       6       Core    Abort signal from abort(3)
       
       SIGFPE        8       Core    Floating-point exception
                                     浮点型异常
       
       SIGKILL       9       Term    Kill signal
                                     杀死进程,不可被捕捉和忽略
        
       SIGSEGV      11       Core    Invalid memory reference
                                     段错误
                                     非法内存引用时,会收到SIGSEGV。
                                     此时输出信息,然后终止发生段错误的进程
       
       SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers; see pipe(7)
                                     管道破裂。
                                     当你往一个管道写数据时,没有读端进程时就会产生这个信号。
                                     
       SIGALRM      14       Term    Timer signal from alarm(2)
                                     定时信号,在进程中调用alarm时,会在超时的时候,
                                     产生这个信号。
       
       SIGTERM      15       Term    Termination signal
       
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
                                     用户自定义信号,这个信号什么含义有用户自己解释。
       
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
                                     当子进程停止或终止时,父进程会收到SIGCHLD这个信号
                                     父进程收到这个信号之后,并不会怎么样,因为这个信号默认是忽略
       
       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
                                     停止进程,不可被捕捉和忽略
       
       SIGTSTP   18,20,24    Stop    Stop typed at terminal
       SIGTTIN   21,21,26    Stop    Terminal input for background process
       SIGTTOU   22,22,27    Stop    Terminal output for background process

    linux下信号相关的API函数
        1)    发送信号     
            NAME
                kill - send signal to a process

            SYNOPSIS
                #include <sys/types.h>
                #include <signal.h>

                int kill(pid_t pid, int sig);

                kill用来把一个信号发送到一个指定的进程或多个进程
                
                kill不仅是一个函数,也是一条指令。

                    pid:指定信号的接受者(可能是多个进程)
                        pid > 0     pid表示指定的那一个接收者进程
                        pid = 0     发送信号给与调用进程同组的所有进程
                        pid = -1    发送信号给系统所有进程(有权限发送的所有进程)
                        pid < -1    发送信号给组ID等于pid的绝对值的所有进程
                    sig:要发送的信号,参考上面的宏
                        
                    返回值:    
                        成功(至少有一个进程成功接收到了信号)返回0,失败返回-1,同时errno被设置。
                    
        2)    raise  
            NAME
                raise - send a signal to the caller

            SYNOPSIS
                #include <signal.h>

                int raise(int sig);
                    发送信号给自己
                    raise(sig) <---> kill(getpid(),sig)
        
        3)    alarm
            NAME
                alarm - set an alarm clock for delivery of a signal

            SYNOPSIS
                #include <unistd.h>

                unsigned int alarm(unsigned int seconds);
                alarm定时发送一个闹钟信号(SIGALRM)给本进程
                
                “闹钟”:每一个进程都有属于自己的一个“闹钟”,“闹钟”时间到了,进程就会收到一个
                SIGALRM的信号,但是同一时刻只有一个“闹钟”生效。
                
                    seconds:多少秒之后,发送一个“闹钟信号”。
                    
                    返回值:返回上一个闹钟的剩余时间。
                
                    例子:
                        alarm(5);            //5秒钟之后收到一个“闹钟信号”
                        ...                    //执行这个代码花了2秒
                        int r = alarm(10);    //闹钟时间被重新设置为10秒,即前面的那个5秒的闹钟被
                                            //替换掉了
                        
                        alarm(0);            //表示取消闹钟
                    
        4)    捕捉信号:改变信号的处理方式
            NAME
                signal - ANSI C signal handling

            SYNOPSIS
                #include <signal.h>

                typedef void (*sighandler_t)(int);
                    sighandler_t:是一个新的类型,这种类型可以用来定义一个函数指针的变量
                        这个变量指向一个无返回值,有一个int类型的参数的函数。
                        
                sighandler_t signal(int signum, sighandler_t handler);
                    signum:要捕捉的那个信号的信号值
                    handler:这是一个函数指针,指向一个函数。
                        
                        信号的处理的方式有如下三种:
                            a.    自定义的处理函数
                                这个处理函数为无返回值,但是带一个int的参数(用来保存收到的
                                那个信号的信号值)的函数。
                                大概格式如下:
                                    void my_sig_handler(int sig)
                                    {
                                        //函数的功能用户自己实现
                                    }
                            
                            b.    默认
                                SIG_DFL:default,采用操作系统默认的处理方式
                            
                            c.     忽略
                                SIG_IGN:ignore,忽略该信号
                                
            练习:
                我们的程序,通常可以被键盘上的ctrl + c干掉
                能不能写一个新的程序,ctrl + c不能干掉你呢?
                两种方式:
                    一种直接忽略掉ctrl + c发过来的信号:
                        
                    第二种方式:
                        自定义的处理函数
            
        5)    pause
            NAME
                pause - wait for signal

            SYNOPSIS
                #include <unistd.h>

                int pause(void);
                让进程停在那里,等待某个信号的到来,直到收到信号。
                当一个信号被捕获,且信号处理函数返回后才返回,这种情况下返回-1。
                
    (4)    共享内存
        为了提高进程间通信的效率,有人就提出能不能让多个进程共享一段内存,这段内存既是你的
        也是我的,你往这段内存空写数据,实际上就是往我的内存中写数据。
        那么我们就把这种进程间的通信方式称之为共享内存。
        有点类似于我们的“共享文件夹”。
        
        这种方式比其他的IPC方式(像PIPE,FIFO,Message...)至少少copy了两次,共享内存的效率最高。
        
        共享内存的实现方式:
            在内核中开辟一块共享内存,其他要通信的进程通过“映射”方式获取这段共享内存的引用(指针)。
            
        进程1可以映射这段内存,同时其他的进程(如P2)也可以映射这段内存,p1往这段内存中写数据
        实际上就是往p2的进程地址空间中写数据,反之亦然。

        System V IPC(msg/shm/sem)操作流程:
            通过ftok()函数获取 ----> System V IPC对象的key ---> 通过key创建或打开这个IPC
            的设施(msg/shm/sem)----> 通过System V IPC提供的读/写函数接口交换数据 ---> 关闭设施
            
            
            通过某个软件订房 ---> 获取房卡 --->  进入房间 ---> 交换数据 ---> 打扫房间溜啦。。
            
        System V共享内存的API函数:
            a.    ftok 用来创建一个System V IPC对象的key
                NAME
                    ftok - convert a pathname and a project identifier to a System V IPC key

                SYNOPSIS
                    #include <sys/types.h>
                    #include <sys/ipc.h>

                    key_t ftok(const char *pathname, int proj_id);
                        pathname:一个文件系统中的路径名(必须是真实存在的并且有权限访问的)。
                        proj_id:整数。这个参数存在的意义在于让一个文件也能生成多个IPC key值。
                            ftok利用同一个文件最多可得到的IPC key键值为256个。
                            因为ftok只取proj_id值二进制的后8bits,即16进制的后两位与文件信息
                            合成IPC Key键值。
                            
                        也就是说。ftok("/home",257) <--->ftok("/home",1);    
                            生成的key值是一样的。
                        
                        返回值:
                            成功生成一个唯一的System V IPC的key(类型为key_t --> int)
                            失败返回-1,同时errno被设置。
            
                    注意:    
                        key值其实就是一个32位的int,key的生成是根据pathname和proj_id来的
                        具体其实是按照pathname指定的文件(目录)的属性,也就是说struct state
                        这个结构体中的st_dev的低八位和st_ino成员变量的低十六位和proj_id的低百位
                        组合而成的。
                        
            b.    shmget
                NAME
                    shmget - allocates a System V shared memory segment

                SYNOPSIS
                    #include <sys/ipc.h>
                    #include <sys/shm.h>

                    int shmget(key_t key, size_t size, int shmflg);
                    shmget用来在内核中创建或打开一个System V共享内存
                    
                        key:System V IPC对象的key,一般由ftok的返回值获得。
                        size:以字节为单位指定共享内存区的大小
                            当实际操作为创建一个新的共享内存区时,必须指定一个不为0的size值
                            (PAGE_SIZE的整数倍,PAGE_SIZE:4K),如果实际操作为访问一个已经存在
                            的共享内存区,那么size因为0
                    
                        shmflg:标志位
                            如果是创建共享内存:IPC_CREAT | 权限位(0666)
                            如果是打开共享内存: 0
                            注意:
                                共享内存一旦创建,是随内核的持续性的。其实你完全可以按照创建
                                的方式去指定也可以,因为发现key对应的共享内存已经存在了,
                                那么它就会自动忽略后面的选择而直接打开。
                                即使使用(创建)它的进程结束了 这块共享内存也存在,除非显示的去释放它。
                                
            c.    映射
                shmat
                NAME
                    shmat, shmdt - System V shared memory operations

                SYNOPSIS
                    #include <sys/types.h>
                    #include <sys/shm.h>

                    void *shmat(int shmid, const void *shmaddr, int shmflg);
                        把内核的共享内存映射到进程空间中去
                        shmid:要映射的共享内存区域的id(通过shmget获取)
                        shmaddr:指向要映射到进程的哪一个地址上去
                                一般为NULL,表示由操作系统自行分配
                        shmflg:SHM_RDONLY   只读
                                0             读写
                        返回值:成功返回映射后的首地址,失败返回NULL,同时errno被设置。
                    
                    
            d.    解映射
                int shmdt(const void *shmaddr);
                shmdt用来解映射一端system v共享内存的。
                    shmaddr:要解映射的那个地址
                    
                    返回值:成功返回0,失败返回-1,同时errno被设置。
                
            e.    共享内存的删除操作
                shmctl
            
                NAME
                    shmctl - System V shared memory control

                SYNOPSIS
                    #include <sys/ipc.h>
                    #include <sys/shm.h>

                    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
                        shmid:要删除的共享内存的ID
                        cmd:IPC_RMID--->删除一块共享内存
                        buf:NULL
    
    5)    信号量
        如果有两个或两个以上的任务(进程/线程),去访问同一个共享资源(硬件上的/软件上的),那么
        我们必须要保证这个共享资源有序访问,否则会产生不可预知的后果。
        
        怎么去解决这个问题呢?
            造成这个问题的原因是主要是进程对同一个资源访问不是有序的,就是说一个进程访问一个
        资源时,其他进程同时也在访问,那么他们之间就形成这种竞争的关系。
        
        所以我们需要对这个共享资源进行某种方式的保护,以使他被有序的访问,以避免竞争。
        
        我们是因为并发--->竞争--->共享资源的非法访问--->程序行为异常
        
        那么我们应该在保存并发的前提下,避免竞争,也就是说在多个进程访问一个共享资源的时候,
        严格串行,那么这种机制我们就称之为信号量机制。
        
        1.    信号量机制
            信号量(semaphore)是一种用于提供不同进程间或一个进程内部不同的线程间同步的一种机制。
                我们把进程/线程/任务称之为叫并发的实体。
                同步:并发的实体间互相等待,相互制约,有序的,有条件的访问共享资源。
                
            所以信号量就是为了保护共享资源,让共享资源有序访问的一种机制。
            
            信号量是我们程序界最高尚的一种东西,因为他不是为了自己而存在的,而是为了别人
            (它保护的对象->共享资源)而存在的,它相当于是一个保镖。
            
            信号量不是用来传输数据的,而是用来协调各进程/线程工作的。

            什么时候需要使用信号量呢?
                有保护的对象的时候,才需要信号量。
            
        2.    如何进行保护呢?
            “保护”:让这个被保护的对象(共享资源)有序的访问,如:“互斥”
            “互斥”:我在访问的时候你不能访问
            
            信号量机制其实是程序猿之间的一个约定,用来保护共享资源的
            
            比如说进程A和进程B,都要访问一个互斥设备,那么我们可以用一个信号量来表示
            能不能访问该设备,然后每一个进程访问该设备时,先去访问信号量,如果能访问
            设备则先把信号量设为“NO”,然后再去访问该互斥设备,访问完互斥设备,
            再把信号量设为“YES”。

            那么我们把多个进程/线程可能同时访问的资源称之为共享资源
            也称之为临界资源。
            把访问临界资源的代码,称之为临界区。

        3.    信号量是如何实现的?
            信号量本质上是一个整数,用来表示资源的数量
            信号量-->0        表示资源不可用
            信号量>0        表示资源可用
        
            一个进程或线程可以在某个信号量上执行如下三个操作:
            a.    创建一个信号量
                在创建一个信号量的同时,我们还要求创建者指定信号量的初始值。
                初始值表示该信号量保护的共享资源可以同时被多少个任务所访问。
                sem --> 5 表示可以有5个线程/进程同时访问它所保护的共享资源
                sem --> 1 表示只有一个进程/线程可以同时访问它所保护的共享资源
                    
            b.    等待一个信号量
                该操作会判断这个信号量的值,如果其值<=0,那么就会等待(阻塞),一旦
                其值>0,这个时候,将它-1,接着访问临界资源。
                也就是程序在进入临界区之前,必须对资源进行申请,这个操作称之为P操作
                申请成功,资源数量(信号量)减少,申请失败,要么等,要么走。
                
            c.    释放一个信号量
                该操作将信号量的值+1。
                程序离开临界区之后,必须释放相应的资源,这个操作称之为V操作
                释放资源,资源的数量(信号量)增加。
                
            总结:
                P操作为资源减操作,V操作为资源加操作。
                
            两者都是属于原子操作:
                是指不会被调度机制打断的操作,这种操作一旦开始,就一直运行到结束。
                也就是原子操作是不可被分隔的。
                
        思考:
            1.    现在有五个资源A.B.C.D.E需要保护,设计师决定用一个信号量S来同时保护这五个资源?
                请问这种设计有没有什么问题?
                
                这种设计可以达到互斥访问的目的,但是降低了并发度,因为A B C D E五个资源
                是可以同时访问的。

            2.    现在有五个资源A.B.C.D.E需要保护,设计师决定用5个信号量s1,s2,s3,s4,s5来
                分别保护这五个资源。

            3.    有这样一种情况:
                现在有一个进程p1需要同时访问A和B,那么在访问A和B之前,p1会先对s1和s2进行判断
                能不能访问,能访问的话就进行P操作,如:P(s1) P(s2)。
                那会不会会有另外一个进程p2也要访问A和B,那么访问之前p2也要判断,判断完
                就访问就P操作,如果进程2进行P操作的流程的P(s2) P(s1)
                就会造成死锁的现象发生。
                 
                解决死锁的问题应该是进程p1和p2一旦获取资源应该要同时对s1和s2进行P操作
                因为P操作是原子操作,所以要么s1.s2两个都没被上锁,要么s1.s2都被上锁。
                不会出现只上锁一个的情况出现。
                
        4.    信号量相关的API函数
            信号量的使用流程:
                a.    生成key--->ftok()
                b.    创建或获取信号量
                c.    信号量值的初始化
                d.    PV操作
                e.    用户删除
                
            信号量也分为两种:
                System V 信号量和POSIX信号量
            
            4.1) System V 信号量(semaphore)
                 System V 信号量分为两种:
                    互斥信号量:
                        该信号量的值要么是1,要么是0
                        它所保护的共享资源同一时刻只允许一个任务去访问它。
                        
                    计数信号量:
                        该信号量的值可以是>=1的值,它所保护的共享资源时允许多个
                        任务同时访问它。
                        
                        如果计数信号量的计数值为1,0,那么我们就可以把其看成是互斥信号量
                    
                System V采用的是计数信号量集:
                    计数信号量集是由一个或者多个计数信号量组成的集合。
                    计数信号量集其实就是一个计数信号量的数组。
                    
                而POSIX信号量采用的是计数信号量。
                
                1.    semget()-->用来创建或打开一个System V信号量集
                    NAME
                        semget - get a System V semaphore set identifier

                    SYNOPSIS
                        #include <sys/types.h>
                        #include <sys/ipc.h>
                        #include <sys/sem.h>

                        int semget(key_t key, int nsems, int semflg);
                            key:System V IPC对象的key(一般由ftok返回)
                            nsems:你要创建的信号量集中的信号量的个数
                                如果我们不是去创建而是去打开一个已经存在的信号量集的话。
                                此处的参数应该要指定为0.一旦创建完一个信号量集的话,
                                其信号量的个数就不能改变啦。
                            semflg:
                                (1) 创建 ---> IPC_CREAT | 0660    
                                (2)    打开 ---> 0
                                
                            返回值:    
                                成功返回System V信号量集的ID,失败返回-1,同时errno被设置。
                    注意:在一个新创建的信号量集中,信号量的值是不确定的
                    所以,我们在新创建一个信号量集后,需要马上去指定信号量的初始值!
                            
                2.    semctl:控制操作(设置或获取信号量集中某个或某些信号量的值)
                    NAME
                        semctl - System V semaphore control operations

                    SYNOPSIS
                        #include <sys/types.h>
                        #include <sys/ipc.h>
                        #include <sys/sem.h>

                        int semctl(int semid, int semnum, int cmd, ...);
                            semid:要操作的信号量集的ID(semget的返回值)
                            semnum:要操作的信号量集中的哪个信号量,就是信号量数组的下标
                                从0开始的,0,1,2,3,4,5......nsems-1。
                            cmd:命令号,常用的有:
                                GETVAL:获取第semnum个信号的值
                                SETVAL:设置第semnum个信号的值
                                GETALL:获取这个信号量集中所有的信号量的值
                                SETALL:设置这个信号量集中所有的信号量的值
                                IPC_RMID:删除这个信号量集
                                ....
                            针对第四个参数:
                                cmd == IPC_RMID,第四个参数不要
                                    如:
                                        semctl(semid,0,IPC_RMID);
                                
                                cmd == GETVAL
                                    第四个参数也不要
                                    函数返回值就是表示那个信号量的值
                                    如:
                                        int val = semctl(semid,1,GETVAL);
                                        用来获取semid指定的信号量集中第2个信号量的值
                                    
                                cmd == SETVAL,第四个参数应该为一个int
                                    表示要设置信号量的值。
                                    如:
                                        int sem_val = 1;
                                        int r = semctl(semid,1,SETVAL,sem_val);
                                    
                                cmd == GETALL
                                    第四个参数,应该为一个unsigned short vals[]
                                    这个数组的作用是用来保存获取的每个信号量的值的
                                    
                                    unsigned short vals[10];
                                    semctl(semid,0,GETALL,vals);
                                    //vals[0] 保存的就是信号量集中第一个信号量的值
                                    .....
                                    
                                cmd == SETALL
                                    第四个参数应该为unsigned short vals[]
                                        这个数组是用来设置每个信号量的值
                                        
                                    如:
                                        unsigned short vals[10] = {1,1,1,1,1,1,1,1,1,1};
                                        int r = semctl(semid,0,SETALL,vals);
                            
                            返回值
                                根据不同的命令,semctl返回值的含义不一样。
                                    一般情况下,0-->成功 
                                    -1---->失败
                                    
                3.    semop:System V 信号量P/V操作
                    NAME
                        semop, semtimedop - System V semaphore operations

                    SYNOPSIS
                        #include <sys/types.h>
                        #include <sys/ipc.h>
                        #include <sys/sem.h>

                        int semop(int semid, struct sembuf *sops, size_t nsops);
                            semid:要操作的是哪个信号量集
                            
                            在System V的信号量P/V操作,用一个结构体struct sembuf来描述
                            P/V操作的。这个结构体可以在man semop中看到
                                struct sembuf
                                {
                                    unsigned short sem_num;  /* semaphore number */
                                        要进行P/V操作的信号量在信号量集中的编号(下标)                                                                    
                                    
                                    short sem_op;   /* semaphore operation */
                                        > 0  --> V操作 unlock
                                        = 0  --> 试一试,看是否会阻塞(是否能访问)
                                        < 0  --> P操作 lock
                                        semval(信号量的值) = 原semval + sem_op;
                                    
                                    short sem_flg;  /* operation flags */
                                        0    : 默认,如果P操作获取不了,则会阻塞
                                        IPC_NOWAIT : 非阻塞,不等待
                                            如果是P操作,能获取则获取,不能获取则走人(返回-1)
                                        SEM_UNDO:撤销。
                                            为了防止进程带锁退出的。
                                            如果进程突然中止,自动释放资源。
                                };
                            sops:struct sembuf的数组    

                            nsops:第二个参数sops数组中的元素个数
                                或者说信号量集中要进行PV操作的信号量的个数
                                
                            返回值:成功返回0,失败返回-1,同时errno被设置。
                            
                        注意:    
                            semop可能会阻塞当前进程/线程,如果是P操作,获取不了的时候
                            且IPC_NOWAIT没有设置的时候,会等待。

                        int semtimedop(int semid, struct sembuf *sops, size_t nsops,
                                  const struct timespec *timeout);
                            限时等待。
                            第四个参数:
                                timeout:描述“限时” 
                                    如果在这段时间内,没有等到,也返回。
                                    
                                struct timespec {
                                    long    tv_sec;         /* seconds */
                                        //秒数
                                        
                                    long    tv_nsec;        /* nanoseconds */
                                        //纳秒      
                                };

                                struct timespec tv;
                                tv.tv_sec = 5;
                                tv.tv_nsec = 0;
                                
            4.2)POSIX信号量--->单个信号量,不像System V的信号量集
                POSIX信号量原理和System V信号量是一样的。
                POSIX信号量分为两种:
                    1.    有名信号量
                        在文件系统中有一个名字,即有一个对应的inode结点,但是信号量的
                        文件内容(整数值)却是在内核中的。
                        一般可以用于任意线程和进程之间。
                        一般有名信号量的操作由:
                            sem_open
                            sem_wait
                            sem_post
                            sem_close
                    
                    2.    无名信号量
                        没有名字,无名信号量存在于内存中
                        一般用于线程间和有亲缘关系的进程间。
                        无名信号量的操作一般有:
                            sem_init
                            sem_wait
                            sem_post
                            sem_destroy

                操作流程:
                    1.    初始化
                    2.    PV操作
                    3.    销毁    

                相关的API函数        
                a.    创建或打开一个POSIX信号量
                    有名信号量:
                    NAME
                        sem_open - initialize and open a named semaphore

                    SYNOPSIS
                        #include <fcntl.h>           /* For O_* constants */
                        #include <sys/stat.h>        /* For mode constants */
                        #include <semaphore.h>

                        sem_t *sem_open(const char *name, int oflag);
                        sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

                        Link with -pthread.

                        sem_t *sem_open(const char *name, int oflag);
                        name : 要创建或打开的POSIX有名信号量在文件系统中的路径名。
                            要求以“/”开头的路径名(路径名中只能有一个/)
                                如:
                                    "/test.sem"
                                也就是说你的有名信号量必须放在根目录下。
                        
    
                        oflag: (1)    打开 0
                                (2) 创建:O_CREAT
                                如果你是打开一个有名信号量的话,则只需要两个参数
                                如果你是创建一个有名信号量的话,则需要四个参数
                                
                        mode:    创建的权限位,有两种方式指定:
                                (1)    S_IRUSR....
                                (2) 0664...
                            
                        value: 指定创建的有名信号量的初始值
                            
                        返回值:成功返回一个sem_t的指针,指向POSIX有名信号量
                                失败返回一个SEM_FAILED,同时errno被设置。
                            
                    无名信号量:
                    NAME
                        sem_init - initialize an unnamed semaphore

                    SYNOPSIS
                        #include <semaphore.h>

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

                        Link with -pthread.
                        
                        sem:指向要初始化的无名信号量
                            一般我们会先定义或分配一个无名信号量sem_t
                                sem_t *psem = malloc(sizeof(sem_t));

                        pshared:
                            该无名信号量的共享方式
                            0:进程内部的线程共享
                                也就是说sem指向的地址是进程内部的地址
                                如malloc开辟出来的内存
                            
                            1:不同进程间共享
                                也就是说sem指向的地址是内核共享内存区域
                            一般设置为0。    
                                                                
                        value:指定无名信号量的初始值
            
                        一般情况下,无名信号量我们用于线程间或有亲缘关系的进程间。
            
            b.    POSIX信号量PV操作
                P操作 
                NAME
                    sem_wait, sem_timedwait, sem_trywait - lock a semaphore

                SYNOPSIS
                    #include <semaphore.h>

                    int sem_wait(sem_t *sem);
                        死等-->此函数会阻塞等待
                        返回值:返回0获取了该信号量,返回-1,出错了,同时errno被设置。
                    
                    int sem_trywait(sem_t *sem);
                        非阻塞版本--->能获取则获取,不能获取则走人(返回-1)
                        返回值:
                            返回0表示获取了该信号量,返回-1,出错,同时errbo被设置。

                    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
                        限时等待--->等一段时间后还是获取不了的话,则返回-1
                            struct timespec {
                                long    tv_sec;         /* seconds */
                                    //秒数
                                    
                                long    tv_nsec;        /* nanoseconds */
                                    //纳秒      
                            };
                        abs_timeout:指定等待超时的绝对时间。
                            绝对时间:
                            相对时间:
                            
                        当前的时间获取:
                            int clock_gettime(clockid_t clk_id, struct timespec *tp);
                                例子:    
                                    //获取当前时间保存起来
                                    struct timespec ts;
                                    clock_gettime(CLOCK_REALTIME,&ts);
                                    
                                    //假设我现在想等待15s:30ms
                                    ts.tv_sec += 15;        //秒钟数加15    
                                    ts.tv_nsec += 30000000;    //纳秒数加30000000即30ms
                                    if(ts.tv_nsec >= 1000000000)//纳秒有可能会超过1s
                                    {
                                        ts.tv_sec += 1;
                                        ts.tv_nsec -= 1000000000;
                                    }
                                    int ret = sem_timedwait(sem,&ts);

                    Link with -pthread.
            
                V操作
                NAME
                    sem_post - unlock a semaphore

                SYNOPSIS
                    #include <semaphore.h>

                    int sem_post(sem_t *sem);

                    Link with -pthread.
                    sem_post用来释放sem指定的POSIX信号量。
            
            c.    POSIX信号量的关闭和删除操作
                有名信号量的关闭操作:
                    sem_close:用来关闭一个POSIX有名信号量
                    sem_unlink:用来删除一个POSIX有名信号量
                    
                    NAME
                        sem_close - close a named semaphore

                    SYNOPSIS
                        #include <semaphore.h>

                        int sem_close(sem_t *sem);

                        Link with -pthread.

                    NAME
                        sem_unlink - remove a named semaphore

                    SYNOPSIS
                        #include <semaphore.h>

                        int sem_unlink(const char *name);

                        Link with -pthread.

                        name:要删除的POSIX有名信号量的路径名
                        返回值:成功返回0,失败返回-1,同时errno被设置。
                    
                无名信号量的销毁操作:    
                    NAME
                        sem_destroy - destroy an unnamed semaphore

                    SYNOPSIS
                        #include <semaphore.h>

                        int sem_destroy(sem_t *sem);

                        Link with -pthread.
                    
                        
            NAME
                sem_getvalue - get the value of a semaphore

            SYNOPSIS
                #include <semaphore.h>

                int sem_getvalue(sem_t *sem, int *sval);

                Link with -pthread.
                
                sem :sem_t指针,表示要获取哪个信号量的值
                sval:int*,用来保存获取到的信号量的值
                返回值:成功返回0,失败返回-1,同时errno被设置。
                    
        信号量一般情况下用来保护共享资源,但是信号量可以有其他的意外作用    
            如:我们fork一个子进程,想让子进程先执行。
            
                    
        作业:
            使用信号量和共享内存,实现文件的传输
            

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值