【问题标题】:Synchronization with child signal and parent matches received signals with the file与子信号同步,父信号将接收到的信号与文件匹配
【发布时间】:2019-11-16 14:03:37
【问题描述】:

编辑: 感谢您迄今为止提出的建议。我更改了程序,现在父级处理了一些信号,但看起来它并没有处理所有这些信号。新代码和结果发布在下面。

编辑2: 我按照建议更改了随机数生成。现在父级只捕获两个信号,但它总是捕获正确的位(最后两个位)。

“不幸的是,我在 C POSIX 方面没有经验,我必须编写一个程序,该程序将接受一个参数(包含二进制数的文件名)并解析该文件。文件中表示的每一位都意味着应该创建一个孩子(每个位专用于一个子级)。
位的值(0 或 1)决定应将哪些信号发送给父级(0 - SIGUSR1, 1 - SIGUSR2)。
子进程应选择一个随机间隔(10-200ms)并向父进程发送适当的信号。

每次新信号到达时,父级应接收信号并打印接收到的最后 5 位。

最后一步是匹配过程 - 父进程检查接收到的信号(分配给 SIGUSR1 或 SIGUSR2 的位),如果匹配,则打印成功。如果没有匹配(无论何时发送错误的位 - 与文件相比),父级从头开始匹配。”

更新版本:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <signal.h>
volatile sig_atomic_t last_signal = 0;

char * str;
char * received;
int count = 0;

#define ERR(source) (fprintf(stderr,"%s:%d\n",__FILE__,__LINE__),\
                     perror(source),kill(0,SIGKILL),\
                                     exit(EXIT_FAILURE))

void sethandler( void (*f)(int), int sigNo)
{
        struct sigaction act;
        memset(&act, 0, sizeof(struct sigaction));
        act.sa_handler = f;
        if (-1==sigaction(sigNo, &act, NULL)) ERR("sigaction");
}


char *readFile(char *fileName)
{
    FILE *file = fopen(fileName, "r");
    char *code;
    size_t n = 0;
    int c;

    if (file == NULL) return NULL; //could not open file
    fseek(file, 0, SEEK_END);
    long f_size = ftell(file);
    fseek(file, 0, SEEK_SET);
    code = malloc(f_size);
    received = malloc(f_size);
    while ((c = fgetc(file)) != EOF) {
        code[n++] = (char)c;
    }

    code[n] = '\0';

    return code;
}

void append(char* s, char c)
{
        int len = strlen(s);
        s[len] = c;
        s[len+1] = '\0';
}

static void sig_handle(int signum)
{
  last_signal = signum;
}

void child_w(int number_of)
{
    if(str[number_of] == '0')
    {
      if (kill(getppid(), SIGUSR1)==0) printf("[SIGUSR1] sent \n");
      else
      {
        printf("ERROR kill. \n");
        exit(EXIT_FAILURE);
      }
    }

    if(str[number_of] == '1')
    {
      if (kill(getppid(), SIGUSR2) == 0) printf("[SIGUSR2] sent \n");
      else
      {
        printf("ERROR kill. \n");
        exit(EXIT_FAILURE);
      }
    }
}

void create_children(int n)
{
  pid_t s;
  int j = n;
  int time = rand() % 191 + 10;  // range 10 - 200
  struct timespec time_wait = { .tv_sec = 0, .tv_nsec = time * 1000000L };
       while(j-->0)
       {
         nanosleep(&time_wait, NULL);
         if((s=fork())<0) ERR("Fork ERROR");
         if(!s) {
                  printf("Child %d started ", j);
                  printf("with bit: %c \n", str[j]);
                  child_w(j);
                 exit(EXIT_SUCCESS);
         }
       }
}

void parent_w(sigset_t oldmask)
{
    int count = 0;
    int match = 0;
    while(1)
    {
      last_signal = 0;
      while(last_signal != SIGUSR1 && last_signal != SIGUSR2)
      {
        sigsuspend(&oldmask);
      }
      printf("\n");
      if(last_signal == SIGUSR1)
      {
        received[count] = '0';
        for(int i=0; i<sizeof(received); ++i)
        {
          printf("%c ", received[i]);
        }
        count++;
      }

      else if(last_signal == SIGUSR2)
      {
        received[count] = '1';
        for(int i=0; i<sizeof(received); ++i)
        {
          printf("%c ", received[i]);
        }
        count++;
      }
      printf("\n");
    }
}

