不可重入函数,信号量,可重入函数

版权声明:转载请附带原博主的网址 https://blog.csdn.net/qq_43260665/article/details/88881407

在了解了一下不/可重入函数以及互斥锁,信号量后,写了这篇blog。
先看一下这个程序:

void fun(int *val)
{
    *val+=1;
    return ;
}


int main()
{
    static int val=0;//or int val=0;
    /*       */
    fun(&val);
    return 0;
}

函数fun对传入的参数加1。此函数若被多个进程调用的话,其结果可能是未知的,因为当/* */语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将val值已经加了1,此时该进恰好执行到fun,此时val已经为2,这样很可能使程序出错。由于静态全局变量作用域是整个文件,全局变量作用域是整个工程,任何其他地方对val都有可能修改,使进程执行混乱。

在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果有一个函数不幸被设计成为这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。
相反,肯定有一个安全的函数,这个安全的函数又叫可重入函数。那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。
一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括 static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括 static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。
编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。

保证函数的可重入性的方法:
-在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量);
-对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。

满足下列条件的函数多数是不可重入(不安全)的:
1)函数体内使用了静态的数据结构;
2)函数体内调用了malloc() 或者 free() 函数;
3)函数体内调用了标准 I/O 函数。

如何将一个不可重入的函数改写成可重入函数呢?把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写它。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的:
1)不要使用全局变量。因为别的代码很可能改变这些变量值。
2)在和硬件发生交互的时候,切记执行类似 disinterrupt() 之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/ 退出核心”。
3)不能调用其它任何不可重入的函数。
4)谨慎使用堆栈。

以上引用来自blog:https://blog.csdn.net/lianghe_work/article/details/47611961

我们再来看一个例子,这个例子在多线程中我已经讲过:
bolg地址:https://mp.csdn.net/mdeditor/88833136#

#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
static int val=10;
//struct semaphore sem;
//sema_init(&sem,1);//only one thread can get sem

void *worker_s(void *args)
{
    int *val_s=(int *)args;
    int num=0;
    while(num!=4)
    {
        printf("++++++++before val++:val=%d\n",val);
        *val_s+=1;
        sleep(1);
        printf("++++++++after val++:val=:%d\n",val);
        num++;
    }
    return NULL;
}
void *worker_t(void *args)
{
    int *val_s=(int *)args;
    int num=0;
    while(num!=4)
    {
        printf("---------before val++:val=%d\n",val);
        *val_s+=1;
        sleep(1);
        printf("---------after val++:val=:%d\n",val);
        num++;
    }
    return NULL;
}

int main()
{
    pthread_t       tid_s;
    pthread_t       tid_t;
    if(pthread_create(&tid_s,NULL,worker_s,&val)!=0)
    {
        printf("pthread_create error:%d:%s\n",errno,strerror(errno));
        return -1;
    }
    if(pthread_create(&tid_t,NULL,worker_t,&val)!=0)
    {
        printf("pthread_create error:%d:%s\n",errno,strerror(errno));
        return -1;
    }
    pthread_join(tid_s,NULL);
    pthread_join(tid_t,NULL);
    return 0;
}

compile and run:

zhanghang@Ubuntu-14:~$ gcc test.c -lpthread
zhanghang@Ubuntu-14:~$ ./a.out 
---------before val++:val=10
++++++++before val++:val=11
---------after val++:val=:12
---------before val++:val=12
++++++++after val++:val=:13
++++++++before val++:val=13
---------after val++:val=:14
---------before val++:val=14
++++++++after val++:val=:15
++++++++before val++:val=15
---------after val++:val=:16
---------before val++:val=16
++++++++after val++:val=:17
++++++++before val++:val=17
---------after val++:val=:18
++++++++after val++:val=:18

++++和—表示两个线程空间的执行,可以看出,运行第三条就开始出现线程的混乱,—val=10直接变成—val=12。这同样可以说明问题,线程空间对val值的改变,会影响到另一线程的运作,在线程这章,我们称val为临界资源,多个线程都要用到它,为使临界资源不因为争夺而使临界资源反常的变化,我们使用到线程的互斥锁:

pthread_mutex_init();
pthread_mutex_lock();
pthread_mutex_unlock();
pthread_mutex_trylock();
......

加持互斥锁后,程序:

#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>

typedef struct mutex_s
{
    int *val;
    pthread_mutex_t lock;
}mutex_t;

static int val=10;

void *worker_s(void *args)
{
    mutex_t *mutex=(mutex_t *)args;
    pthread_mutex_lock(&mutex->lock);
    int num=0;
    while(num!=4)
    {
        printf("++++++++before val++:val=%d\n",val);
        *(mutex->val)+=1;
        sleep(1);
        printf("++++++++after val++:val=:%d\n",val);
        num++;
    }
    pthread_mutex_unlock(&mutex->lock);
    return NULL;
}
void *worker_t(void *args)
{
    mutex_t *mutex=(mutex_t *)args;
    while(pthread_mutex_trylock(&mutex->lock)!=0)
    {
        continue;
    }
    int num=0;
    while(num!=4)
    {
        printf("---------before val++:val=%d\n",val);
        *(mutex->val)+=1;
        sleep(1);
        printf("---------after val++:val=:%d\n",val);
        num++;
    }
    pthread_mutex_unlock(&mutex->lock);
    return NULL;
}

