美文网首页
Linux下进程编程

Linux下进程编程

作者: XDgbh | 来源:发表于2018-07-05 23:29 被阅读106次
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png

    示例 编写mysignal.c,然后编译运行

      1 #include <signal.h> //  处理信号的头文件
      2 #include <string.h>
      3 #include <sys/types.h>
      4 #include <unistd.h>
      5 #include <iostream>
      6
      7 //信号处理时的变量只能用这个支持原子操作的
      8 sig_atomic_t  sigusr1_count = 0;
      9 //因为linux中信号处理相关的函数时C语言写的,
     10 //因此也要用C语言来写我们自定义的信号处理函数
     11 extern "C"  {
     12
     13      void OnSigUsr1(int signal_number)
     14      {  ++sigusr1_count;  }
     15
     16  }
     17
     18 int main ()
     19 {
     20   std::cout  << "pid: " << (int)getpid() << std::endl;
     21   //信号结构体
     22   struct sigaction sa;
     23   memset( &sa, 0, sizeof(sa) );
     24   //将自定义的信号处理函数的指针赋值给结构体成员
     25   sa.sa_handler = &OnSigUsr1;
     26   //SIGUSR1 linux提供的可供用户自定义的信号,值为10信号编号。
     27   //设置信号配置函数
     28   sigaction( SIGUSR1, &sa, NULL );
     29   std::cout << "SIGUSR1 value: " << SIGUSR1 << std::endl; //将打印出10
     30   std::cout << "SIGUSR1 counts: " << sigusr1_count << std::endl;
     31   std::cout << "sleep 100 seconds ..." << std::endl;
     32   sleep( 100 );
     33   //  在终端中输入kill –s SIGUSR1 pid或者kill -10 pid,
     34   //  将使得本程序信号函数执行,信号计数器将递增。sleep立即终止,然后往下执行
     35   gtd::cout << std::endl<<"SIGUSR1 counts: " << sigusr1_count << std::endl;
     36   if(sigusr1_count == 1)
     37     std::cout<<"reseive SIGUSR1 success !"<<std::endl;
     38   return 0;
     39 }
    
    
    image.png

    在sleep100秒时,当前shell会被占用,可打开另一个shell,然后执行下图的任意一条命令来发送信号。

    image.png
    -s SIGUSR1 和 -10 是等价的。SIGUSR1是由宏定义的#define SIGUSR1 10即信号值为10。
    同理,在强制杀死一个进程时在命令行执行的#kill -9 pid等价于#kill -s SIGKILL pid

    进程管理

    image.png
    image.png

    fork()实例1

    #include <iostream>
    #include <sys/types.h>
    #include <unistd.h>
    using namespace std;
    int main ()
    {
      cout << "the main program process ID is " << (int)getpid() << endl;
      pid_t  child_pid = fork();
      //注意:fork之前的代码只有父进程执行,
      //fork之后的代码父子进程都有机会执行, 受代码逻辑的控制而进入不同分支。
      if( child_pid != 0 )
      {
        //只有父进程才能进的分支
        sleep(3);  //让父进程停3秒,看看子进程是否会先执行它的分支
        cout << "this is the parent process, with id " << (int)getpid() << endl;
        cout << "the child’s process ID is " << (int)child_pid << endl;
      }
      else
       {
          //child_pid ==0,只有子进程才能进的分支
          cout << "this is the child process, with id " << (int)getpid() <<endl;
        }  
        
       cout<<"父子进程都执行的代码。"<<endl;
       return 0;
    }
    
    

    编译执行结果


    image.png

    可见:fork之前的代码只有父进程执行,fork之后的代码父子进程都有机会执行, 受代码逻辑的控制而进入不同分支。父子进程分别异步执行,不再互相影响。

    fork()实例2

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
     
    int global = 100;
     
    int main (void) {
        int local = 200;
        char* heap = (char*)malloc (256 * sizeof (char));
        sprintf (heap, "ABC");
        printf ("父进程:%d %d %s\n", global, local, heap);
        pid_t pid = fork ();
        if (pid == -1) {
            perror ("fork");
            return -1;
        }
     
        if (pid == 0) {
            global++;
            local++;
            sprintf (heap, "XYZ");
            printf ("子进程:%d %d %s\n", global, local, heap);
            sleep(3);
        }
     
        printf ("父子进程都执行的代码:%d %d %s\n", global, local, heap);
        free (heap);
     
        return 0;
    }
    

    代码编译执行结果:


    image.png

    可见:子进程是父进程的副本,子进程获得父进程数据段和堆栈段(包括I/O流缓冲区)的拷贝,不是共享数据,只是子进程共享父进程的代码段。

    更多fork相关例子,可以看这篇博文:https://blog.csdn.net/meetings/article/details/47123359

    image.png
    image.png
    image.png
    image.png

    子进程成为僵尸进程的例子

    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    int main ()
    {
      pid_t child_pid;
      child_pid = fork();
      if( child_pid > 0 )   //  父进程,速度睡眠六十秒
        sleep( 60 );
      else          //  子进程,立即退出,没有让父进程获取到退出状态并清除
        exit( 0 );
      return 0;
    }
    

    子进程的异步清除

    • SIGCHLD信号:子进程终止时,向父进程自动发送,编写此信号处理例程,异步清除子进程。
    #include <signal.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    sig_atomic_t child_exit_status;
    extern "C"  {
        void CleanUp( int sig_num )
        {
            int status;
            wait( &status );        //  清除子进程
            child_exit_status = status; //  存储子进程的状态
        }
    }
    int main ()
    {
      //  处理SIGCHLD信号
      struct sigaction sa;
      memset( &sa, 0, sizeof(sa) );
      sa.sa_handler = &CleanUp;
      sigaction( SIGCHLD, &sa, NULL );
    
      //  正常处理代码在此,例如调用fork()创建子进程
    
      return 0;
    }
    
    image.png
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <linux/fs.h>
    
    int main()
    {
      pid_t pid = fork();
      if( pid == -1 )       
        return -1;
      else if( pid != 0 )       
        exit( EXIT_SUCCESS );  //父进程退出
      //  接下来就都是子进程运行
      if( setsid() == -1 )      
        return -2;
      //  设置工作目录
      if( chdir( "/" ) == -1 )  
        return -3;
      //  重设文件权限掩码
      umask( 0 );
      //  关闭文件描述符,因为只打开了默认的0,1,2三个输入流、输出流、错误流三个
      for( int i = 0; i < 3; i++ )  close( i );
      //  重定向标准流,将0,1,2都挂载到哑设备上,相当于什么都不干,不输入也不输出
      open( "/dev/null", O_RDWR );  // stdin
      dup( 0 );             // stdout
      dup( 0 );             // stderr
      //  守护进程的实际工作代码在此
      return 0;
    }
    

    进程间通信

    进程间通信

    要引用的头文件在linux目录/usr/include或者/usr/include/sys下:

    1、如要使用管道:#include <fcntl.h> 文件控制,<unistd.h> 一些默认的文件描述符0/1/2或符号常量等,如: image.png
    2、如要使用信号量:#include <sys/sem.h> semaphore信号量。

    3、如要使用共享内存:#include<sys/shm.h> share memory共享内存。
    4、如要使用映射内存:#include<sys/mman.h> memory mapping内存映射。
    5、如要使用消息队列:#include<sys/msg.h> message queue消息队列。
    6、如要使用socket套接字:#include<sys/socket.h>
    共用的几个:<sys/ipc.h> 进程间通信,<fcntl.h> 文件控制,<sys/types.h> 基本系统数据类型,<unistd.h> 多个宏定义的符号常量。

    image.png

    分页系统的核心在于:将虚拟内存空间和物理内存空间皆划分为大小相同的页面,如4KB、8KB或16KB等,并以页面作为内存空间的最小分配单位,一个程序的一个页面可以存放在任意一个物理页面里。
    分页系统的核心是页面的翻译,即从虚拟页面到物理页面的映射(Mapping)。该翻译过程如下伪代码所示:
    if(虚拟页面非法、不在内存中或被保护)
    {
    陷入到操作系统错误服务程序
    } else {
    将虚拟页面号转换为物理页面号
    根据物理页面号产生最终物理地址
    }

    image.png
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    
    const int buf_size = 4096;
    
    //  向stream中写入count次msg
    void Write( const char * msg, int count, FILE * stream )
    {
      for( ; count > 0; --count )
      {
        fprintf( stream, "%s\n", msg );
        fflush( stream );
        sleep (1);
      }
    }
    //  从stream中读取数据
    void Read( FILE * stream )
    {
      char buf[buf_size];
      //  一直读取到流的尾部
      while( !feof(stream) && !ferror(stream) && fgets(buf, sizeof(buf), stream) != NULL )
      {
        fprintf( stdout, "Data received: \n" );
        fputs( buf, stdout );
      }
    }
    
    int main()
    {
      int fds[2];
      pipe( fds );      //  创建管道
      pid_t pid = fork();   //  创建子进程
      if( pid == 0 )  {     //  子进程
        close( fds[1] );        //  只读取,关闭管道写入端
        //  将文件描述符转换为FILE *,以方便C/C++标准库函数处理
        FILE * stream = fdopen( fds[0], "r" );
        Read( stream );     //  从流中读取数据
        close( fds[0] );        //  关闭管道读取端
      }
      else if( pid > 0 )  { //  父进程
        char buf[buf_size]; //  数据缓冲区,末尾封装两个‘\0’
        for( int i = 0; i < buf_size-2; i++ )    buf[i] = 'A' + i % 26;
        buf[buf_size-1] = buf[buf_size-2] = '\0';
        close( fds[0] );        //  只写入,关闭管道读取端
        FILE * stream = fdopen( fds[1], "w" );
        Write( buf, 3, stream );
        close( fds[1] );        //  关闭管道写入端
      } 
      return 0;
    }
    
    image.png
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <fcntl.h>
    const int buf_size = 4096;
    int main ()
    {
      int fds[2];
      pipe( fds );      //  创建管道
      pid_t pid = fork();
      if( pid == (pid_t)0 ) //  子进程
      {
        close( fds[0] );        //  关闭管道读取端
        dup2( fds[1], STDOUT_FILENO );      //  管道挂接到标准输出流
        char * args[] = { "ls", "-l", "/", NULL };  //  使用“ls”命令替换子进程
        execvp( args[0], args );
      }
      else          //  父进程
      { 
        close( fds[1] );        //  关闭管道写入端
        char buf[buf_size];
        FILE * stream = fdopen( fds[0], "r" );  //  以读模式打开管道读取端,返回文件指针
        fprintf( stdout, "Data received: \n" );
        //  在流未结束,未发生读取错误,且能从流中正常读取字符串时,输出读取到的字符串
        while( !feof(stream) && !ferror(stream) && fgets(buf, sizeof(buf), stream) != NULL )
        {
          fputs( buf, stdout );
        }
        close( fds[0] );        //  关闭管道读取端
        waitpid( pid, NULL, 0 );    //  等待子进程结束
      }
      return 0;
    }
    
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png

    共享内存创建和连接示例

    #include <stdio.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    int main ()
    {
      struct shmid_ds  shmbuf;
      int seg_size;
      const int shared_size = 4096;    //一个内存页面的大小
      //  分配共享内存段
      int seg_id = shmget( IPC_PRIVATE, shared_size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR );
      //  连接共享内存段
      char * shared_mem = ( char * )shmat( seg_id, 0, 0 );
      printf( "Shared memory attached at %p\n", shared_mem );
      //  获取段尺寸信息
      shmctl( seg_id, IPC_STAT, &shmbuf );
      seg_size = shmbuf.shm_segsz;
      printf( "Segment size: %d\n", seg_size );
      //  向共享内存区段写入字符串
      sprintf( shared_mem, "Hello, world." );
      //  拆卸共享内存区段
      shmdt( shared_mem );
      //  在不同的地址处重新连接共享内存区段
      shared_mem = ( char * )shmat( seg_id, ( void * )0x5000000, 0 );
      printf( "Shared memory reattached at %p\n", shared_mem );
      //  获取共享内存区段中的信息并打印
      printf( "%s\n", shared_mem );
      //  拆卸共享内存区段
      shmdt( shared_mem );
      //  释放共享内存区段,与semctl类似
      shmctl( seg_id, IPC_RMID, 0 );
      return 0;
    }
    

    代码编译执行结果:


    image.png
    image.png
    image.png
    image.png

    映射内存实例:

    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <wait.h>
    #include <iostream>
    #include <iomanip>    //设定输出格式的
    const int mapped_size = 4096;
    const int mapped_count = mapped_size / sizeof(int);
    int main( int argc, char * const argv[] )
    {
      //  打开文件作为内存映射的对象,确保文件尺寸足够存储1024个整数
      int fd = open( argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );
      //将文件的指针(光标)定位到指定位置
      lseek( fd, mapped_size - 1, SEEK_SET );
      write( fd, "", 1 );
      lseek( fd, 0, SEEK_SET );
      //创建映射内存
      int * base = ( int * )mmap( 0, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, fd, 0 );
      close( fd );      //  创建映射内存后,关闭文件的文件描述符
      pid_t pid = fork();
      if( pid == (pid_t)0 ) //  子进程写入数据
      {
        //  写入数据0~1023
        for( int i = 0, * p = base; i < mapped_count; *p++ = i++ )
          ;
        //释放映射内存
        munmap( base, mapped_size );
      }
      else if( pid > (pid_t)0 ) // 父进程读取数据
      {
        sleep( 10 );        //  等待10秒
        for( int i = 0, *p = base; i < mapped_count; i++, p++ )
          std::cout << std::setw(5) << *p << " ";  //设定输出宽度w为5个字符位置
        std::cout << std::endl;
        munmap( base, mapped_size );
      }
      return 0;
    }
    

    使用共享内存和映射内存比管道的优势在于,管道一次最多只能传递一个内存页面大小的数据,而共享内存和映射内存的大小不限制而且还可以跟文件挂钩,比管道更灵活。

    image.png
    image.png

    用消息队列多用于传递短消息,不用于传递太长的复杂消息。

    image.png
    image.png
    image.png
    image.png
    image.png

    相关文章

      网友评论

          本文标题:Linux下进程编程

          本文链接:https://www.haomeiwen.com/subject/stupuftx.html