进程线程

来源:互联网 发布:苏州梯田软件 编辑:程序博客网 时间:2024/06/07 11:21
进程
一 概念
    1 定义
          进程是一个独立的可调度的任务
          进程是一个程序执行一次的过程
          进程是程序执行和资源管理的最小单位
    2 与程序区别
        1 程序是一个可执行的二进制文件,静态
        2 进程是一个程序执行过程,动态
    3 进程部分数据从程序中来,比如代码段、用户数据段。但是进程中的堆、栈、pc计数器确实程序所没有的。
        进程包含三个段:数据段   存放的是全局变量,常数以及动态数据分配的数据空间。
                                    正文段   放的是程序中的代码段
                                    堆栈段   放的是函数的返回地址,函数的参数以及程序中的局部变量。
    4 使用进程号标识一个进程,进程号是一个进程的唯一标识,操作系统通过此标识找到进程,getpid获取当前进程ID, getppid获取当前进程的父进程ID
        pstree查看进程树,注意init进程是所有进程的祖先进程。init进程号为1    pid_t getpid(void);
       pid_t getppid(void);
    5 进程类型:交互进程。批处理进程;重点守护进程
    6 运行状态:运行态、等待态(可中断,不可中断)、停止态(strl+z)、停止态或僵尸态
    7 进程的执行模式
        用户模式
        内核模式
    8 常用命令top ps kill

       进程具有并发性,动态性,交互性和独立性。
       并发性:指的是系统中多个进程可以同事并发执行,相互之间不受干扰。
       动态性:指的是进程都有完整的声明周期,而且在进程周期内,进程的状态是不断变化的,另外进程具有动态的地址空间(代码,数据和进程控制块)。
       交互性:指的是进程在执行过程中可能会与其他进程发生直接或间接的通信,如进程同步和互斥,需要添加一定的进程处理机制。
       独立性:指的是进程是一个相对完整的资源分配和调度的基本单位,各个进程的地址空间是相互独立的,只有采用某些特定的通信机制才能实现进程间的通信。

       Linux系统中主要包括以下几种类型的进程
       (1)交互式进程:这类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入之后,
          这类进程能够立刻响应,典型的交互式进程有shell命令进程,文本编辑器和图形应用程序运行等。
        (2)批处理进程:这类进程不必与用户进行交互,因此通常在后台运行,因为这类进程通常不必很快的响应,
            因此往往不会优先调度,典型的批处理进程是编译器的编译操作,数据库搜索引擎。
        (3)守护进程:这类进程一直在后台运行,和任何终端都不关联,通常启动系统时开始执行,系统关闭时才结束,
            很多系统进程都是以守护进程的形式存在。

        Linux下的进程结构
        进程不但包括程序的指令和数据,而且包括程序计数器和处理器的所有寄存器以及存储临时数据的进程堆栈。
        内核将所有进程存放在双向循环链表中,链表的每一项都是task_struct,称为进程控制块的结构,
        该结构包含了一个与进程相关的所有信息,他能完整的描述一个进程,如进程的状态,进程的基本信息,
        进程标识符,内存相关信息,父进程相关信息,与进程相关的终端信息,当前工作目录,打开的文件信息,所接收的信号信息

        进程状态:   1.运行状态
                 2.可中断的阻塞状态
                 3.不可中断的阻塞状态
                 4.暂停状态
                 5.僵死状态
                 6.消亡状态

        进程标识符:Linux内核通过唯一的进程标识符PID来标识每个进程。

        Linux进程的创建:1.fork()通过复制当前进程创建一个子进程,子进程与父进程的区别仅仅在于不同的PID,
                    PPID和某些资源及统计量。exec函数族负责读取可执行文件并将其载入地址空间开始运行。

        进程的内存结构:Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的地址空间,
        该空间是大小为4GB的线性虚拟空间,用户所看到的和接触到的都是该虚拟地址,
        无法看到实际的物理内存地址,利用这种虚拟地址不但更安全,而且用户程序可以使用比实际物理
        内存更大的地址空间。它被分为两部分-用户空间与内核空间,用户空间地址是0-3GB,
        内核空间地址是3-4GB,用户进程通常情况下只能访问用户空间的虚拟地址,
        不能访问内核空间虚拟地址,只有用户进程使用系统调用时才可以访问到内核空间,
        每当进程切换,用户空间就会跟着变化,二内核空间由内核负责映射,他不会跟着进程改变,
        是固定的。每个进程的用户空间是完全独立的,互不相干的。

        用户空间包括以下几个功能区:
        1.只读段。包含程序代码,和只读数据。
        2.数据段。存放的是全局变量和静态变量,其中可读可写数据段存放已初始化的全局变量和静态变量。
         BSS数据段存放未初始化的全局变量和静态变量。
        3.栈。由系统自动分配释放,存放函数的参数值,局部变量的值,返回地址等。
        4.堆。存放动态分配的数据,一般由程序员动态分配和释放,若程序员不释放,程序结束时可能由操作系统回收。
        5.共享的内存映射区域。这是Linux动态连接器和其他共享库代码的映射区域。

二 系统调用
    1 fork

            fork()函数用于从已存在的进程中创建一个新进程,新进程称为子进程,而原进程称为父进程。
        1 fork函数,创建一个子进程,子进程拷贝父进程数据
          两个进程在调度时先后顺序不确定
        2 重点:返回值,子进程拷贝了父进程数据,在执行时从fork调用后开始执行,子进程fork返回值为0
            父进程中fork返回子进程pid
        exec函数  : 提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,
                 并用他来取代原调用进程的数据段,代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其余全被替换了。
                 可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
        何时调用exec函数
                  1.当进程认为自己不能为系统和用户做出任何贡献时就可以调用exec函数。
                  2.如果一个进程想执行另一个程序,那么它就可以调用fork()函数新建一个进程,
                   然后调用exec函数族中的任意一个函数,这样看起来就像通过执行应用程序而产生一个新进程。
    2 vfork 
        创建进程同fork,但只创建进程不拷贝父进程数据,需要改变父进程数据时才拷贝(何时拷贝),写时拷贝
    3 exec函数族
            一共6个函数,但目的只有一个:执行一个可执行文件,并传递参数,参数传递方式有两种:
            一种是逐个列举方式,另一种是将所有的参数通过指针数组传递。
            参数分为命令行参数和环境变量
        命令行参数传递方式
            1 l(list),以逗号分隔,一个一个列出命令行参数,例如 “aaa”,"bbb","ccc",NULL
            2 v(vector),字符串数组,以NULL结尾
        环境变量传递
            v只有一种方式,字符串数组,以NULL结尾。
        exec函数族中,l--list  v--vector  e--环境变量  p--PATH环境变量中查找可执行程序,只给出文件名即可
    4 exit和_exit,入参进程退出状态,int类型  
       void(int status)(status是一个整形的参数,可以利用这个参数传递进程结束时的状态,
                        一般来说,0表示正常结束,其他的值表示非正常结束)
        注意exit退出时清理IO缓冲区(输出缓冲区的内容),_exit退出时不清理IO缓冲区(不输出缓冲区内容)
    5 wait和waitpid
           wait用于等待子进程退出并获取状态,如子进程未退出,则阻塞在wait处
           waitpid用于等待指定的子进程退出,并获取状态,退出状态需要有exit或_exit指定
           当设置为WNOHANG时,不阻塞,如果子进程未退出,则返回为0,子进程退出返回子进程ID
           退出状态需要由WEXITSTATUS获取。
三 孤儿进程和僵尸进程
    孤儿进程:父进程先于子进程结束,此时子进程的父进程为init进程,孤儿进程退出有init进程处理,无危害
    僵尸进程:父进程未处理子进程退出状态,此时子进程所占用的空间已经释放但仍占用一个tesk_struct结构体
            僵尸进程在开发过程中一定要避免,因为task_struct也会占用一定的系统资源。
            避免方式 1 可以使用wait或waitpid方式避免
                      2 忽略子进程退出信号 signal(SIGCHLD, SIG_IGN)

实现shell命令
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>

  5. #define BUF_SIZE 100
  6. #define ARR_SIZE 10

  7. enum ERROR_TYPE
  8. {
  9.     ERROR = -1,
  10.     OK
  11. };

  12. int FuncArr(char *p, char *pArr[])
  13. {
  14.     if (NULL == p || NULL == pArr)
  15.     {
  16.         return ERROR;
  17.     }    
  18.     *pArr = p;
  19.     pArr++;
  20.     p++;
  21.     while(*p)
  22.     {
  23.         if (' ' == *(p-1) && '\0' != *p)
  24.         {
  25.             *pArr = p;
  26.             *(p-1) = '\0';
  27.             pArr++;
  28.         }
  29.         p++;
  30.     }
  31.     return OK;
  32. }

  33. int main()
  34. {
  35.     char buf[BUF_SIZE] = {0};
  36.     pid_t pid = -1;
  37.     char *strArr[ARR_SIZE] = {NULL};
  38.     while(1)
  39.     {
  40.         memset(buf, 0, BUF_SIZE);
  41.         memset(strArr, 0, sizeof(strArr));
  42.         gets(buf);
  43.         if (OK != FuncArr(buf, strArr))
  44.         {
  45.             continue;
  46.         }
  47.         pid = fork();
  48.         if (-1 == pid)
  49.         {
  50.             perror("fork");
  51.             return ERROR;
  52.         }
  53.         else if (0 == pid)
  54.         {
  55.             if (-1 == execvp(strArr[0], strArr))
  56.             {
  57.                 perror("execvp");
  58.             }
  59.             exit(1);
  60.         }
  61.         else
  62.         {
  63.             wait(NULL);
  64.         }
  65.     }
  66. }

  1. #include <stdio.h>
  2. #include <pthread.h>

  3. void * ThreadFunc(void *arg)
  4. {
  5.     printf("hello\r\n");
  6.     //pthread_exit("aaaaaa ddddd");
  7. }

  8. int main()
  9. {
  10.     pthread_t tId;

  11.     if (0 != pthread_create(&tId, NULL, ThreadFunc, NULL))
  12.     {
  13.         printf("create thread error\r\n");
  14.         return -1;
  15.     }
  16.     //sleep(1);   如果没有sleep函数,线程共享的地址被释放,就不会打印结果。
  17.     //char *pTmp = NULL;
  18.     //pthread_join(tId, (void **)&pTmp);
  19.     //printf("%s\r\n", pTmp);
  20.     pthread_join(tId, NULL);
  21.     return 0;
  22. }