int main()
{
    pthread_t       tid_s;
    pthread_t       tid_t;
    mutex_t mutex;
    mutex.val=&val;
    pthread_mutex_init(&mutex.lock,NULL);
    if(pthread_create(&tid_s,NULL,worker_s,&mutex)!=0)
    {
        printf("pthread_create error:%d:%s\n",errno,strerror(errno));
        return -1;
    }
    if(pthread_create(&tid_t,NULL,worker_t,&mutex)!=0)
    {
        printf("pthread_create error:%d:%s\n",errno,strerror(errno));
        return -1;
    }
    pthread_join(tid_s,NULL);
    pthread_join(tid_t,NULL);
    return 0;
}

compile and run:

zhanghang@Ubuntu-14:~$ gcc test.c -lpthread
zhanghang@Ubuntu-14:~$ ./a.out 
---------before val++:val=10
---------after val++:val=:11
---------before val++:val=11
---------after val++:val=:12
---------before val++:val=12
---------after val++:val=:13
---------before val++:val=13
---------after val++:val=:14
++++++++before val++:val=14
++++++++after val++:val=:15
++++++++before val++:val=15
++++++++after val++:val=:16
++++++++before val++:val=16
++++++++after val++:val=:17
++++++++before val++:val=17
++++++++after val++:val=:18

互斥锁在这里,对全局静态变量上锁,同一空间只能有一个线程访问该变量,拿到锁的人,重要不释放锁,所有人都不能访问该变量,就不会出现第一个示例的情况。互斥锁(如上文),文件锁:参考博客:https://mp.csdn.net/mdeditor/88823011#也是类似信号量机制的一种。

相关blog:https://blog.csdn.net/huangweiqing80/article/details/83038154
(忍住尴尬,找错资料)

接下来看看Linux中为我们提供的信号量机制函数:

信号量、同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已。但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通信的信号量的详细介绍可以参阅我的另一篇博文:Linux进程间通信——使用信号量。相似地,线程同步是控制线程执行和访问临界区域的方法。
一、什么是信号量
线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。
而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。
二、信号量的接口和使用
信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。
1、sem_init函数
该函数用于创建信号量,其原型如下:

int sem_wait(sem_t *sem);

该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.
2、sem_wait函数
该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:

int sem_wait(sem_t *sem);

sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.
3、sem_post函数
该函数用于以原子操作的方式将信号量的值加1。它的原型如下:

int sem_post(sem_t *sem);

与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.
4、sem_destroy函数
该函数用于对用完的信号量的清理。它的原型如下:成功时返回0,失败时返回-1.

int sem_destroy(sem_t *sem);

三、使用信号量同步线程
改写用信号量改写上面的线程函数:

#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<semaphore.h>
static int val=10;

sem_t sem;

void *worker_s(void *args)
{
    int *val_s=(int *)args;
    int num=0;
    sem_wait(&sem);
    while(num!=4)
    {
        printf("++++++++before val++:val=%d\n",val);
        *val_s+=1;
        sleep(1);
        printf("++++++++after val++:val=:%d\n",val);
        num++;
    }
    sem_post(&sem);
    return NULL;
}
void *worker_t(void *args)
{
    int *val_s=(int *)args;
    int num=0;
    sem_wait(&sem);
    while(num!=4)
    {
        printf("---------before val++:val=%d\n",val);
        *val_s+=1;
        sleep(1);
        printf("---------after val++:val=:%d\n",val);
        num++;
    }
    sem_post(&sem);
    return NULL;
}

int main()
{
    pthread_t       tid_s;
    pthread_t       tid_t;
    sem_init(&sem,0,1);
    if(pthread_create(&tid_s,NULL,worker_s,&val)!=0)
    {
        printf("pthread_create error:%d:%s\n",errno,strerror(errno));
        return -1;
    }
    if(pthread_create(&tid_t,NULL,worker_t,&val)!=0)
    {
        printf("pthread_create error:%d:%s\n",errno,strerror(errno));
        return -1;
    }
    pthread_join(tid_s,NULL);
    pthread_join(tid_t,NULL);
    return 0;
}

compile and run:

zhanghang@Ubuntu-14:~$ gcc test.c -lpthread
zhanghang@Ubuntu-14:~$ ./a.out 
---------before val++:val=10
---------after val++:val=:11
---------before val++:val=11
---------after val++:val=:12
---------before val++:val=12
---------after val++:val=:13
---------before val++:val=13
---------after val++:val=:14
++++++++before val++:val=14
++++++++after val++:val=:15
++++++++before val++:val=15
++++++++after val++:val=:16
++++++++before val++:val=16
++++++++after val++:val=:17
++++++++before val++:val=17
++++++++after val++:val=:18

有关信号量的知识暂时我就知道这么多,后续有待补充,上面的代码可能由于本人往后写的缘故,会有所变动。。。
参考blog:https://blog.csdn.net/ljianhui/article/details/10813469

猜你喜欢

转载自blog.csdn.net/qq_43260665/article/details/88881407