int main(int argc, char ** argv)
{
  char filename[250];

  if(argc!=2)
  {
    printf("Provide one parameter - filename. \n");
    return EXIT_FAILURE;
  }
  strcpy(filename, argv[1]);

  str = readFile(filename);
  printf("FILE: ");
  for(int i=0; i<sizeof(str); ++i)
  {
    printf("%c ", str[i]);
  }
  printf("\n");

  for(int i=0; i<sizeof(received); ++i)
  {
    received[i] = '-';
  }

  sethandler(sig_handle, SIGUSR1);
  sethandler(sig_handle, SIGUSR2);

  sigset_t mask, oldmask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGUSR1);
    sigaddset(&mask, SIGUSR2);
    sigprocmask(SIG_BLOCK, &mask, &oldmask);

  create_children(sizeof(str));

  parent_w(oldmask);

  sigprocmask(SIG_UNBLOCK, &mask, NULL);
  free(str);
  free(received);

  return EXIT_SUCCESS;
}

现在输出总是如下所示:

FILE: 1 0 0 1 1 0 1 0 
Child 7 started with bit: 0 
[SIGUSR1] sent 
Child 6 started with bit: 1 
[SIGUSR2] sent 
Child 5 started with bit: 0 
[SIGUSR1] sent 
Child 4 started with bit: 1 
[SIGUSR2] sent 
Child 3 started with bit: 1 
[SIGUSR2] sent 
Child 2 started with bit: 0 
[SIGUSR1] sent 
Child 1 started with bit: 0 
[SIGUSR1] sent 

0 - - - - - - - 
Child 0 started with bit: 1 
[SIGUSR2] sent 

0 1 - - - - - - 

任何进一步的建议将不胜感激:)。

【问题讨论】:

  • signal(SIGUSR2, sig_handle2); 移动到main() 。处理SIGUSR1 是因为您只从parent_w 执行signal(SIGUSR1, sig_handle1);(并且在循环中,将其从循环中删除),所以只有SIGUSR1处理程序已注册。
  • 如果您正确使用sigaction(),那么您不需要继续重新注册您的处理程序。他们会坚持,如果你指定他们应该这样做。 signal() 函数不一定是这样,在大多数情况下确实应该避免使用。
  • 感谢您的建议。我改变了程序,它工作得更好一点。检查编辑和新代码:)

标签: c arrays linux synchronization posix


【解决方案1】:

除了其他人提到的问题之外,您的 readFile() 函数通过超出您为文件内容分配的缓冲区来调用未定义的行为:

char *readFile(char *fileName)
{
    FILE *file = fopen(fileName, "r");
    char *code;
    size_t n = 0;
    int c;

    if (file == NULL) return NULL; //could not open file
    fseek(file, 0, SEEK_END);
    long f_size = ftell(file);
    fseek(file, 0, SEEK_SET);
    code = malloc(f_size);
    received = malloc(f_size);
    while ((c = fgetc(file)) != EOF) {
        code[n++] = (char)c;
    }

    code[n] = '\0';  // <- this is f_size + 1 bytes into the code array

    return code;
}

当您使用code[n] = '\0'; 终止数据时,您写入的数据超出了code 指向的缓冲区末尾,从而调用了未定义的行为。

还有,题外话……

严格来说,您不能使用fseek()/ftell() 来获取文件的大小。在您的情况下,您使用 FILE *file = fopen(fileName, "r");text 模式下打开文件,但在 ftell() 的文本模式下 not 返回字节偏移量。每7.21.9.4 The ftell function, paragraph 2 of the C11 standard

ftell 函数获取stream 指向的流的文件位置指示符的当前值。对于二进制流,该值是从文件开头开始的字符数。对于文本流,其文件位置指示符包含未指定的信息,可由 fseek 函数用于将流的文件位置指示符返回到 ftell 调用时的位置;两个这样的返回值之间的差异不一定是衡量写入或读取字符数的有意义指标

在 POSIX 系统上,您不会有任何问题,因为 POSIX 将 ftell() 定义为始终返回准确的字节偏移量。但在 Windows 上,您可能会读取 更少 个字节,否则文件大小会显示为 \r\n 字符序列实际上在文件内容中被读取为单个 \n 字符。

但在某些系统上,您将真正获得“未指定的信息”,您的代码将完全失败。

并且寻找二进制流的末尾也不是可移植的。其实it's explicitly undefined behavior

将文件位置指示器设置为文件结尾,与 fseek(file, 0, SEEK_END) 一样,对于二进制流具有未定义的行为...

同样,在 POSIX 或 Windows 系统上不是问题。

fseek()/ftell() 的一个真正问题是,从ftell() 返回的long 值在许多系统上没有足够的范围来表示大于 2 GB 的文件大小。 long 在 32 位 Linux 系统上是 32 位,在 所有 Windows 系统(包括 32- 64 位)上也只有 32 位。