Linux守护进程

是Linux中的后台服务进程。他是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。
守护进程在系统启动时开始运行,系统关闭时终止。
在Linux中,每一个系统与用户进行交流的界面称为终端。
守护进程编写步骤
  1. 创建子进程,父进程退出
  2. 在子进程中创建新会话(这是最重要的一步)
    1. 了解setsid()函数之前要了解两个概念:进程组和会话期。进程组是一个或多个进程的集合,
    2. 进程组由进程组ID来唯一标识,除了进程号之外,进程组ID 也是进程的一个必备属性。
    3. 每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID,且进程组ID不会因组长进程的退出而受到影响。
    4. 会话期:会话期是一个或多个进程的集合。通常,一个会话开始于用户登录,终止于用户退出,或者开始于终端打开,结束于终端关闭。
    5. setsid()函数用于创建一个新的会话,并担任该会话组的组长,调用setsid()函数有下面的三个作用:
      1. 让进程摆脱原会话的控制。
      2. 让进程摆脱原进程组的控制。
      3. 让进程摆脱原控制终端的控制。
  3. 改变当前目录为根目录
  4. 重设文件权限掩码
  5. 关闭文件描述符

  6. #include <stdio.h>
  7. #include <unistd.h>
  8. #include <stdlib.h>
  9. #include <sys/types.h>
  10. #include <sys/stat.h>

  11. int main()
  12. {
  13.     pid_t pid = -1;
  14.     pid = fork();
  15.     if (-1 == pid)
  16.     {
  17.         printf("fork error\r\n");
  18.         return -1;
  19.     }
  20.     if (pid > 0)  //父进程退出
  21.     {
  22.         exit(0);
  23.     }
  24. //create session
  25.     setsid();
  26.     chdir("/");   改变当前目录
  27.     umask(0);   重设文件权限掩码

  28.     int iFdSize = getdtablesize();
  29.     int fd = 0;
  30.     for (; ifd< iFdSize; fd++)   关闭文件描述符
  31.     {
  32.         close(fd);
  33.     }
  34.     sleep(10);
  35.     return 0;
  36. }

线程指的是共享相同地址空间的多个任务。
使用线程的好处:大大提高了任务切换的效率。   
避免了额外的TLB&cache刷新
线程通过第三方的线程库来实现
一个进程中的线程共享以下资源
  1. 可执行的命令
  2. 静态数据
  3. 进程中打开的文件描述符
  4. 信号处理函数
  5. 当前工作目录
  6. 用户ID
  7. 用户组ID

每个线程的私有资源
  1. 线程ID
  2. PC 计数器和相关寄存器
  3. 堆栈
    1. 局部变量
    2. 返回地址
  4. 错误号
  5. 信号掩码和优先级
  6. 执行状态和属性

