如何在linux中使用EINTR引发失败的semop调用?

时间:2018-04-27 15:35:21

标签: c semaphore

我试图用semop调用来诱导EINTR失败。

key_t semkey;
int semid;
struct sembuf sbuf;
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
} arg;
struct semid_ds ds;

/* Get unique key for semaphore. */
if ((semkey = ftok("/tmp", 'a')) == (key_t) -1) {
    perror("IPC error: ftok"); exit(1);
}
/* Get semaphore ID associated with this key. */
if ((semid = semget(semkey, 0, 0)) == -1) {
    /* Semaphore does not exist - Create. */
    if ((semid = semget(semkey, 1, IPC_CREAT | IPC_EXCL | S_IRUSR |
        S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) != -1)
    {
        /* Initialize the semaphore. */
        arg.val = 0;
        sbuf.sem_num = 0;
        sbuf.sem_op = 2;  /* This is the number of runs without queuing. */
        sbuf.sem_flg = 0;
        if (semctl(semid, 0, SETVAL, arg) == -1
            || semop(semid, &sbuf, 1) == -1) {
            perror("IPC error: semop"); exit(1);
        }
    }
    else if (errno == EEXIST) {
        if ((semid = semget(semkey, 0, 0)) == -1) {
            perror("IPC error 1: semget"); exit(1);
        }
        goto check_init;
    }
    else {
        perror("IPC error 2: semget"); exit(1);
    }
}
else
{
    /* Check that semid has completed initialization. */
    /* An application can use a retry loop at this point rather than
       exiting. */
    check_init:
    arg.buf = &ds;
    if (semctl(semid, 0, IPC_STAT, arg) < 0) {
        perror("IPC error 3: semctl"); exit(1);
    }
    if (ds.sem_otime == 0) {
        perror("IPC error 4: semctl"); exit(1);
    }
}

sbuf.sem_num = 0;
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
while (semop(semid, &sbuf, 1) == -1) 
{
    if (errno != EINTR)
    {
        perror("IPC Error: semop"); exit(1);
        break;
    }
}

我得到的最多是资源不可用失败或资源忙。我甚至尝试过在两个不同线程或两个不同进程中运行的多个信号量。但我能够让EINTR失败。当某个时候semop正在等待信号量时,我甚至尝试将信号作为SIGCHLD发送到进程。

根据zwol建议,

这是我尝试的但它仍然有效,我的意思是我无法获得EINTR。

int g_global_variable = 0;
void *sigusr1_block_thread (void *vargp)
{
    while (1)
    {
        sleep (10);
        printf ("sigusr1_block_thread\n");
    }
    return NULL;
}
void *semop_wait_thread (void *vargp)
{
    int sem;
    struct sembuf sops[2];

    if((sem = semget(IPC_PRIVATE, 1,  IPC_CREAT | 0600))==-1){
        return NULL;
    }
    if(semctl(sem,0,SETVAL,2)==-1){
        exit(1);
    }

    sops[0].sem_num=0;     
    sops[0].sem_op=-1;     
    sops[0].sem_flg=0;      

    sops[1].sem_num=0;     
    sops[1].sem_op=0;     
    sops[1].sem_flg=0;

    g_global_variable = 1;
    printf ("Starting semop call \n");
    if(eintr_check_semop(sem, sops,2)<0)
        printf("Error semop\n");

    return NULL;
}

int main()
{
    pthread_t tid, tid1, tid2, tid3, tid4;
    sigset_t set;
    int s;

    pthread_create(&tid, NULL, semop_wait_thread, NULL);
    pthread_create(&tid2, NULL, semop_wait_thread, NULL);
    pthread_create(&tid3, NULL, semop_wait_thread, NULL);
    pthread_create(&tid4, NULL, semop_wait_thread, NULL);


    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGCHLD);

    s = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (s != 0)
        printf ("Error during pthread_sigmask");

    pthread_create(&tid1, NULL, sigusr1_block_thread, NULL);


    while (1)
    {
        sleep (1);
        if (g_global_variable == 1)
        {
            sleep (10);
            printf ("Send SIGUSR1/SIGCHLD signals \n");
            /* Send signal */
            pthread_kill( tid, SIGCHLD);
            pthread_kill( tid2, SIGCHLD);
            pthread_kill( tid3, SIGCHLD);
            pthread_kill( tid4, SIGCHLD);
            pthread_kill( tid1, SIGCHLD);
            pthread_kill( tid1, SIGUSR1);
            break;
        }
        else
            continue;
    }

    pthread_join(tid, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_join(tid4, NULL);
    return 0;
}

eintr_check_semop只是一个检查semop错误和返回值的函数。如果EINTR打印出相同的消息。

如果我将sigusr1发送到阻塞线程(t,t2,t3,t4)semop call break并且是循环的。

我无论如何都没有得到EINTR。然后我检查了内核源代码。

https://elixir.bootlin.com/linux/latest/source/ipc/sem.c

在EINTR期间,我发现它们正在循环而不是报告相同。

1 个答案:

答案 0 :(得分:1)

EINTR仅在进程在阻塞系统调用时被阻塞时收到信号,并且该信号具有处理程序,并且该处理程序配置为中断而不是重新启动系统调用时才会发生。 (这个原则有一些例外,但没有一个涉及semop。)你的程序没有任何信号处理程序,所以即使你做了EINTR也不会发生发送信号。

我无法确切地告诉你如何做到这一点,但应该有效的整体模式是:

  1. 为某些信号建立信号处理程序。如果您没有理由选择其他特定信号,请使用SIGUSR1。使用sigaction执行此操作,而不是signal,并且SA_RESTART中不包含sa_flags。处理程序不必做任何事情;它必须存在。

  2. 如果程序有多个线程,请使用pthread_sigmask阻止除{1}之外的每个线程中的SIGUSR1

  3. SIGUSR1解除阻塞的线程中,执行semop操作,该操作将阻塞(具有非零值的信号量上的“等待零”,不{{1} }})。

  4. 以上线程在IPC_NOWAIT上明确被阻止,来自该程序中的另一个线程,请使用semoppthread_kill发送到阻止线。或者,从程序外部,使用常规SIGUSR1kill发送到整个过程;因为信号在一个线程中被解锁,该线程将接收信号。

  5. 最困难的部分是确保在发送信号之前线程在SIGUSR1被阻止。我不确定这是否可以从程序内部进行,没有竞争条件。