【讨论】:

    【解决方案2】:

    正如@KamilCuk 所观察到的,在信号处理程序触发时重新注册它们是没有必要或不合适的。由于 signal() 函数的实现不确定(直到今天),这曾经是标准的:一些实现注册处理程序,以便在它们触发一次后,信号处理被重置。但是,使用sigaction(),once 可以指定他们是否想要“一次性”行为,或者他们是否希望信号处理程序在触发时保持注册状态,后者是该函数的默认设置。

    sigaction() 允许控制signal() 实现不同的一些其他细节。在实践中,signal() 很少有合适的用途,sigaction() 也不能涵盖。如果您正在为 POSIX 编程,那么最好忘记 signal() 的存在。

    尽管如此,我认为您对signal() 的使用并不是这里的关键问题。


    另一个问题是信号处理程序在他们可以安全地做的事情上受到相当的限制:

    • 他们可以访问sig_atomic_t 类型的文件范围变量
    • 他们可能会调用异步信号安全标准函数
    • 他们可以声明和访问局部变量
    • 他们可以调用程序的任何其他符合这些限制的函数

    但是,信号处理程序通常使用自己的独立堆栈调用,而且这些堆栈通常非常小,因此在实践中,它们不能安全地以局部变量的方式声明,也不能启动非常深的调用树。确切的限制是什么尚未明确,因此信号处理程序通常应尽可能少做。

    特别是,printf() 和任何其他 stdio 函数都不是异步信号安全的。如果调用其中任何一个,信号处理程序会产生未定义的行为。如果您愿意,他们可能会致电write(),但这里可能有更好的选择。例如,父级可以pause()sigsuspend() 等待信号,然后在处理程序外部打印它需要做的任何事情。处理程序只需要设置一个变量来指示接收到的信号。这将避免父母像现在一样忙于等待,尽管您仍然存在潜在冲突的问题。

    这更有可能是您的问题的一部分,但我怀疑这也不是关键问题。


    我认为真正的问题可能是信号丢失了。普通信号不会排队,因此如果在该信号已经为进程挂起时接收到一个信号,那么它没有额外的影响。该问题的结构是通过要求每个孩子在发出信号之前延迟随机时间量来避免这种情况,但是

    1. 这并不真正安全,只是不太可能发生碰撞,并且
    2. 您的实施实际上并没有延迟。

    考虑:

      int time = rand()%100 + 10;
      struct timespec time_wait = {time/1000, 0};
      nanosleep(&time_wait, NULL);
    

    变量 time 将被分配一个介于 10 和 109 之间的值,因此 time / 1000(整数除法)将始终计算为 0。 p>

    这样的东西会更合适:

    int time = rand() % 191 + 10;  // range 10 - 200
    struct timespec time_wait = { .tv_sec = 0, .tv_nsec = time * 1000000L };
    nanosleep(&time_wait, NULL);
    

    此外,我不会在每个子节点中播种一个单独的 (P)RNG,而是在父节点中播种一次,并在每个分叉之前在那里生成延迟。从同一个 RNG 中抽取随机数会产生更均匀的分布。

    【讨论】:

    • 感谢您的建议。我更新了代码,现在它工作得更好了。已更改的所有内容都在已编辑的问题中:)
    • @daniel_p,虽然您已经解决了nanosleep() 参数的问题,但您使随机数生成问题变得更糟。以前,您在每个孩子中为 PRNG 播种了不同的种子,但您已将其删除。如果您还实施了我的建议,即在父进程中生成所有随机数,那就可以了,但您没有。就目前的代码而言,孩子们都将从相同的种子开始并产生相同的随机延迟,这使您(仍然)很可能丢失信号。
    • 好点!我错过了您建议将随机数生成移至父进程的最后一部分。我会看看并尝试改变它:)
    • 我更改了随机数生成。现在信号按顺序发送。不幸的是,父级只捕获了两个信号(两位)——我还更新了代码。
    • @daniel_p,问题中现在出现的代码只计算一个延迟,并将其用于所有孩子。而且,现在是父母睡觉,而不是要求的孩子。在分叉之前,在父节点的每个循环迭代上计算一个新的延迟,但在分叉之后,在子节点中不要休眠。就目前而言,由于您在所有孩子都开始之前阻止信号,并给他们足够的时间来发送信号,所以当您尝试接收它们时,所有孩子都已采取行动,并且再次,大部分信号都丢失了。
    【解决方案3】:

    首先,好的代码。

    第二:

    • 手册页是您的朋友。 man signal
    • signal() 注册 信号处理程序。所以signal(SIGUSR1, some_function)之后函数some_function会在收到信号后执行。
    • 从信号处理程序中删除 signal() 调用(为什么要从处理程序内部重新注册相同的处理程序?它已经是此信号的处理程序。)
    • 从父循环中删除signal() 调用。只需注册一次函数即可。
    • 您的sethandler 功能与signal 相同。

    经过一些修复:

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <sys/time.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <string.h>
    #include <time.h>
    #include <signal.h>
    
    char * str; // the array consisting of bits from the file
    char * received; //array of the parent receiving the signals
    int count = 0;
    //Error macro
    #define ERR(source) (fprintf(stderr,"%s:%d\n",__FILE__,__LINE__),\
                         perror(source),exit(EXIT_FAILURE))
    
    //Reading file char by char and returning allocated char array
    char *readFile(char *fileName)
    {
        FILE *file = fopen(fileName, "r");
        char *code;
        size_t n = 0;
        int c;
    
        if (file == NULL) return NULL; //could not open file
        fseek(file, 0, SEEK_END);
        long f_size = ftell(file);
        fseek(file, 0, SEEK_SET);
        code = malloc(f_size);
        received = malloc(f_size);
        while ((c = fgetc(file)) != EOF) {
            code[n++] = (char)c;
        }
    
        code[n] = '\0';
    
        return code;
    }
    // Append the character to the received array
    void append(char* s, char c)
    {
            int len = strlen(s);
            s[len] = c;
            s[len+1] = '\0';
    }
    // SIGUSR1 handler. I tried to implement simple counter to check if the parent is receiving the signals, then proceed to printing last 5 bits received. Unfortunately this part seems to not work at all.
    static void sig_handle1(int signum) {
      count++;
      printf("%s %d \n", __func__, count);
    
    }
    // Handler for SIGUSR2 - same as handler for SIGUSR1
    static void sig_handle2(int signum) {
      count++;
      printf("%s %d \n", __func__, count);
    
    }
    // Child function - set the random interval, wait and then send the appropriate signal to the parent
    void child_w(int number_of)
    {
      srand(time(NULL)*getpid());
      int time = rand()%100 + 10;
      struct timespec time_wait = {time/1000, 0};
      nanosleep(&time_wait, NULL);
    
        if(str[number_of] == '0')
        {
          if (kill(getppid(), SIGUSR1)==0) printf("[SIGUSR1] sent \n");
          else
          {
            printf("ERROR kill. \n");
            exit(EXIT_FAILURE);
          }
        }
    
        if(str[number_of] == '1')
        {
          if (kill(getppid(), SIGUSR2) == 0) printf("[SIGUSR2] sent \n");
          else
          {
            printf("ERROR kill. \n");
            exit(EXIT_FAILURE);
          }
        }
    }
    // Function which will create children (number of children = number of bits in the file)
    void create_children(int n)
    {
      pid_t s;
      int j = n;
           while(j-->0)
           {
             if((s=fork())<0) ERR("Fork ERROR");
             if(!s) {
                      printf("Child %d started ", j);
                      printf("with bit: %c \n", str[j]);
                      child_w(j);
                     //if(j==1) kill(getppid(), SIGUSR2);
                     exit(EXIT_SUCCESS);
             }
           }
    }
    // Parent function to check the received signals
    void parent_w()
    {
            signal(SIGUSR1, sig_handle1);
            signal(SIGUSR2, sig_handle2);
      while(1)
      {
              pause();
    
      }
    }
    
    int main(int argc, char ** argv)
    {
      char filename[250];
    
      if(argc!=2)
      {
        printf("Provide one parameter - filename. \n");
        return EXIT_FAILURE;
      }
      strcpy(filename, argv[1]);
    
      str = readFile(filename);
      printf("FILE: ");
      for(int i=0; i<sizeof(str); ++i)
      {
        printf("%c ", str[i]);
      }
      printf("\n");
    
      create_children(sizeof(str)-1);
      parent_w();
    
    
      free(str);
      free(received);
    
      return EXIT_SUCCESS;
    }
    

    示例执行:

    FILE: 1 0 0 1 1 0 1
    
    Child 0 started with bit: 1
    Child 1 started with bit: 0
    Child 2 started with bit: 0
    Child 3 started with bit: 1
    [SIGUSR2] sent
    sig_handle2 1
    [SIGUSR1] sent
    sig_handle1 2
    [SIGUSR1] sent
    sig_handle1 3
    [SIGUSR2] sent
    sig_handle2 4
    Child 4 started with bit: 1
    Child 5 started with bit: 0
    sig_handle2 5
    [SIGUSR2] sent
    Child 6 started with bit: 1
    sig_handle1 6
    [SIGUSR1] sent
    sig_handle2 7
    [SIGUSR2] sent
    ^C
    

    【讨论】:

    • 如果他们使用signal() 注册处理程序,他们将在处理程序内部重新注册处理程序,并且他们对signal() 的实现提供了一次性注册。这曾经是一种标准做法,但 sigaction() 的引入使得它变得不必要了。
    • 此外,这里提供的修改后的代码继承了原始代码中的几个关键问题。根本问题不太可能是信号注册。
    • 感谢您的建议。我更新了代码,现在它工作得更好了。已更改的所有内容都在已编辑的问题中:)
    猜你喜欢
    • 2013-05-30
    • 1970-01-01
    • 2015-08-19
    • 1970-01-01
    • 1970-01-01
    • 2011-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多