函数原型  int pthread_creat(pthread_t *thread, const pthread_attr_t *arr, void *(*routine(void *), void *arg)
thread 创建的线程
attr  指定线程的属性
routine  线程执行的函数
arg  传递给线程执行函数的参数
返回值  成功返回 0
出错返回 -1


int pthread_join(pthread_t thread, void **value_ptr)
thread : 要等待的线程
value_ptr  指针*value指向线程返回的函数
成功  0
出错 -1

  1. int pthread_exit(void *value_ptr)
  2. value_ptr 线程退出时返回的值

  3. int pthread_cancel(pthread_t thead)
  4. thread: 要取消的线程

  5. 成功  1
  6. 错误  -1

  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <stdlib.h>

  4. void * ThreadFunc(void *arg)
  5. {
  6.     //int *p = (int *)arg;
  7.     //int b = *((int *)arg);
  8.     int *p = malloc(sizeof(int));
  9.     *p = *((int *)arg);
  10.     printf("hello %d\r\n", *p);
  11. }

  12. int main()
  13. {
  14.     pthread_t tId;
  15.     int a = 10;    
  16.     if (0 != pthread_create(&tId, NULL, ThreadFunc, (void *)&a))
  17.     {
  18.         printf("create thread error\r\n");
  19.         return -1;
  20.     }
  21.     pthread_join(tId, NULL);
  22.     return 0;
  23. }

  1. #include <stdio.h>
  2. #include <pthread.h>

  3. void * ThreadFunc(void *arg)
  4. {
  5.     printf("hello\r\n");
  6.     //pthread_exit("aaaaaa ddddd");
  7. }

  8. int main()
  9. {
  10.     pthread_t tId;

  11.     if (0 != pthread_create(&tId, NULL, ThreadFunc, NULL))
  12.     {
  13.         printf("create thread error\r\n");
  14.         return -1;
  15.     }
  16.     //sleep(1);
  17.     //char *pTmp = NULL;
  18.     //pthread_join(tId, (void **)&pTmp);
  19.     //printf("%s\r\n", pTmp);
  20.     pthread_join(tId, NULL);
  21.     return 0;
  22. }
一守护进程
    1 守护进程通常用于提供某些服务,例如mysql数据库服务器提供的数据服务
    2 创建步骤
        1 创建子进程,父进程退出,子进程成为孤儿进程
        2 setsid 产生一个新会话,创建一个进程组,进程的组长进程是调用setsid的进程
        *此时以脱离终端,不会因为终端的关闭而影响到守护进程,已经是一个守护进程。
        通常还需要以下几步操作(可选)
        (进程组是一个或多个进程的集合,进程组由进程ID唯一标识。       每个进程组都有一个组长进程,
            进程组ID就是组长进程的进程号            会话组是一个或多个进程的集合)
        3 改变工作目录 chdir("/tmp") 或根目录
        4 文件掩码 umask(0)
        5 关闭文件描述符 getdtablesize() 返回一个进程能打开的文件描述符最大值
二 线程
    1 概念:linux使用task_struct来描述一个线程,这与进程相同,线程和进程都参与系统调度  -- 多任务
        但是线程共享进程的空间 -- 共享地址空间
        总结 : 线程指的就是共享地址空间的多任务
    2 创建线程 pthread_create 
            第一个入参为要创建的线程
            第二个入参为线程属性 通常使用NULL
            第三个入参为线程处理函数,注意函数类型
            第四个入参为线程处理函数入参,入参为void*类型,在线程函数中使用时,
                需要做对应的转换和数据拷贝工作,其目的是main函数(主线程)和线程函数互不影响。
                拷贝的数据需要考虑释放 *******
      
    3 等待线程结束和线程信息返回
        pthread_join
            参数1 等待的线程
            参数2 线程的结束信息,注意参数为void *类型,意味着可以指向任意类型的一块内存,也就是说与
                    创建线程时候入参一样可以是任意类型。
        ptherad_exit
            参数1 void *类型,其返回数据由pthread_join获取
    4 可重入函数和线程安全函数 
        从数据独立性角度考虑,当线程函数中存在全局变量或静态变量时入不加锁控制,
        则在多线程访问时会引起数据错乱,此时线程函数为不可重入函数,
        使用malloc或局部变量保证数据独立可以使函数变成可重入函数。
四 互斥
    1 目的是保存临界资源,资源有可能是一段代码的连续执行,也有可能是全局变量,
        但互斥的目的是保证同一时刻只有且只能有一个操作在修改临界资源
    2 线程互斥锁
        pthread_mutex_init 初始化锁
        pthread_mutes_lock  加锁
        pthread_mutex_unlock 解锁
五 同步:强调的是顺序
    1 信号量 P V 操作
        P 信号量值减1 , V 信号量值加一
        在sem_wait未申请到信号量资源时,阻塞,一直等到申请到信号量资源
    2 相关函数
        sem_init    初始化信号量
            参数1 信号量
            参数2 0:用于线程,1 用于进程
            参数3 信号量初始值
        sem_wait    p操作
        sem_post    v操作

*******注意在sem_wait申请资源后之后,sem_post释放资源之前存在return语句情况下需要在return前释放资源
*******注意在加锁后解锁前,有return语句情况下,需要在return之前解锁


  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <pthread.h>

  5. void *Func(void *arg)
  6. {
  7.     char *pTmp = (char *)arg;
  8. #ifndef MALLOC_MEMORY
  9.     static char s_arr[11] = {0};
  10. #else
  11.     printf("malloc\r\n");
  12.     char *s_arr = (char *) malloc (11);
  13.     if (NULL == s_arr)
  14.     {
  15.         return (void *)NULL;
  16.     }
  17.     memset(s_arr, 0, 11);
  18. #endif
  19.     int i = 0;
  20.     for(; i < 10; i++)
  21.     {
  22.         s_arr[i] = pTmp[i];
  23.         //usleep(2000);
  24.         //sleep(1);
  25.     }
  26.     printf("%s\r\n", s_arr);
  27. #ifdef MALLOC_MEMORY
  28.     free(s_arr);
  29.     s_arr = NULL;
  30. #endif
  31. }

  32. int main()
  33. {
  34.     pthread_t tID1;
  35.     pthread_t tID2;
  36.     char arr1[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
  37.     char arr2[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};

  38.     if (0 != pthread_create(&tID1, NULL, Func, (void *)arr1))
  39.     {
  40.         return -1;
  41.     }
  42.     if (0 != pthread_create(&tID2, NULL, Func, (void *)arr2))
  43.     {
  44.         return -1;
  45.     }

  46.     pthread_join(tID1, NULL);
  47.     pthread_join(tID2, NULL);
  48.     return 0;
  49. }
线程锁
引入互斥锁的目的是用来保证共享数据的操作完整性,主要用来保护临界资源
  1. #include <stdio.h>
  2. #include <pthread.h>

  3. pthread_mutex_t  g_mutex;

  4. void *Func(void *arg)
  5. {
  6.     pthread_mutex_lock(&g_mutex);

  7.     char *pTmp = (char *)arg;
  8.     if (NULL == pTmp)
  9.     {
  10.         pthread_mutex_unlock(&g_mutex);
  11.         return (void *)NULL;
  12.     }
  13.     static char s_arr[11] = {0};
  14.     int i = 0;
  15.     for(; i < 10; i++)
  16.     {
  17.         s_arr[i] = pTmp[i];
  18.         //usleep(2000);
  19.         sleep(1);
  20.     }
  21.     printf("%s\r\n", s_arr);

  22.     pthread_mutex_unlock(&g_mutex);
  23. }

  24. int main()
  25. {
  26.     pthread_t tID1;
  27.     pthread_t tID2;
  28.     char arr1[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
  29.     char arr2[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};

  30.     if (0 != pthread_mutex_init(&g_mutex, NULL))
  31.     {
  32.         return -1;
  33.     }

  34.     if (0 != pthread_create(&tID1, NULL, Func, (void *)arr1))
  35.     {
  36.         return -1;
  37.     }
  38.     if (0 != pthread_create(&tID2, NULL, Func, (void *)arr2))
  39.     {
  40.         return -1;
  41.     }

  42.     pthread_join(tID1, NULL);
  43.     pthread_join(tID2, NULL);
  44.     return 0;
  45. }

线程间同步
多线程共享同一个进程的地址空间
优点 : 线程间很容易通信,通过全局变量实现数据共享和交换
缺点 : 多个线程同时访问共享对象时需要引入同步和互斥机制
同步是指多个任务按照规定的顺序相互配合完成一件事情
信号量代表某一类资源,其值表示系统中该资源的数量,他是一个受保护的变量,只能通过三种操作来访问
  1. 初始化
  2. P操作(申请资源)
  3. V操作(释放资源)
posix 中定义了两类信号量

  1. 无名信号量
  2. 有名信号量    
    
常用函数

int sem_init(sem_t *sem,int pshared,unsigned int value);
sem:初始化的信号量
pshared:信号量共享的范围(0是线程间使用  非0是进程间使用)
value  : 信号量初值
成功返回:0
失败返回:-1

int sem_wait(sem_t *sem);  //p操作
int sem_post(sem_t *sem);  //V操作


  1. #include <stdio.h>
  2. #include <pthread.h>
  3. #include <semaphore.h>

  4. sem_t g_sem1;
  5. sem_t g_sem2;

  6. void *Func1(void *arg)
  7. {
  8.     int i = 10;
  9.     while(i--)
  10.     {
  11.         sem_wait(&g_sem1);
  12.         printf("hello\r\n");
  13.         sleep(1);
  14.         sem_post(&g_sem2);
  15.     }
  16. }

  17. void *Func2(void *arg)
  18. {
  19.     int i = 10;
  20.     while(i--)
  21.     {
  22.         sem_wait(&g_sem2);
  23.         printf("world\r\n");
  24.         sleep(1);
  25.         sem_post(&g_sem1);

  26.     }
  27. }

  28. int main()
  29. {
  30.     pthread_t tId1;
  31.     pthread_t tId2;

  32.     if (0 != sem_init(&g_sem1, 0, 1) || 0 != sem_init(&g_sem2, 0, 0))
  33.     {
  34.         return -1;
  35.     }

  36.     if (0 != pthread_create(&tId1, NULL, Func1, NULL))
  37.     {
  38.         return -1;
  39.     }
  40.     if (0 != pthread_create(&tId2, NULL, Func2, NULL))
  41.     {
  42.         return -1;
  43.     }
  44.     pthread_join(tId1, NULL);
  45.     pthread_join(tId2, NULL);
  46.     return 0;
  47. }




  1. #include <sys/types.h>

  2. #include <sys/wait.h>

  3. #include <stdio.h>

  4. #include <unistd.h>

  5. #include <stdlib.h>


  6. int main()
  7. {
  8.     pid_t pid, ret;

  9.     if ((pid == fork()) < 0)
  10.     {
  11.         printf("error fork\r\n");

  12.         return -1;
  13.     }
  14.     else if (pid == 0)  //子进程
  15.     {
  16.         //子进程暂停5秒
  17.         sleep(5);
  18.         //子进程正常退出
  19.         exit(0);
  20.     }
  21.     else //父进程
  22.     {
  23.         //循环测试子进程是否退出
  24.         do
  25.         {
  26.             //调用waitpid ,且父进程不阻塞
  27.             //ret = waitpid(pid,NULL,WNOHANG);  //WNOHANG:若指定的子进程没有结束,则waitpid不阻塞而立即返回,此时返回值为零 
  28.             //如果改为ret = waitpid(pid, NULL, 0);父进程会一直阻塞
  29.             ret = waitpid(pid, NULL, 0);
  30.             //若子进程未退出,则父进程暂停1s

  31.             //若子进程退出,waitpid返回子进程号,若没有子进程退出,waitpid返回0
  32.             if (ret ==  0)
  33.             {
  34.                 printf("The child process has not exited,pid =  %d\n", getpid());

  35.                 sleep(1);
  36.             }
  37.         } while (ret == 0);

  38.         //若发现子进程退出, 打印出相应信息
  39.         if (pid == ret)
  40.         {
  41.             printf("child process exited,pid = %d\n", getpid());
  42.         }
  43.         else
  44.         {
  45.             printf("some error occured.\n");
  46.         }
  47.     }
  48. }

一、进程间的通信方式

# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。

进程的亲缘关系通常是指父子进程关系。
# 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。

它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。

因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。

消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,是最有效的进程间通信方式。

这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,

它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,

如信号两,配合使用,来实现进程间的同步和通信。
# 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,

它可用于不同及其间的进程通信。

pipe

管道是基于文件描述符的通信方式。当一个管道建立时,他会创建两个文件描述符fd[0]和fd[1].    其中fd【0】用于读管道,而fd[1]用于写管道。
管道包括有名管道和无名管道
无名管道只能用于具有亲缘关系的进程之间的通信(父子之间)
是一个单工的通信模式,具有固定的读端和写端
管道可以看成是一种特殊的文件,对于他的读写也可以使用普通的read,write函数
他不属于任何文件系统, 并且只存在于内存中。

有名管道
可以使互不相关的两个进程实现彼此通信
该管道可以通过路径名来指出, 并且在文件系统中是可见的,在建立了管道之后,两个进程就可以把它当做普通文件一样进行读写。
FIFO严格的遵守先进先出规则,不支持如lssek()操作
管道
    通讯领域概念
    半双工:传输方向双向,但同一时刻只有一个方向
    双工:传输方向双向,可以双向同时传输
    单工:传输方向单向

    无名管道
        具有亲缘关系的进程
        读写端固定,fd[0] 读, fd[1] 写
        pipe   read write close
            入参 一个2个成员的描述符数组
            返回值 0成功, -1失败
        半双工,但经常安装单工使用,原因:两个进程之间很难保证在同一时刻或者读或者写。
    有名管道
        单工
        无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围
        有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
        进程通过文件IO来操作有名管道
        有名管道遵循先进先出规则
        不支持如lseek() 操作
        向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。
            如果读进程不读走管道缓冲区中的数据,那么写操作将会一直阻塞。
        有名管道文件类型 p
        mkfifo  open write read close
            参数1 创建的fifo文件名
            参数2 创建fifo文件的权限 , 例如,0666 ,创建后是0664   0666 &(~umask)
        ***************注意:读端不存在时管道无意义。

信号

    1.信号是软件层次上对中断机制的一种模拟,在原理上,一个信号收到一个信号与处理器收到一个中断请求可以说是一样的。
       信号是异步的,一个进程不必通过任何操作来等待信号的到来,进程也不知道信号到底什么时候到达。
       信号可以直接进行用户空间进程和内核进程的交互,内核进程也可以利用它来通知用户空间发生了哪些系统事件。
    2.信号是进程间通信机制中唯一的异步通信方式。
    3.信号的产生有硬件来源(比如按下了键盘和其他硬件故障)和软件来源(一些函数kill,raise,alarm,非法运算等等)。
    1 执行过程  信号的默认处理函数
    int kill(pid_t pid,int sig)
    pid : 要接收信号的进程的进程号
    0 : 信号被发送到所有和pid进程在同一个进程组的进程
    -1 :  信号发给所有的进程表中的进程(处了进程号最大的进程外)
    2 kill 
        参数1 进程ID
        参数2 信号值
        raise 给自己发信号
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <signal.h>

  6. int main()
  7. {
  8.     pid_t pid;

  9.     int ret;

  10.     //创建一子进程
  11.     if ((pid = fork()) < 0)
  12.     {
  13.         printf("Fork error\r\n");

  14.         exit(-1);
  15.     }
  16.     if (pid == 0)
  17.     {
  18.         //在子进程中使用raise()函数发出SIGSTOP信号,使子进程暂停
  19.         printf("child (pid : %d) is waiting for any signal\r\n",getpid());

  20.         raise(SIGSTOP);

  21.         exit(0);
  22.     }
  23.     else
  24.     {
  25.         //在父进程中收集子进程的状态,并调用kill()函数发送信号
  26.         if ((waitpid(pid,NULL,WNOHANG)) == 0)
  27.         {
  28.             kill(pid,SIGKILL);

  29.             printf("parent kill child process %d\r\n",pid);
  30.         }
  31.         waitpid(pid,NULL,0);

  32.         exit(0);
  33.     }
  34. }

    3 alarm 
        定时器,参数 设定多少秒后产生SIGALRM信号,此信号默认处理是结束进程,
        一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替
       pause()函数是用于将调用进程挂起直至接收到信号为止。
       此函数等待一个信号产生后继续执行,否则挂起进程(此时cpu利用率很低,接近于0)
    
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>


  4. int main()
  5. {
  6.     //调用alarm函数
  7.     alarm(5);

  8.     pause();

  9.     printf("I have been waken up.\r\n");//此语句不会被执行
  10. } 

    4 signal
        入参1 信号
        入参2 信号处理函数
        用于修改信号默认处理函数
    5 atoi 数字字符串转成整型
共享内存
    共享的是内核里的内存3-4g
    1 创建 shmget
        入参1 key  通过ftok获取
        入参2 size 共享内存大小,字节为单位
        入参3 shmflg IPC_CREAT | 0666
        返回值 共享内存标识符 int类型
    2 映射 shm_at
        入参1 内存标识符,shmget 返回值
        入参2 指定的映射地址,通常用NULL,使用shmat返回的映射地址
        入参3 0 共享内存可读写
    3 取消映射 shmdt
        入参1 shm_at映射的地址
    4 删除 shmctl
        入参1 内存标识符
        入参2 IPC_RMID
        入参3 IPC_RMID时给NULL
msg_send.c
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <sys/ipc.h>
  6. #include <sys/msg.h>

  7. #define BUF_SIZE 10

  8. struct MyBuf
  9. {
  10.     long m_lType;
  11.     char buf[BUF_SIZE];
  12. };

  13. int main()
  14. {
  15.     key_t key = ftok(".", 15);
  16.     if (-1 == key)
  17.     {
  18.         return -1;
  19.     }
  20.     printf("ftok ok\r\n");
  21.     int iMsgID = msgget(key, IPC_CREAT | 0666);
  22.     if (-1 == iMsgID)
  23.     {
  24.         return -1;
  25.     }
  26.     printf("msgget ok\r\n");
  27.     //send msg
  28.     struct MyBuf stBuf;
  29.     memset(&stBuf, 0, sizeof(struct MyBuf));
  30.     stBuf.m_lType = 6;
  31.     strcpy(stBuf.buf, "ttt");

  32.     int iRet = msgsnd(iMsgID, (void *)&stBuf, BUF_SIZE, 0);
  33.     printf("%d\r\n", iRet); 
  34.     return 0;
  35. }

msg_recive.c
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <sys/ipc.h>
  6. #include <sys/msg.h>

  7. #define BUF_SIZE 10

  8. struct MyBuf
  9. {
  10.     long m_lType;
  11.     char buf[BUF_SIZE];
  12. };

  13. int main()
  14. {
  15.     key_t key = ftok(".", 15);
  16.     if (-1 == key)
  17.     {
  18.         return -1;
  19.     }
  20.     printf("ftok ok\r\n");
  21.     int iMsgID = msgget(key, IPC_CREAT | 0666);
  22.     if (-1 == iMsgID)
  23.     {
  24.         return -1;
  25.     }
  26.     printf("msgget ok\r\n");
  27.     //receive msg
  28.     struct MyBuf stBuf;
  29.     memset(&stBuf, 0, sizeof(struct MyBuf));

  30.     int iRet = msgrcv(iMsgID, (void *)&stBuf, BUF_SIZE,6, 0);
  31.     printf("%d %s\r\n", iRet, stBuf.buf); 
  32.     return 0;
  33. }


  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/shm.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>

  7. #define BUFFER_SIZE 2048

  8. int main()
  9. {
  10.     pid_t pid;

  11.     int shmid;

  12.     char *shm_addr;

  13.     char flag[] = "WROTE";

  14.     char buff[BUFFER_SIZE];

  15.     //创建共享内存
  16.     if ((shmid = shmget(IPC_PRIVATE,BUFFER_SIZE,0666)) < 0)
  17.     {
  18.         perror("shmget\r\n");

  19.         exit(1);
  20.     }
  21.     else
  22.     {
  23.         printf("Create shared-memory:%d\r\n",shmid);
  24.     }
  25.     //显示共享内存情况
  26.     system("ipcs -m");

  27.     pid = fork();

  28.     if (pid == -1)
  29.     {
  30.         perror("fork\r\n");

  31.         exit(1);
  32.     }
  33.     else if (pid == 0)//子进程处理
  34.     {
  35.         //映射共享内存
  36.         if ((shm_addr = shmat(shmid,0,0)) == (void *)-1)
  37.         {
  38.             perror("Child:shmat\r\n");

  39.             exit(1);
  40.         }
  41.         else
  42.         {
  43.             printf("Child : Attach shared-memory: %p\r\n",shm_addr);
  44.         }
  45.         system("ipcs -m");

  46.         //通过检查在共享内存的头部是否标志字符串WROTE来确认父进程已经向共享内存写入有效数据
  47.         while (strncmp(shm_addr,flag,strlen(flag)))
  48.         {
  49.             printf("Child:wait for enable data...\r\n");

  50.             sleep(5);
  51.         }
  52.         strcpy(buff,shm_addr+strlen(flag));

  53.         printf("Child : shared-memory : %s\r\n",buff);

  54.         //解除共享内存映射
  55.         if ((shmdt(shm_addr)) < 0)
  56.         {
  57.             perror("shmdt");

  58.             exit(1);
  59.         }
  60.         else
  61.         {
  62.             printf("Child : Deattach shared-memory\r\n");
  63.         }
  64.         system("ipcs -m");

  65.         //删除共享内存
  66.         if (shmctl(shmid,IPC_RMID,NULL) == -1)
  67.         {
  68.             perror("Child: shmctl(IPC_RMID)\r\n");

  69.             exit(1);
  70.         }
  71.         else
  72.         {
  73.             printf("Delete shared-memory\r\n");
  74.         }
  75.         system("ipcs -m");
  76.     }
  77.     else //父进程处理
  78.     {
  79.         //映射共享内存
  80.         if ((shm_addr = shmat(shmid,0,0)) == (void *)-1)
  81.         {
  82.             perror("Parent : shmat");

  83.             exit(1);
  84.         }
  85.         else
  86.         {
  87.             printf("Parent: Attach shared-memory : %p\r\n",shm_addr);
  88.         }
  89.         sleep(1);

  90.         printf("\nInput some string:\r\n");

  91.         fgets(buff,BUFFER_SIZE,stdin);

  92.         strncpy(shm_addr + strlen(flag),buff,strlen(buff));

  93.         strncpy(shm_addr,flag,strlen(flag));

  94.         //解除共享内存映射
  95.         if ((shmdt(shm_addr)) < 0)
  96.         {
  97.             perror("Parent: shmdt");

  98.             exit(1);
  99.         }
  100.         else
  101.         {
  102.             printf("Parent: Deattach shared-memory\r\n");
  103.         }
  104.         system("ipcs -m");

  105.         waitpid(pid,NULL,0);

  106.         printf("Finished\r\n");
  107.     }
  108.     exit(0);
  109. }

    查看共享内存命令 ipcs

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #include <string.h>
  6. #include <errno.h>

  7. #define MAX_DATA_LEN 256

  8. int main()
  9. {
  10.     pid_t pid;

  11.     int pipe_fd[2];

  12.     char buf[MAX_DATA_LEN];

  13.     const char data[] = "Pipe test program";

  14.     int real_read, real_write;

  15.     memset(buf,0,sizeof(buf));

  16.     if (pipe(pipe_fd) < 0)
  17.     {
  18.         perror("fail to pipe");

  19.         exit(-1);
  20.     }
  21.     if ((pid = fork()) == 0)//创建子进程
  22.     {
  23.         close(pipe_fd[1]);//子进程关闭写描述符,并通过使子进程暂停1s等待父进程关闭相应的读描述符

  24.         sleep(1);

  25.         if ((real_read = read(pipe_fd[0],buf,MAX_DATA_LEN)) > 0)//子进程读取管道内容
  26.         {
  27.             printf("%d byte read from the pipe is '%s'\r\n",real_read,buf);
  28.         }
  29.         close(pipe_fd[0]);//关闭子进程读描述符

  30.         exit(0);
  31.     }
  32.     else if (pid > 0)
  33.     {
  34.         close(pipe_fd[0]);//父进程关闭读描述符

  35.         if ((real_write = write(pipe_fd[1],data,strlen(data))) != -1)
  36.         {
  37.             printf("parent wrote %d bytes:'%s'\r\n",real_write,data);
  38.         }
  39.         close(pipe_fd[1]);//关闭父进程写描述符

  40.         waitpid(pid,NULL,0);//收集子进程退出信息

  41.         exit(0);
  42.     }
信号
信号是在软件层次上对中断机制的一种模拟。
int kill(pid_t pid, int sig);
pid>0时发送信号给进程号为pid的进程
pid = 0:信号被发送到所有和当前进程在同一个进程组的进程
pid = -1 : 信号发给所有进程表中的进程
pid < -1 : 信号发给进程组号为-pid的每一个进程


  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <signal.h>

  4. #include <sys/types.h>

  5. #include <sys/wait.h>


  6. int main()
  7. {
  8.     pid_t pid;

  9.     int ret;

  10.     //创建子进程
  11.     if ((pid = fork()) < 0)
  12.     {
  13.         printf("Fork error\n");

  14.         exit(1);
  15.     }

  16.     if (pid == 0)
  17.     {
  18.         //在子进程中使用raise()函数发出SIGSTOP信号,使子进程暂停
  19.         printf("child(pid : %d) is waiting for any signal\n",getpid());

  20.         raise(SIGSTOP);

  21.         exit(0);
  22.     }
  23.     else
  24.     {
  25.         //在父进程中收集子进程的状态,并调用kill()函数发送信号
  26.         if((waitpid(pid, NULL, WNOHANG)) == 0)
  27.         {
  28.             kill(pid, SIGKILL);

  29.             printf("parent kill child process %d\n",pid);
  30.         }
  31.         waitpid(pid, NULL, 0);

  32.         exit(0);
  33.     }
  34. }

 消息队列
    1 创建 
        msgget
            入参1 key,用ftok创建
            入参2 权限, IPC_CREAT | 0666
            返回值 成功返回 消息队列ID,失败返回-1, 可用ipcs命令查看
    2 发消息
        msgsnd
            入参1 消息队列ID
            入参2 消息结构msgbuf如下:
                    struct msgbuf
                    {
                        long mtype;          //消息类型,必须大于0
                        char mtext[N]};   //消息正文
                    }
            入参3 ***消息正文大小,需要注意这里
            参数3 通常使用0,消息发送完成后返回
            返回值, 0 成功, -1 失败
    3 收消息
        msgrcv
            参数1 消息队列id
            参数2 接受消息的结构体指针,同自定义的struct msgbuf
            参数3 ***消息正文大小
            参数4 消息类型
                0:接收消息队列中第一个消息。
                大于0:接收消息队列中第一个类型为msgtyp的消息.
                小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。
            参数5 
                通常使用0, 若无消息函数会一直阻塞
    4 msgctl
        删除消息队列 msgctl(消息队列ID, IPC_RMID, NULL)
信号量
    1 创建
        semget
            入参1 key 通过ftok产生
            入参2 信号灯集中信号灯的个数
            入参3 权限 IPC_CREAT | 0666
            返回值 成功:信号灯集ID,失败 -1
    2 初始化
        semctl 通过此函数可以设置信号灯集中某个信号的初始值
            参数1 信号灯集ID
            参数2 修改信号灯集中的信号灯编号
            参数3 命令
                    cmd:    GETVAL:获取信号灯的值
                            SETVAL:设置信号灯的值
                            IPC_RMID:从系统中删除信号灯集合
            设置信号灯初值时用到第四个参数
            union semun arg,此结构体在<linux/sem.h>中有定义,<sys/sem.h>中无定义
            参照man semctl函数说明中关于union semun共同体说明



    3 semop  PV操作
        参数1 信号灯集ID
        参数2 struct sembuf
                    struct sembuf {
                        short  sem_num;  //  要操作的信号灯的编号,索引从0开始
                        short  sem_op;   //    0 :  等待,直到信号灯的值变成0
                               //   1  :  释放资源,V操作
                               //   -1 :  分配资源,P操作                    
                        short  sem_flg; // 0,  IPC_NOWAIT,  SEM_UNDO
                    };
        参数3 操作的信号灯个数
    4 删除
        semctl(信号灯集ID, 0, IPC_RMID)

有名信号灯
    sem_open
        参数1 信号灯名
        参数2 O_CREAT 如果不存在则创建,如指定此参数,当有名信号存在情况下忽略参数3和参数4
        参数3 0666 权限
        参数4 0 / 1 初始值
    sem_close
        关闭一个有名信号灯
    编译 需要加 -lpthread 
code
msgrecv.c
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <sys/ipc.h>
  6. #include <sys/msg.h>

  7. #define BUF_SIZE 10

  8. struct MyBuf
  9. {
  10.     long m_lType;
  11.     char buf[BUF_SIZE];
  12. };

  13. int main()
  14. {
  15.     key_t key = ftok(".", 15);
  16.     if (-1 == key)
  17.     {
  18.         return -1;
  19.     }
  20.     printf("ftok ok\r\n");
  21.     int iMsgID = msgget(key, IPC_CREAT | 0666);
  22.     if (-1 == iMsgID)
  23.     {
  24.         return -1;
  25.     }
  26.     printf("msgget ok\r\n");
  27.     //receive msg
  28.     struct MyBuf stBuf;
  29.     memset(&stBuf, 0, sizeof(struct MyBuf));

  30.     int iRet = msgrcv(iMsgID, (void *)&stBuf, BUF_SIZE,6, 0);
  31.     printf("%d %s\r\n", iRet, stBuf.buf); 
  32.     return 0;
  33. }
  1. msg_send
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <sys/ipc.h>
  7. #include <sys/msg.h>

  8. #define BUF_SIZE 10

  9. struct MyBuf
  10. {
  11.     long m_lType;
  12.     char buf[BUF_SIZE];
  13. };

  14. int main()
  15. {
  16.     key_t key = ftok(".", 15);
  17.     if (-1 == key)
  18.     {
  19.         return -1;
  20.     }
  21.     printf("ftok ok\r\n");
  22.     int iMsgID = msgget(key, IPC_CREAT | 0666);
  23.     if (-1 == iMsgID)
  24.     {
  25.         return -1;
  26.     }
  27.     printf("msgget ok\r\n");
  28.     //send msg
  29.     struct MyBuf stBuf;
  30.     memset(&stBuf, 0, sizeof(struct MyBuf));
  31.     stBuf.m_lType = 6;
  32.     strcpy(stBuf.buf, "ttt");

  33.     int iRet = msgsnd(iMsgID, (void *)&stBuf, BUF_SIZE, 0);
  34.     printf("%d\r\n", iRet); 
  35.     return 0;
  36. }

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <linux/ipc.h>
  7. #include <linux/sem.h>

  8. #define BUF_SIZE 10

  9. int main()
  10. {
  11.     key_t key = ftok(".", 16);
  12.     if (-1 == key)
  13.     {
  14.         return -1;
  15.     }
  16.     printf("ftok ok\r\n");

  17.     //create sem
  18.     int iSemID = semget(key, 1, IPC_CREAT | 0666);
  19.     if (-1 == iSemID)
  20.     {
  21.         return -1;
  22.     }
  23.     printf("semget ok\r\n");

  24.     //set value
  25.     union semun stSemInit;
  26.     stSemInit.val = 1;
  27.     int iRet = semctl(iSemID, 0, SETVAL, stSemInit);
  28.     if (-1 == iRet)
  29.     {
  30.         semctl(iSemID, 0, IPC_RMID);
  31.         return -1;
  32.     }
  33.     printf("set val ok\r\n");

  34.     // p v
  35.     pid_t pid = fork();
  36.     if (pid < 0)
  37.     {
  38.         semctl(iSemID, 0, IPC_RMID);
  39.         return -1;
  40.     }
  41.     struct sembuf stBuf;
  42.     if (0 == pid)
  43.     {
  44.         stBuf.sem_num = 0;
  45.         stBuf.sem_op = -1;
  46.         stBuf.sem_flg = SEM_UNDO;
  47.         semop(iSemID, &stBuf, 1);

  48.         printf("hello\r\n");
  49.         sleep(5);

  50.         stBuf.sem_num = 0;
  51.         stBuf.sem_op = 1;
  52.         stBuf.sem_flg = SEM_UNDO;
  53.         semop(iSemID, &stBuf, 1);
  54.         exit(0);
  55.     }
  56.     else
  57.     {
  58.         sleep(1);
  59.         stBuf.sem_num = 0;
  60.         stBuf.sem_op = -1;
  61.         stBuf.sem_flg = SEM_UNDO;
  62.         semop(iSemID, &stBuf, 1);

  63.         printf("world\r\n");

  64.         stBuf.sem_num = 0;
  65.         stBuf.sem_op = 1;
  66.         stBuf.sem_flg = SEM_UNDO;
  67.         semop(iSemID, &stBuf, 1);
  68.         wait(NULL);
  69.         semctl(iSemID, 0, IPC_RMID);
  70.     }
  71.     return 0;
  72. }

sem_a.c
  1. #include <stdio.h>
  2. #include <fcntl.h>           /* For O_* constants */
  3. #include <sys/stat.h>        /* For mode constants */
  4. #include <semaphore.h>

  5. int main()
  6. {
  7.     sem_t *pSem1 = sem_open("aaa", O_CREAT, 0666, 1);
  8.     if (SEM_FAILED == pSem1)
  9.     {
  10.         return -1;
  11.     }
  12.     sem_t *pSem2 = sem_open("bbb", O_CREAT, 0666, 0);
  13.     if (SEM_FAILED == pSem2)
  14.     {
  15.         sem_close(pSem1);
  16.         return -1;
  17.     }
  18.     while(1)
  19.     {
  20.         sem_wait(pSem1);
  21.         printf("hello\r\n");
  22.         sleep(1);
  23.         sem_post(pSem2);
  24.     }
  25. }

sem_b.c
  1. #include <stdio.h>
  2. #include <fcntl.h>           /* For O_* constants */
  3. #include <sys/stat.h>        /* For mode constants */
  4. #include <semaphore.h>

  5. int main()
  6. {
  7.     sem_t *pSem1 = sem_open("aaa", O_CREAT, 0666, 1);
  8.     if (SEM_FAILED == pSem1)
  9.     {
  10.         return -1;
  11.     }
  12.     sem_t *pSem2 = sem_open("bbb", O_CREAT, 0666, 0);
  13.     if (SEM_FAILED == pSem2)
  14.     {
  15.         sem_close(pSem1);
  16.         return -1;
  17.     }
  18.     while(1)
  19.     {
  20.         sem_wait(pSem2);
  21.         printf("world\r\n");
  22.         sleep(1);
  23.         sem_post(pSem1);
  24.     }
  25. }

共享内存函数(shmget、shmat、shmdt、shmctl

 
Linux进程间通信(7)
 

共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。下面的表格列出了这四个函数的函数原型及其具体说明。

1.   shmget函数原型

shmget(得到一个共享内存标识符或创建一个共享内存对象)

所需头文件

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/ipc.h>

函数说明

得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符

函数原型

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

函数传入值

key

0(IPC_PRIVATE):会建立新共享内存对象

大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值

size

大于0的整数:新建的共享内存大小,以字节为单位

0:只获取共享内存时指定为0

shmflg

0:取共享内存标识符,若不存在则函数会报错

IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错

函数返回值

成功:返回共享内存的标识符

出错:-1,错误原因存于error中

附加说明

上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

错误代码

EINVAL:参数size小于SHMMIN或大于SHMMAX

EEXIST:预建立key所指的共享内存,但已经存在

EIDRM:参数key所指的共享内存已经删除

ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL)

ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位

EACCES:没有权限

ENOMEM:核心内存不足

在Linux环境中,对开始申请的共享内存空间进行了初始化,初始值为0x00。

如果用shmget创建了一个新的消息队列对象时,则shmid_ds结构成员变量的值设置如下:

Ÿ        shm_lpidshm_nattachshm_atime、shm_dtime设置为0。

Ÿ        msg_ctime设置为当前时间。

Ÿ        shm_segsz设成创建共享内存的大小。

Ÿ        shmflg的读写权限放在shm_perm.mode中。

Ÿ        shm_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。

2.   shmat函数原型

shmat(把共享内存区对象映射到调用进程的地址空间)

所需头文件

#include <sys/types.h>

#include <sys/shm.h>

#include<sys/ipc.h>

函数说明

连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问

函数原型

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

函数传入值

shmid

共享内存标识符

shmaddr

指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置

shmflg

SHM_RDONLY:为只读模式,其他为读写模式

函数返回值

成功:附加好的共享内存地址

出错:-1,错误原因存于error中

附加说明

fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)

错误代码

EACCES:无权限以指定方式连接共享内存

EINVAL:无效的参数shmid或shmaddr

ENOMEM:核心内存不足

3.   shmdt函数原型

shmat(断开共享内存连接)

所需头文件

#include <sys/types.h>

#include <sys/shm.h>

#includ<sys/ipc.h>

函数说明

与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存

函数原型

int shmdt(const void *shmaddr)

函数传入值

shmaddr:连接的共享内存的起始地址

函数返回值

成功:0

出错:-1,错误原因存于error中

附加说明

本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程

错误代码

EINVAL:无效的参数shmaddr

4.   shmctl函数原型

shmctl(共享内存管理)

所需头文件

#include <sys/types.h>

#include <sys/shm.h>

#include <sys/ipc.h>

函数说明

完成对共享内存的控制

函数原型

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

函数传入值

shmid

共享内存标识符

cmd

IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中

IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内

IPC_RMID:删除这片共享内存

buf

共享内存管理结构体。具体说明参见共享内存内核结构定义部分

函数返回值

成功:0

出错:-1,错误原因存于error中

错误代码

EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存

EFAULT:参数buf指向无效的内存地址

EIDRM:标识符为msqid的共享内存已被删除

EINVAL:无效的参数cmd或shmid

EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行

 

共享内存应用范例

5.   父子进程通信范例

父子进程通信范例,shm.c源代码如下:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. #include <sys/ipc.h>
  5. #include <sys/shm.h>
  6. #include <error.h>
  7. #define SIZE 1024
  8. int main()
  9. {
  10.     int shmid ;
  11.     char *shmaddr ;
  12.     struct shmid_ds buf ;
  13.     int flag = 0 ;
  14.     int pid ;
  15.  
  16.     shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ;
  17.     if ( shmid < 0 )
  18.     {
  19.             perror("get shm  ipc_id error") ;
  20.             return -1 ;
  21.     }
  22.     pid = fork() ;
  23.     if ( pid == 0 )
  24.     {
  25.         shmaddr = (char *)shmat( shmid, NULL, 0 ) ;
  26.         if ( (int)shmaddr == -1 )
  27.         {
  28.             perror("shmat addr error") ;
  29.             return -1 ;
  30.  
  31.         }
  32.         strcpy( shmaddr, "Hi, I am child process!\n") ;
  33.         shmdt( shmaddr ) ;
  34.         return  0;
  35.     } else if ( pid > 0) {
  36.         sleep(3 ) ;
  37.         flag = shmctl( shmid, IPC_STAT, &buf) ;
  38.         if ( flag == -1 )
  39.         {
  40.             perror("shmctl shm error") ;
  41.             return -1 ;
  42.         }
  43.  
  44.         printf("shm_segsz =%d bytes\n", buf.shm_segsz ) ;
  45.         printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ;
  46.         printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ;
  47.         shmaddr = (char *) shmat(shmid, NULL, 0 ) ;
  48.         if ( (int)shmaddr == -1 )
  49.         {
  50.             perror("shmat addr error") ;
  51.             return -1 ;
  52.  
  53.         }
  54.         printf("%s", shmaddr) ;
  55.         shmdt( shmaddr ) ;
  56.         shmctl(shmid, IPC_RMID, NULL) ;
  57.     }else{
  58.         perror("fork error") ;
  59.         shmctl(shmid, IPC_RMID, NULL) ;
  60.     }
  61.  
  62.     return 0 ;
  63. }

编译 gcc shm.c –o shm。

执行 ./shm,执行结果如下:

shm_segsz =1024 bytes

shm_cpid = 9503

shm_lpid = 9504

Hi, I am child process!

6.   多进程读写范例

多进程读写即一个进程写共享内存,一个或多个进程读共享内存。下面的例子实现的是一个进程写共享内存,一个进程读共享内存。

(1)下面程序实现了创建共享内存,并写入消息。

shmwrite.c源代码如下:

  1. #include <stdio.h>
  2. #include <sys/ipc.h>
  3. #include <sys/shm.h>
  4. #include <sys/types.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. typedef struct{
  8.     char name[8];
  9.     int age;
  10. } people;
  11. int main(int argc, char** argv)
  12. {
  13.     int shm_id,i;
  14.     key_t key;
  15.     char temp[8];
  16.     people *p_map;
  17.     char pathname[30] ;
  18.  
  19.     strcpy(pathname,"/tmp") ;
  20.     key = ftok(pathname,0x03);
  21.     if(key==-1)
  22.     {
  23.         perror("ftok error");
  24.         return -1;
  25.     }
  26.     printf("key=%d\n",key) ;
  27.     shm_id=shmget(key,4096,IPC_CREAT|IPC_EXCL|0600); 
  28.     if(shm_id==-1)
  29.     {
  30.         perror("shmget error");
  31.         return -1;
  32.     }
  33.     printf("shm_id=%d\n", shm_id) ;
  34.     p_map=(people*)shmat(shm_id,NULL,0);
  35.     memset(temp, 0x00, sizeof(temp)) ;
  36.     strcpy(temp,"test") ;
  37.     temp[4]='0';
  38.     for(i = 0;i<3;i++)
  39.     {
  40.         temp[4]+=1;
  41.         strncpy((p_map+i)->name,temp,5);
  42.         (p_map+i)->age=0+i;
  43.     }
  44.     shmdt(p_map) ;
  45.     return 0 ;
  46. }

(2)下面程序实现从共享内存读消息。

shmread.c源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/ipc.h>
  4. #include <sys/shm.h>
  5. #include <sys/types.h>
  6. #include <unistd.h>
  7. typedef struct{
  8.     char name[8];
  9.     int age;
  10. } people;
  11. int main(int argc, char** argv)
  12. {
  13.     int shm_id,i;
  14.     key_t key;
  15.     people *p_map;
  16.     char pathname[30] ;
  17.  
  18.     strcpy(pathname,"/tmp") ;
  19.     key = ftok(pathname,0x03);
  20.     if(key == -1)
  21.     {
  22.         perror("ftok error");
  23.         return -1;
  24.     }
  25.     printf("key=%d\n", key) ;
  26.     shm_id = shmget(key,0, 0);   
  27.     if(shm_id == -1)
  28.     {
  29.         perror("shmget error");
  30.         return -1;
  31.     }
  32.     printf("shm_id=%d\n", shm_id) ;
  33.     p_map = (people*)shmat(shm_id,NULL,0);
  34.     for(i = 0;i<3;i++)
  35.     {
  36.         printf( "name:%s\n",(*(p_map+i)).name );
  37.         printf( "age %d\n",(*(p_map+i)).age );
  38.     }
  39.     if(shmdt(p_map) == -1)
  40.     {
  41.         perror("detach error");
  42.         return -1;
  43.     }
  44.     return 0 ;
  45. }

(3)编译与执行

①    编译gcc shmwrite.c -o  shmwrite。

②    执行./shmwrite,执行结果如下:

key=50453281

shm_id=688137

③    编译gcc shmread.c -o shmread。

④    执行./shmread,执行结果如下:

key=50453281

shm_id=688137

name:test1

age 0

name:test2

age 1

name:test3

age 2

⑤    再执行./shmwrite,执行结果如下:

key=50453281

shmget error: File exists

⑥    使用ipcrm -m 688137删除此共享内存。



1. 信号概念 
信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件)。信号是硬件中断的软件模拟(软中断)。每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD、SIGINT等,它们在系统头文件中定义,也可以通过在shell下键入kill –l查看信号列表,或者键入man 7 signal查看更详细的说明。
信号的生成来自内核,让内核生成信号的请求来自3个地方:
l         用户:用户能够通过输入CTRL+c、Ctrl+\,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;
l         内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出等;
l         进程:一个进程可以通过系统调用kill给另一个进程发送信号,一个进程可以通过信号和另外一个进程进行通信。
由进程的某个操作产生的信号称为同步信号(synchronous signals),例如除0;由象用户击键这样的进程外部事件产生的信号叫做异步信号。(asynchronous signals)。
       进程接收到信号以后,可以有如下3种选择进行处理:
l         接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下CTRL+c,将导致内核向进程发送一个SIGINT的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行;
l         忽略信号:进程可以通过代码,显示地忽略某个信号的处理,例如:signal(SIGINT,SIGDEF);但是某些信号是不能被忽略的,
l         捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。
 
有两个信号既不能被忽略也不能被捕捉,它们是SIGKILL和SIGSTOP。即进程接收到这两个信号后,只能接受系统的默认处理,即终止线程。
2. signal信号处理机制 
可以用函数signal注册一个信号捕捉函数。原型为:
#include 
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
 
signal 的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DEF(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回 SIG_ERR。
sighandler_t是信号捕捉函数,由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个参数,表示信号值。
示例:

1、  捕捉终端CTRL+c产生的SIGINT信号:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
 
void SignHandler(int iSignNo)
{
    printf("Capture sign no:%d/n",iSignNo); 
}
 
int main()
{
    signal(SIGINT,SignHandler); 
    while(true) 
        sleep(1); 
    return 0; 
}
该程序运行起来以后,通过按 CTRL+c将不再终止程序的运行。应为CTRL+c产生的SIGINT信号已经由进程中注册的SignHandler函数捕捉了。该程序可以通过 Ctrl+/终止,因为组合键Ctrl+/能够产生SIGQUIT信号,而该信号的捕捉函数尚未在程序中注册。
2、  忽略掉终端CTRL+c产生的SIGINT信号:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
 
int main()
{
    signal(SIGINT,SIG_IGN); 
    while(true) 
        sleep(1); 
    return 0; 
}
该程序运行起来以后,将CTRL+C产生的SIGINT信号忽略掉了,所以CTRL+C将不再能是该进程终止,要终止该进程,可以向进程发送SIGQUIT信号,即组合键CTRL+/
 
3、  接受信号的默认处理,接受默认处理就相当于没有写信号处理程序:
 

  1. #include <unistd.h>
  2. #include <stdio.h>
  3. #include <sys/wait.h>
  4. #include <sys/types.h>
  5.  
  6. int main()
  7. {
  8.     signal(SIGINT,DEF); 
  9.     while(true) 
  10.         sleep(1); 
  11.     return 0; 
  12. }
3. sigaction信号处理机制 
3.1. 信号处理情况分析 
在signal处理机制下,还有许多特殊情况需要考虑:
1、  册一个信号处理函数,并且处理完毕一个信号之后,是否需要重新注册,才能够捕捉下一个信号;
2、  如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个同类型的信号,这时该怎么处理;
3、  如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,这时该怎么处理;
4、  如果程序阻塞在一个系统调用(如read(...))时,发生了一个信号,这时是让系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。
 
示例:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <unistd.h>
  4. #include <signal.h>
  5.  
  6. int g_iSeq=0;
  7.  
  8. void SignHandler(int iSignNo)
  9. {
  10.     int iSeq=g_iSeq++; 
  11.     printf("%d Enter SignHandler,signo:%d./n",iSeq,iSignNo); 
  12.     sleep(3); 
  13.     printf("%d Leave SignHandler,signo:%d/n",iSeq,iSignNo); 
  14. }
  15.  
  16. int main()
  17. {
  18.     char szBuf[8]; 
  19.     int iRet; 
  20.     signal(SIGINT,SignHandler); 
  21.     signal(SIGQUIT,SignHandler); 
  22.     do{ 
  23.         iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1); 
  24.         if(iRet<0){ 
  25.             perror("read fail."); 
  26.             break; 
  27.         } 
  28.       szBuf[iRet]=0; 
  29.         printf("Get: %s",szBuf); 
  30.     }while(strcmp(szBuf,"quit/n")!=0); 
  31.     return 0; 
  32. }
