zoukankan      html  css  js  c++  java
  • 进程 信号 通信 消息队列 共享内存 进程间通信 等读书笔记及个人小练习

    一、基本概念
     1、进程与程序
     程序是存储在磁盘上的文件,它是包含要执行的机器指令和数据的静态实体。
     进程是一个正在运行的程序,一个程序可能包含多个进程(多任务、多进程),进程在操作系统中是一个执行任务的单位。
     
     2、进程的分类
     交互进程:需要用户输入数据,也会显示一些结果给用户看。
     批处理进程:用来执行脚本的进程,例如Makefil。
     守护进程:它是一种一直活跃的进程,一般是后台的,由操作系统启动时通过开过开机脚本加载或由超级用户加载。
     
     3、查看进程
     ps:显示当用户当前终端所控制的进程。
     -a:显示所有用户的进程
     -x:包括无终端控制的进程
     -u:显示详细信息
     -w:以更宽的方式显示
     
     4、进程信息表
     USER:属主
     PID:进程号
     %CPU:cpu占用率
     %MEM:内存使用率
     VSZ:虚拟内存的大小
     RSS:物理内存的使用量
     TTY:终端设备号,如果不是终端控制进程用'?'表示。
     STAT:终端的状态
     START:开始时间
     TIME:运行时间
     COMMAND:开启此进程的命令
     
     5、进程的状态
     O:就绪态,一切准备工作都已经做好,等待被调用。
     R:运行态,Linux下没有就绪态,O也就是R。
     S:可唤醒的睡眠态,系统调用、获取到资源、收到信息都可以被唤醒。
     D:不可唤醒的睡眠态,必须等到的事件发生。
     T:暂停态,收到了SIGSTOP信号,当收到SIGCONT信号则继续运行。
     X:死亡态。
     Z:僵尸态。
     <:高优先级。
     N:低优先级。
     L:多线程进程。
     s:有子进程的进程。
     +:后台进程组。
     
     6、父子进程
      如果进程B是由进程A开启的,那么我们把进程A叫进程B的父进程,进程B叫作进程A的子进程。
      当子进程结束后会向父进程发送,SIGCHLD,父进程收到信号后再支回收子进程。
      当先父进程先结束,子进程就会变成孤儿进程,会被孤儿院(init)收养。
      如果子进程已经结束,但父进程没有及时回收,子进程就变成了僵局进程。 
     
     7、进程标识符,
      俗称进程号,它是一个无符号整数,使用getpid函数可以获取到。
      这种编号是循环使用,当进程结束后,它就跟这个编号没有关系,这个编号也会被再次使用。
      延时重用。
     
    二、getxxid
     pid_t getpid(void);
     功能:获取进程id
     
     pid_t getppid(void);
     功能:获取父进程id
     
     uid_t getuid(void);
     功能:获取实际调用者的用户id,
     
        uid_t geteuid(void);
        功能:获取程序的拥有者用户的id
     
     gid_t getgid(void);
     功能:获取实际使用者的组id,
     
        gid_t getegid(void);
        功能:获取程序的拥有者用户组id
        
        1、切换用户
        2、chmod u+s a.out
        3、chmod g+s a.out

       
        进程在运行时会根据实际的调用都来获取它的权限,这样可以让程序比实际的拥有者还要高的权限。
       
    三、fork
     pid_t fork(void);
     功能:创建子进程
     
     1、失败返回-1,如果成功会返回两次。
     2、父进程会返回子进程的id,子进程返回0
     3、根据返回值的不同,分别为子进程和父进程设计不同的分支。
        4、通过fork创建出的子进程,就是父进程的副本,它会把父进程的堆、栈、全局段、静态数据段、IO流的缓冲区都拷贝一份,父子进程共享代码段。
        5、fork函数调用成功后,父子进程就开始各自执行了,它们的先后顺序是不确定的,但可以通过某些实现来保证。
        
        练习1:编写一个程序,让此程序拥有5个进程同时执行。
     练习2:编写一个程序,让此程序有三级子进程。
     
     6、当总进程数超过系统的限制时,就无法再创建进程的了,此时fork函数会返回-1,执行失败。
     
     7、孤儿进程:子进程还有运行,但父进程已经结束,此时子进程会被init(1)收养,子进程变成孤儿进程。
     8、僵尸进程:子进程已经死亡,但父进程没有及时回收子进程,此时子进程就会变成僵尸进程。
     9、父进程打开的文件和子进程是共享的。
     注意:fork之前的代码,只有父进程在执行,fork之后的代码父子进程都有机会执行,根据fork返回值来控制进入不同的分支。
     
    四、vfork
     pid_t vfork(void);
     功能:创建子进程
     
     1、vfork不能单独创建子进程,需要与excl函数簇,配合才完成子进程的创建。
     2、它不会复制父进程的栈、堆、数据、全局等段,也不会共享代码段,而是通过excl函数调用一个程序直接启动,从面提高创建进程的效率。 
     3、使用vfork创建的子进程保证,先执行子进程,后执行父进程。
     
    五、进程的正常退出
     1、从main退出,在main中执行return 0;
      返回值的低8位会被父进程获取到。
      
     2、系统的_exit(stat)/标准的_Exit(stat),这两个函数几乎没有什么区别。
     #include <unistd.h>
     void _exit(int status);
     #include <stdlib.h>
     void _Exit(int status);
     a、返回值的低8位会被父进程获取到。
     b、使用_exit/_Exit退出前会关闭所有打开的文件流,如果有子进程则会托附给init,然后向父进程发送SIGCHLD信号。
     c、此函数不会返回。
      
     3、调用标准C的exit(stat)函数
     a、exit在底层实现上调用了_exit/_Exit函数,所以_exit/_Exit的特点它都具备。
     b、exit结束前会调用通过atexit/on_exit注册的函数。
     
     int atexit(void (*function)(void));
     int on_exit(void (*function)(int , void *), void *arg);
     
     4、最后一个线程正常结束
     
    六、进程异常中止
     1、进程调用了abort函数(段错误、浮点异常)
     2、进程接收到某些信号
      crtl+c
      crtl+
      ctrl+z
     3、最后一个线程收到取消操作,而且线程作出响应。
     
    七、子进程的回收
     pid_t wait(int *status);
     功能:等待子进程结束,并回收
     
        pid_t waitpid(pid_t  pid,  int  *status,int options);
        功能:等待指定的子进程结束,并回收
       
        1、当一个进程结束时,内核会向它的父进程发送一个信号
         父进程收到这个信号后可以指定一个函数来处理,也可以选择忽略,默认情况下是忽略的。
         
        2、父进程调用wait调用才是有意义
         如果所有的子进程都在运行,则父进程阻塞(wait)
         只要有一个子进程结束了,会立即返回子进程的id和结束状态。
         当所有子进程都结束运行时,wait会返回-1。
         如果在调用wait之前子进程就已经结束(僵尸子进程),执行wait函数时会立即返回并回收僵尸进程。
         
        3、waitpid函数可以指定等于哪个子进程
         pid:指定的pid
          == -1 功能与wait类似,pid就无意义了。
          > 0 等待进程号是pid的进程结束。
          
          == 0 等待组id等于pid的进程组中任意进程结束。
          < -1 等待组id是pid的绝对值的进程组中任意进程结束。
          
         status:用于接收子进程的结束状态,如果不需要状态码可以设置为NULL;
         
         options:
          0 以阻塞状态等待子进程结束
          WNOHANG 如果没有子进程退出会立即返回。
          WUNTRACED 等待的进程处于停止状态,并且之前没有报告过,则立即返回。
          
        4、如果不调用wait/waitpid函数,子进程结束后就处于僵尸状态,当父进程也结束时,父进程的父进程会把他们统一回收。
         
    八、exec函数簇
     exec函数的功能:加载一个可执行文件,要和vfork函数配合才有意义。
     
     int execl(const char *path,  const  char
           *arg, ...);
        path:可执行文件的路径
        arg:给可执行文件的参数,类似于命令行参数,必须以NULL结尾,第一个必须是可以执行文件名。
        execl("","a.out",NULL);
       
        1、通过exec创建的子进程会替换掉父进程给的代码段,不拷贝父进程的堆、栈、全局、静态数据段,会用新的可执行文件替换掉他们。
        2、exec只是加载一个可执行文件,并不创建进程,不会产生新的进程号。
        3、只有exec函数执行结束(无论成功还是失败),父进程才能继续执行。
     int  execlp(const char *file, const char
           *arg, ...);
        file:可执行文件的文件名,会从PATH环境变量指定的位置去找可执行文件。
           
        int execle(const char *path, const  char
           *arg,..., char * const envp[]);
           
        int  execv(const char *path, char *const
           argv[]);
         
        int execvp(const char *file, char *const
           argv[]);
     
    一、基本的概念
     1、中断
     中止、暂停当前正在执行的进程,转而去执行其它的任务。
     硬中断:来自硬件设备的中断
     软中断:来自其它程序的中断
     
     2、信号
     信号是一种软中断,可以把他看作是进程与进程、内核与进程通信的一种方式,它为进程的异步执行,提供了技术支持。
     
     3、常见的信号
      SIGINT(2) 终端中断信号Ctrl+c
      SIGQUIT(3) 终端退出信号Ctrl+/ 
      SIGABRT(6) 调用abort函数产生的信号
      SIGFPE(8) 算术信号
      SIGKILL(9) 死亡信号
      SIGSEGV(11) 段错误信号
      SIGALRM(14) 闹钟信号
      SIGCHLD(17) 子进程结束信号
      SIGCONT(18) 进程继续信号
      SIGSTOP(19) 进程暂停信号
      SIGTSTP(20) 终端停止信号

      
     4、不可靠信号(非实时)
      1、编号小于SIGRGMI(34)的信号都是不可靠的,这些信号是建立在早期的信号机制上的,一个事件发生可能会产生多次信号。
      2、不可靠信号不支持排除,在接收信号的时候可能会丢失,如果一个发给一个进程多次,它可能只接收到一次,其它的可能就丢失了。 
      3、进程在处理这种信号的时候,哪怕设置的信号处理函数,当信号处理函数执行完毕后,会再次恢复成默认的信号处理方式。
      
     5、可靠信号(实时)
      1、位于[SIGRGMI(34),SIGRTMAX(64)]区间的都是可靠信号。
      2、可靠信号支持排除,不会丢失。
      3、无论是可靠信号还是不可靠信号都是通过:kill、signal、sigqueue、sigaction进行处理。
     
     6、信号的来源
      硬件来源:
       键盘:Ctrl+c、Ctrl+/、Ctrl+z
       驱动:硬件设备被激活、使用、失效。
       内存:非法访问内存。
      软件来源:
       命令:kill、killall
       函数:kill/raise/alarm/setitimer/sigqueue。
       
     7、信号的处理方式
      1、忽略
      2、终止
      3、终止+core
      core dump 把内存的使用情况扔出来。
      core文件是一种二进制文件,需要一些高度工具才能解析出来(gdb)。
      1、gcc -g code.c -> 生成带调试信息的可执行文件。
      2、运行可执行文件产生core文件
      3、gdb ./a.out core 程序会停止在产生错误的位置。
      
      ubuntu默认不产生core谁的,需要使用命令设置:
       ulimit -c unlimited
      
      4、捕获并处理
    二、信号的捕获和处理
     #include <signal.h>
        typedef void (*sighandler_t)(int);
        sighandler_t  signal(int  signum, sighan‐
           dler_t handler);
        功能:向注册一个信号处理函数
        signum:信号的编号,可以直接写数字,也可以使用系统提供的宏。
        handler:函数指针
         SIG_IGN 忽略信号
         SIG_DFL 恢复信号默认的处理方式
        返回值:是之前信号处理方式
         函数指针、SIG_IGN、SIG_DFL、SIG_ERR
       
        练习:实现一个杀不死的程序。
        1、在有些系统中向内核注册的信号处理函数只执行一次(在执行前就被恢复成默认的处理方式),如果想持续处理信号,可以在每次的处理函数结束时再次注册。
       
        2、SIGKILL、SIGSTOP不能被捕获、也不能被忽略。
       
        3、普通用户只能给自己的进程发信号,超级用户可以能任意进程发送信号。
         
    三、子进程的信号处理
        1、通过fork创建的子进程会继承父进程的信号处理方式。
        2、通过vfork+exec创建的子进程不会继承父进程的信号处理方式,会恢复成默认的。
        练习:测试通过vfork+exec创建的子进程是否会继承父进程的信号处理方式。
       
    四、信号的发送
     1、键盘
      Ctrl+c 终端中断信号
      Ctrl+z 终端暂停信号,fg命令再次开启
      Ctrl+/ 终端退出信号
     2、错误产生的信号
      除0
      非法内存访问
      硬件总线
     3、命令产生的信号
      kill -信号 进程号
      killall -信号 程序名(杀死所有同名的进程)。
     4、函数产生的信号
      int kill(pid_t pid, int sig);
      功能:向指定的进程发送信号
      pid:与waitpid一样
      sig:信号
       0表示空信号,不会向进程发送信号,但是会测试是否能向pid发送信号,这样可以检测一个进程是否存在,返回-1表示进程不存在,errno为ESRCH。
      返回值:-1,说明进程不存在
      
      int raise(int sig);
      功能:向自己发送信号
     
     练习:实现kill命令的功能。
     
    五、pause
     #include <unistd.h>
        int pause(void);
        功能:休眠
       
        1、进程调用了pause函数后会进程睡眠状态,直到有信号把它叫醒(不被忽略的信号)。
        2、当信号来临后,先执行信号处理函数,信号处理函数结束后pause再返回。
        3、pause函数要么不返回(一直睡眠),要么返回-1,并且修改errno的值。
        4、从功能上来讲它相当于没有时间限制的sleep函数。
       
    六、sleep
     #include <unistd.h>
     unsigned int sleep(unsigned int seconds);
     功能:使用调用的进程睡眠seconds秒
     
     1、调用sleep的进程如果没有睡眠足够的秒数,除非收到信号后才会返回。
     2、sleep的返回值是0,或剩余的睡眠秒数。
     3、相当于有时间限制的pause
     
     int usleep(useconds_t usec);v
     功能:睡眠usec微秒
     
     1秒=1000毫秒=1000000微秒。
     它是一种更精确的睡眠函数。
     
     -std=c99 不建议使用
     -std=gnu99 在使用系统调用时一定要使用此标准
     
    七、alarm
     #include <unistd.h>
        unsigned int alarm(unsigned int seconds);
        功能:定时一个闹钟信号
       
        1、让内核向调用它的进程,在seconds秒后发送一个SIGALRM信号。
        2、SIGALRM信号的默认处理方式是直接退出。
       
        练习:实现一个闹钟命令,./alarm sec
       
    八、信号集和信号屏蔽
     1、信号集:
      多个信号的集合,sigset_t
      由128个二进制位组成,每个二进制位表示一个信号
      
      int sigemptyset(sigset_t *set);
      功能:清空信号集
      
            int sigfillset(sigset_t *set);
      功能:填满信号信
      
            int sigaddset(sigset_t *set, int signum);
      功能:向信号集中添加信号
      
            int sigdelset(sigset_t *set, int signum);
      功能:从信号集中删除信号
      
            int  sigismember(const sigset_t *set, int
           signum);
            功能:测试一个信号集中是否有某个信号
            返回值:有返回1,没有返回0,失败返回-1

     2、屏蔽信号集中的信号
      每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号。
      
      int sigprocmask(int how, const  sigset_t
           *set, sigset_t *oldset);
           功能:设置进程的信号掩码(信号屏蔽码)
           how:修改信号掩码的方式
             SIG_BLOCK:向信号掩码中添加信号
             SIG_UNBLOCK:从信号掩码中删除信号
             SIG_SETMASK:用新的信号集替换旧的信号掩码
           newset:新添加、删除、替换的信号集,也可以为空
           oldset:获取旧的信号掩码
           当newset为空时,就是在备份信号掩码
     当进程执行一些敏感操作时不希望被打扰(原子操作),此需要向屏蔽信号。
     屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。
     当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次。
     不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。
     可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。
     在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复
     
     int sigpending(sigset_t *set);
     功能:获取末决状态的信号
     
    九、信号处理sigaction signal
     
    int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
     功能:设置或获取信号处理方式
     
      struct sigaction {
       // 信号处理函数指针
          void (*sa_handler)(int);
          // 信号处理函数指针 需要使用sigqueue发送信号
            void (*sa_sigaction)(int, siginfo_t *, void *);
            // 信号屏蔽码
            sigset_t sa_mask;
            int sa_flags;
             SA_NOCLDSTOP:忽略SIGCHLD信号
             SA_NODEFER/SA_NOMASK:在处理信号时不屏蔽信号
             SA_RESETHAND:处理完信号后,恢复系统默认处理方式
             SA_RESTART:当信号处理函数中断的系统调用,则重启系统调用。
             SA_SIGINFO:用sa_sigaction处理信号
            // 保留
            void (*sa_restorer)(void);
         };
         
         
         int sigqueue(pid_t pid, int  sig,  const
           union sigval value);
     
     
        
    一、基本概念
     进程间通信(IPC):进程之间交换数据的过程叫进程间通信。
     进程间通信的方式:
      简单的进程间通信:
       命令行:父进程通过exec函数创建子进程时可以附加一些数据。
       环境变量:父进程通过exec函数创建子进程顺便传递一张环境变量表。
       信号:父子进程之间可以根据进程号相互发送信号,进程简单通信。
       文件:一个进程向文件中写入数据,另一个进程从文件中读取出来。
       命令行、环境变量只能单身传递,信号太过于简单,文件通信不能实时。
      
      XSI通信方式:X/open 计算机制造商组织。
       共享内存、消息队列、信号量
      网络进程间通信方式:
       网络通信就是不同机器的进程间通信方式。
      
      传统的进程间通信方式:管道
    二、管道
     1、管道是一种古老的通信的方式(基本上不再使用)
     2、早期的管道是一种半双工,现在大多数是全双工。
     3、有名管道(这种管道是以文件方式存在的)。
     int mkfifo(const char *pathname, mode_t mode);
       
     管道通信的编程模式:
      进程A    进程B
      创建管道mkfifo
      打开管道open   打开管道
      写/读数据read/write 读/写数据
      关闭管道close   关闭管道
       
     4、无名管道:由内核帮助创建,只返回管道的文件描述符,看不到管道文件,但这种管道只能用在fork创建的父子进程之间。
      int pipe(int pipefd[2]);
      pipefd[0] 用来读数据
      pipefd[1] 用来写数据
       
      练习:使用无名管道,让父子进程通信。
    三、XSI IPC进程间通信
     1、XSI通信是靠内核的IPC对象进程通信。
     2、每一个IPC对象都有一个IPC标识(类似文件描述符),IPC标识它是一个非的整数。
     3、IPC对象必须要先创建,创建后才能进程获取、设置、操作、删除。
     4、创建IPC对象必须要提供一个键值(key_t),键值是创建、获取IPC对象的依据。
     5、产生键值的方法:
      固定的字面值:1980014
      使用函数计算:键值=ftok(项目路径,项目id)
      使用宏让操作系统随机分配:IPC_PRIVTE
       必须把获取到IPC对象标识符记录下来,告诉其它进程
     6、XSI可以创建的IPC对象有:
      共享内存,消息队列,信号量
    四、共享内存
     1、由内存维护一个共享的内存区域,其它进程把自己的虚拟地址映射到这块内存,然后多个进程之间就可以共享这块内存了。
     2、这种进程间通信的好处是不需要信息复制,是进程间通信最快的一种方式。
     3、但这种通信方式会面临同步的问题,需要与其它通信方式配合,最合适的就是信号。
     
     共享内存的编程模式:
      1、进程之间要约定一个键值
      进程A  进程B 
      创建共享内存  
      加载共享内存 加载共享内存
      卸载共享内存 卸载共享内存
      销毁共享内存
     
     int shmget(key_t key, size_t size, int shmflg);
     功能:创建共享内存
     size:共享的大小,尽量是4096的位数
     shmflg:IPC_CREAT|IPC_EXCL
     返回值:IPC对象标识符(类似文件描述符)
     
     void *shmat(int shmid, const void *shmaddr, int shmflg);
     功能:加载共享内存(进程的虚拟地址与共享的内存映射)
     shmid:shmget的返回值
     shmaddr:进程提供的虚拟地址,如果为NULL,操作系统会自动选择一块地址映射。
     shmflg:
      SHM_RDONLY:限制内存的权限为只读
      SHM_REMAP:映射已经存的共享内存。
      SHM_RND:当shmaddr为空时自动分配
      SHMLBA:shmaddr的值不能为空,否则出错
     返回值:映射后的虚拟内存地址
      
     int shmdt(const void *shmaddr);
     功能:卸载共享内存(进程的虚拟地址与共享的内存取消映射关系)
     
     int shmctl(int shmid, int cmd, struct shmid_ds *buf);
     功能:控制/销毁共享内存
     cmd:
      IPC_STAT:获取共享内存的属性
      IPC_SET:设置共享内存的属性
           IPC_RMID:删除共享内存
        buf:
         记录共享内存属性的对象
         
    五、消息队列
     1、消息队列是一个由系统内核负责存储和管理、并通过IPC对象标识符获取的数据链表。
     
     int msgget(key_t key, int msgflg);
     功能:创建或获取消息队列
     msgflg:
      创建:IPC_CREAT|IPC_EXEC
      获取:0
      
     int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
     功能:向消息队列发送消息
     msqid:msgget的返回人值
     msgp:消息(消息类型+消息内容)的首地址
     msgsz:消息内存的长度(不包括消息类型)
     msgflg:
      MSG_NOERROR:当消息的实际长比msgsz还要长的话,
       则按照msgsz长度截取再发送,否则产生错误。
       
        ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
        功能:从消息队列接收消息
        msgp:存储消息的缓冲区
        msgsz:要接收的消息长度
        msgtyp:消息的的类型(它包含消息的前4个字节)
        msgflg:
         MSG_NOWAIT:如果要接收的消息不存在,直接返回。
          否则阻塞等待。
         MSG_EXCEPT:从消息队列中接收第一个不msgtyp类型的第一个消息。
         
        int msgctl(int msqid, int cmd, struct msqid_ds *buf);
     功能:控制/销毁消息队列
     cmd:
      IPC_STAT:获取消息队的属性
      IPC_SET:设置消息队列的属性
      IPC_RMID:删除消息队列
     
     
        
         
      
       
      
     
  • 相关阅读:
    CreateRemoteThread注入DLL
    远程线程注入引出的问题
    jQuery中排除指定元素,同时选择剩下的所有元素
    YUIDoc的使用方法小结
    实验二 栈和队列的应用
    实验一 线性表的基本操作
    最大子段和详解
    HDOJ 1995 汉诺塔V
    错排公式 详细解答
    HDOJ 2212 DFS
  • 原文地址:https://www.cnblogs.com/mingyoujizao/p/9439346.html
Copyright © 2011-2022 走看看