Linux下进程编程

2018-07-05  本文已影响106人  XDgbh
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;
}

子进程的异步清除

#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
上一篇 下一篇

猜你喜欢

热点阅读