程序运行时,针对于如下几种输入情况(要输入得快),看输出结果:
1、  CTRL+c] [CTRL+c] [CTRL+c]
2、  [CTRL+c] [CTRL+/]
3、  hello [CTRL+/] [Enter]
4、  [CTRL+/] hello [Enter]
5、  hel [CTRL+/] lo[Enter]
 
针对于上面各种情况,不同版本OS可能有不同的响应结果。
3.2. sigaction信号处理注册 
如果要想用程序控制上述各种情况的响应结果,就必须采用新的信号捕获机制,即使用sigaction信号处理机制。
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction也用于注册一个信号处理函数。
参数signum为需要捕捉的信号;
参数 act是一个结构体,里面包含信号处理函数地址、处理方式等信息。
参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息。
如果函数调用成功,将返回0,否则返回-1
结构体 struct sigaction(注意名称与函数sigaction相同)的原型为:
struct sigaction {
    void (*sa_handler)(int);         // 老类型的信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);//新类型的信号处理函数指针
sigset_t sa_mask;                 // 将要被阻塞的信号集合
int sa_flags;                         // 信号处理方式掩码
void (*sa_restorer)(void);     // 保留,不要使用。
}
       该结构体的各字段含义及使用方式:
1、字段sa_handler是一个函数指针,用于指向原型为void handler(int)的信号处理函数地址,       即老类型       的信号处理函数;
2、字段sa_sigaction也是一个函数指针,用于指向原型为:
void handler(int iSignNum,siginfo_t *pSignInfo,void *pReserved);
的信号处理函数,即新类型的信号处理函数。
该函数的三个参数含义为:
              iSignNum :传入的信号
              pSignInfo :与该信号相关的一些信息,它是个结构体
              pReserved :保留,现没用
3、字段sa_handler和sa_sigaction只应该有一个生效,如果想采用老的信号处理机制,就应该让sa_handler指向正确的信号处理函数;否则应该让sa_sigaction指向正确的信号处理函数,并且让字段 sa_flags包含SA_SIGINFO选项。
4、字段sa_mask是一个包含信号集合的结构体,该结构体内的信号表示在进行信号处理时,将要被阻塞的信号。针对sigset_t结构体,有一组专门的函数对它进行处理,它们是:
              #include <signal.h> 
        int sigemptyset(sigset_t *set);                                   // 清空信号集合set
        int sigfillset(sigset_t *set);                                 // 将所有信号填充进set中
        int sigaddset(sigset_t *set, int signum);               // 往set中添加信号signum
        int sigdelset(sigset_t *set, int signum);                // 从set中移除信号signum
        int sigismember(const sigset_t *set, int signum); // 判断signnum是不是包含在set中
       例如,如果打算在处理信号SIGINT时,只阻塞对SIGQUIT信号的处理,可以用如下种方法:
              struct sigaction act; 
              sigemptyset(&act.sa_mask); 
              sigaddset(&act_sa_mask,SIGQUIT); 
              sigaction(SIGINT,&act,NULL); 
5、  字段sa_flags是一组掩码的合成值,指示信号处理时所应该采取的一些行为,各掩码的含义为:
 
掩码 描述 
SA_RESETHAND 处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已经被废弃。 
SA_NODEFER 在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。如果sa_flags包含了该掩码,则结构体sigaction的sa_mask将无效! 
SA_RESTART 如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着从阻塞的系统返回。该掩码符合普通的程序处理流程,所以一般来说,应该设置该掩码,否则信号处理完后,阻塞的系统调用将会返回失败! 
SA_SIGINFO 指示结构体的信号处理函数指针是哪个有效,如果sa_flags包含该掩码,则sa_sigactiion指针有效,否则是sa_handler指针有效。

 
 
       练习与验证:
针对于先前的5种输入情况,给下面代码再添加一些代码,使之能够进行如下各种形式的响应:
       1 、[CTRL+c] [CTRL+c]时,第1个信号处理阻塞第2个信号处理;
       2 、[CTRL+c] [CTRL+c]时,第1个信号处理时,允许递规地第2个信号处理;
       3 、[CTRL+c] [CTRL+/]时,第1个信号阻塞第2个信号处理;
       4 、read不要因为信号处理而返回失败结果。
 

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <unistd.h>
  4. #include <signal.h>
  5.  
  6. int g_iSeq=0;
  7.  
  8. void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)
  9. {
  10.     int iSeq=g_iSeq++; 
  11.     printf("%d Enter SignHandlerNew,signo:%d./n",iSeq,iSignNo); 
  12.     sleep(3); 
  13.     printf("%d Leave SignHandlerNew,signo:%d/n",iSeq,iSignNo); 
  14. }
  15.  
  16. int main()
  17. {
  18.     char szBuf[8]; 
  19.     int iRet; 
  20.     struct sigaction act; 
  21.     act.sa_sigaction=SignHandlerNew; 
  22.     act.sa_flags=SA_SIGINFO; 
  23.               // 
  24.     sigemptyset(&act.sa_mask); 
  25.    sigaction(SIGINT,&act,NULL); 
  26.     sigaction(SIGQUIT,&act,NULL); 
  27.     do{ 
  28.         iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1); 
  29.         if(iRet<0){ 
  30.             perror("read fail."); 
  31.             break; 
  32.         } 
  33.         szBuf[iRet]=0; 
  34.         printf("Get: %s",szBuf); 
  35.     }while(strcmp(szBuf,"quit/n")!=0); 
  36.     return 0; 
  37. }
 
3.3. sigprocmask信号阻塞 
函数sigaction中设置的被阻塞信号集合只是针对于要处理的信号,例如
struct sigaction act;
                  sigemptyset(&act.sa_mask); 
              sigaddset(&act.sa_mask,SIGQUIT); 
                  sigaction(SIGINT,&act,NULL); 
       表示只有在处理信号SIGINT时,才阻塞信号SIGQUIT;
       函数sigprocmask是全程阻塞,在sigprocmask中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。
       原型为:
       #include <signal.h> 
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数how的值为如下3者之一:
       a :SIG_BLOCK ,将参数2的信号集合添加到进程原有的阻塞信号集合中
       b :SIG_UNBLOCK ,从进程原有的阻塞信号集合移除参数2中包含的信号
       c :SIG_SET,重新设置进程的阻塞信号集为参数2的信号集
参数set为阻塞信号集
参数oldset是传出参数,存放进程原有的信号集。
示例:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <unistd.h>
  4. #include <signal.h>
  5.  
  6. int g_iSeq=0;
  7.  
  8. void SignHandlerNew(int iSignNo,siginfo_t *pInfo,void *pReserved)
  9. {
  10.     int iSeq=g_iSeq++; 
  11.     printf("%d Enter SignHandlerNew,signo:%d./n",iSeq,iSignNo); 
  12.     sleep(3); 
  13.     printf("%d Leave SignHandlerNew,signo:%d/n",iSeq,iSignNo); 
  14. }
  15.  
  16. int main()
  17. {
  18.     char szBuf[8]; 
  19.     int iRet; 
  20.     struct sigaction act; 
  21.     act.sa_sigaction=SignHandlerNew; 
  22.     act.sa_flags=SA_SIGINFO; 
  23.     // 屏蔽掉SIGINT 信号,SigHandlerNew 将不能再捕捉SIGINT 
  24. sigset_t sigSet; 
  25.     sigemptyset(&sigSet); 
  26.     sigaddset(&sigSet,SIGINT); 
  27.     sigprocmask(SIG_BLOCK,&sigSet,NULL); 
  28.               // 
  29.     sigemptyset(&act.sa_mask); 
  30.     sigaction(SIGINT,&act,NULL); 
  31.     sigaction(SIGQUIT,&act,NULL); 
  32.     do{ 
  33.         iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1); 
  34.         if(iRet<0){ 
  35.             perror("read fail."); 
  36.             break; 
  37.         } 
  38.         szBuf[iRet]=0; 
  39.         printf("Get: %s",szBuf); 
  40.     }while(strcmp(szBuf,"quit/n")!=0); 
  41.     return 0; 
  42. }
 
4. 用程序发送信号 
4.1. kill信号发送函数 
原型为:
#include <sys/types.h>
    #include <signal.h> 
int kill(pid_t pid, int sig);
       参数pid为将要接受信号的进程的pid
       参数sig为要发送的信号
       如果成功,返回0,否则为-1。
       示例,输入结束后,将通过发送信号SIGQUIT把自己杀掉:
  1. #include <stdio.h>
  2. #include <signal.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. int main()
  6. {
  7.                   while(true){ 
  8.                if(getchar()==EOF) 
  9.             kill(getpid(),SIGQUIT); 
  10.                   } 
  11.     return 0; 
  12. }
4.2. sigqueue信号发送函数 
sigqueue也可以发送信号,并且能传递附加的信息。
原型为:
#include <signal.h>
    int sigqueue(pid_t pid, int sig, const union sigval value); 
参数pid为接收信号的进程;
参数sig为要发送的信号;
参数value为一整型与指针类型的联合体:
       union sigval { 
int   sival_int; 
void *sival_ptr;
    }; 
由sigqueue函数发送的信号的第3个参数value的值,可以被进程的信号处理函数的第2个参数info->si_ptr接收到。
示例1,进程给自己发信号,并且带上附加信息:
  1. #include <signal.h>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7.  
  8. void SignHandlerNew(int signum,siginfo_t *info,void *myact)
  9. {
  10.                   char *pszInfo=(char *)(info->si_ptr); 
  11.     printf("Get:%d info:%s/n",signum,pszInfo); 
  12. }
  13.  
  14. int main(int argc,char**argv)
  15. {
  16.                   struct sigaction act;   
  17.     union sigval mysigval; 
  18.                   int sig; 
  19.     char data[]="other info"; 
  20.                   // 
  21.                   if(argc<2){ 
  22.                       printf("usage: SIGNNUM/n"); 
  23.         return -1; 
  24.                   } 
  25.     mysigval.sival_ptr=data; 
  26.                   sig=atoi(argv[1]); 
  27.     sigemptyset(&act.sa_mask); 
  28.           act.sa_sigaction=SignHandlerNew; 
  29.                   act.sa_flags=SA_SIGINFO; 
  30.                   sigaction(sig,&act,NULL); 
  31.                   while(true){ 
  32.                       printf("wait for the signal/n"); 
  33.                       sigqueue(getpid(),sig,mysigval); 
  34.                       sleep(2); 
  35.                   } 
  36. }
 
示例2:一个进程向另外一个进程发送信号。注意:发送进程不要将自己进程空间的地址发送给接收进程,因为接收进程接收到地址也访问不到发送进程的地址空间的。
 
示例2信号接收程序:

  1. #include <signal.h> 
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7.  
  8. void SignHandlerNew(int signum,siginfo_t *info,void *myact)
  9. {
  10.                   printf("Get:%d info:%d/n",signum,info->si_int); 
  11. }
  12.  
  13. int main(int argc,char**argv)
  14. {
  15.                   struct sigaction act; 
  16.     // 
  17.                   if(argc<2){ 
  18.                       printf("usage: signnum/n"); 
  19.         return -1; 
  20.                   } 
  21.     sigemptyset(&act.sa_mask); 
  22.     act.sa_sigaction=SignHandlerNew; 
  23.                   act.sa_flags=SA_SIGINFO; 
  24.     sigaction(atoi(argv[1]),&act,NULL); 
  25.                   while(1) 
  26.     { 
  27.                       printf("wait for the signal/n"); 
  28.                sleep(2); 
  29.     } 
  30.                   return 0; 
  31. }
       
示例2信号发送程序:
  1. #include <signal.h>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7.  
  8. int main(int argc,char**argv)
  9. {
  10.                   union sigval mysigval; 
  11.     int iPid,iSignNo,iData; 
  12.                   // 
  13.     if(argc<4){ 
  14.                       printf("usage: pid signnum data/n"); 
  15.                       return -1; 
  16. }
  17.     iPid=atoi(argv[1]); 
  18.                   iSignNo=atoi(argv[2]); 
  19.     iData=atoi(argv[3]); 
  20.     mysigval.sival_int=iData; 
  21.                   if(sigqueue(iPid,iSignNo,mysigval)<0) 
  22.                       perror("Send signal fail."); 
  23.     return 0; 
  24. }     
       
5. 计时器与信号 
5.1. 睡眠函数 
Linux下有两个睡眠函数,原型为:
       #include <unistd.h> 
        unsigned int sleep(unsigned int seconds); 
              void usleep(unsigned long usec); 
       函数sleep让进程睡眠seconds秒,函数usleep让进程睡眠usec毫秒。
       sleep 睡眠函数内部是用信号机制进行处理的,用到的函数有:
              #include <unistd.h> 
unsigned int alarm(unsigned int seconds);     // 告知自身进程,要进程在seconds秒后自动产生一个//SIGALRM的信号,
int pause(void);                       // 将自身进程挂起,直到有信号发生时才从pause返回
       
       示例:模拟睡眠3秒:
  1. #include <signal.h> 
  2. #include <stdio.h>
  3. #include <unistd.h>
  4.  
  5. void SignHandler(int iSignNo)
  6. {
  7.     printf("signal:%d/n",iSignNo); 
  8. }
  9.  
  10. int main()
  11. {
  12.     signal(SIGALRM,SignHandler); 
  13.     alarm(3); 
  14.     printf("Before pause()./n"); 
  15.     pause(); 
  16.     printf("After pause()./n"); 
  17.     return 0; 
  18. }
注意:因为sleep在内部是用alarm实现的,所以在程序中最好不要sleep与alarm混用,以免造成混乱。
5.2. 时钟处理   
Linux为每个进程维护3个计时器,分别是真实计时器、虚拟计时器和实用计时器。
l         真实计时器计算的是程序运行的实际时间;
l         虚拟计时器计算的是程序运行在用户态时所消耗的时间(可认为是实际时间减掉(系统调用和程序睡眠所消耗)的时间);
l         实用计时器计算的是程序处于用户态和处于内核态所消耗的时间之和。
例如:有一程序运行,在用户态运行了5秒,在内核态运行了6秒,还睡眠了7秒,则真实计算器计算的结果是18秒,虚拟计时器计算的是5秒,实用计时器计算的是11秒。
用指定的初始间隔和重复间隔时间为进程设定好一个计时器后,该计时器就会定时地向进程发送时钟信号。3个计时器发送的时钟信号分别为:SIGALRM,SIGVTALRM和SIGPROF。
用到的函数与数据结构:
#include <sys/time.h>
 
//获取计时器的设置
//which指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VITUAL(虚拟计时器、ITIMER_PROF(实用计时器))
//value为一结构体的传出参数,用于传出该计时器的初始间隔时间和重复间隔时间
//如果成功,返回0,否则-1
int getitimer(int which, struct itimerval *value);
 
//设置计时器
//which指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VITUAL(虚拟计时器、ITIMER_PROF(实用计时器))
//value为一结构体的传入参数,指定该计时器的初始间隔时间和重复间隔时间
//ovalue为一结构体传出参数,用于传出以前的计时器时间设置。
//如果成功,返回0,否则-1
int setitimer(int which, const struct itimerval *value, struct itimer val *ovalue);
    
struct itimerval {
struct timeval it_interval; /* next value */            // 重复间隔
struct timeval it_value;    /* current value */     // 初始间隔     
};
struct timeval {
long tv_sec;                /* seconds */                    // 时间的秒数部分
long tv_usec;               /* microseconds */        // 时间的微秒部分
};
 
示例:启用真实计时器的进行时钟处理
  1. #include <stdio.h> 
  2. #include <unistd.h>
  3. #include <signal.h>
  4. #include <sys/time.h>
  5.  
  6. void TimeInt2Obj(int imSecond,timeval *ptVal)
  7. {
  8. ptVal->tv_sec=imSecond/1000;
  9.         ptVal->tv_usec=(imSecond%1000)*1000; 
  10. }
  11.  
  12. void SignHandler(int SignNo)
  13. {
  14. printf("Clock/n");
  15. }
  16.  
  17. int main()
  18. {
  19. signal(SIGALRM,SignHandler);
  20.     itimerval tval; 
  21.     TimeInt2Obj(1,&tval.it_value);            // 设初始间隔为1毫秒,注意不要为0
  22.               TimeInt2Obj(1500,&tval.it_interval);    // 设置以后的重复间隔为1500毫秒
  23.     setitimer(ITIMER_REAL,&tval,NULL); 
  24.                while(getchar()!=EOF); 
  25.     return 0; 
  26. }

SIGHUP     终止进程     终端线路挂断
SIGINT     终止进程     中断进程
SIGQUIT   建立CORE文件终止进程,并且生成core文件
SIGILL   建立CORE文件       非法指令
SIGTRAP   建立CORE文件       跟踪自陷
SIGBUS   建立CORE文件       总线错误
SIGSEGV   建立CORE文件       段非法错误
SIGFPE   建立CORE文件       浮点异常
SIGIOT   建立CORE文件       执行I/O自陷
SIGKILL   终止进程     杀死进程
SIGPIPE   终止进程     向一个没有读进程的管道写数据
SIGALARM   终止进程     计时器到时
SIGTERM   终止进程     软件终止信号
SIGSTOP   停止进程     非终端来的停止信号
SIGTSTP   停止进程     终端来的停止信号
SIGCONT   忽略信号     继续执行一个停止的进程
SIGURG   忽略信号     I/O紧急信号
SIGIO     忽略信号     描述符上可以进行I/O
SIGCHLD   忽略信号     当子进程停止或退出时通知父进程
SIGTTOU   停止进程     后台进程写终端
SIGTTIN   停止进程     后台进程读终端
SIGXGPU   终止进程     CPU时限超时
SIGXFSZ   终止进程     文件长度过长
SIGWINCH   忽略信号     窗口大小发生变化
SIGPROF   终止进程     统计分布图用计时器到时
SIGUSR1   终止进程     用户定义信号1
SIGUSR2   终止进程     用户定义信号2
SIGVTALRM 终止进程     虚拟计时器到时



0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 唐三复活了小舞失去的魂环怎么办了 我该怎么办?身陷动漫城输了很多钱 庄家开2球大小球踢成2球怎么办 去哪儿网订机票时邮箱写错了怎么办 在南航航班上把手机丢飞机上怎么办 买了品牌鞋穿了一周就破了怎么办 狗让狠狠的打了一顿不理人了怎么办 调好米粉宝宝吃的时候就凉了怎么办 情人间闹分手删了微信后悔了怎么办 8个月宝宝不坐椅子一直要抱怎么办 2个月婴儿3天没有拉大便了怎么办 8个月的宝宝不吃米糊和稀饭怎么办 2岁零5个月的宝宝不说话怎么办 两岁宝宝不拔掉老是拉在裤上怎么办 一岁的宝宝吞了一颗五子棋该怎么办 别人欠我钱还把我拉黑我该怎么办 欠我钱的人耍赖不还我该怎么办 交易猫买的炉石传说号被找回怎么办 淘宝上卖水果过季了不想下架怎么办 两岁宝宝被蚊子咬了挠破流水怎么办 我打了人一拳他就躺地下了怎么办 在微信上被认识的人骗了钱该怎么办 微信上面被不认识的人骗了钱怎么办 柜体和订做的柜门颜色对不上怎么办 拉鞭炮的车压了我的电车不陪怎么办 脚爱出汗穿高跟凉鞋总往前滑怎么办 视频的格式是VⅠD打开很慢怎么办 汕头普法学法我点了考试没考怎么办 德云的生活攻略第三天卡关了怎么办 我的世界房子被参观的人烧了怎么办 新买的手表返厂维修弄划伤了怎么办 糖猫手表丢了别人捡了换了卡怎么办 我妈总怀疑我爸偷她东西怎么办啊 在百度上买的演出票不配送了怎么办 北交大预报名信息填错了怎么办保研 我租了个店面房子但是写了拆怎么办 电话换了微信账号密码都忘了怎么办 宝宝被开水烫了的泡泡破皮了怎么办 脚上泡泡破了的留下的黑印子怎么办 我的车子被前夫霸占了不给我怎么办 自己和同学吵了一架生气了要怎么办