Linux 线程信号量,进程信号量和内核驱动程序信号量(线程同步信号量,进程同步信号量和设备驱动同步信号量)

时间:2023-02-21 21:29:45
1、进程与线程的区别

     新进程创建出该进程的一份新拷贝,所有的全局变量都是都是两份,父进程子进程各一份。新进程拥有PID号,时间调度上也是独立的。

     新线程拥有一个新的栈,所以他只是拥有自己的局部变量,而全局变量适合父进程共享的,全局变量只有一份。

2、宏_REENTRANT告诉编译器我们需要可重入功能

3、#include <pthread.h>  //大多数函数以pthread_t 开头

   int pthread_create(pthread_t * thread,thread_attr_t* attr,void *(*start_routine)(void *),void * arg);

   参数:thread指向pthread_t 的指针,

         attr 设置线程属性

         start_routine:指向返回void * 参数是void * 的函数,这个函数就是新线程要执行的内容

        arg:start_routine函数的参数

  void pthread_exit(void * retval);

  线程返回,retval即为线程函数的返回码

  int pthread_join(pthread_t th,void**thread_return);

  th要等待线程的thread_t 号,指向线程返回值的指针

4、信号量:

  #include <semaphore.h>

  int sem_init(sem_t * sem,int pshared,unsigned int value);

  参数:sem一个指向sem_t变量的指针

        pshared 0表示当前进城所有,非零表示进程间共有,一般取0

        value 信号量初始值

  int sem_wait(sem_t * sem);

  将sem指向的信号量减1

  int sem_post(sem_t * sem);

  将sem指向的信号量加1

  int sem_destroy(sem_t * sem);

  释放sem指向的信号量

5、互斥锁,一般用来保证一段代码区一个时间只有一个进程访问

  与信号量类似

  #include<pthread.h>

int pthread_mutex_init(pthread_mutex_t*mutex,const pthread_mutexattr_t * mutexattr);//第二个参数设置互斥锁属性,一般为NULL

  int pthread_mutex_lock(pthread_mutex_t *mutex);

  int pthread_mutex_unlock(pthread_mutex_t *mutex);

  int pthread_mutex_destroy(pthread_mutex_t *mutex);

信号量和互斥锁一般放在全局变量区供两个线程函数共同使用

说明:Linux对信号量提供了两组接口,上面的用于同步线程。还有一组同步进程

6、设置线程的属性

  #include <pthread.h>

  int pthread_attr_init(pthread_attr_t * attr);

  初始化一个线程使用对象,在设置线程属性前必须先调用这个函数

  具体设置函数略。。。可以查阅相关资料,不常使用

7、用于同步进程的信号量

  #include <sys/sem.h>

  int semget(key_t key,int num_sems,int sem_flags);

  作用:创建一个和key绑定的信号量,或者根据key得到信号量

  参数:key是整数值,不相关的进程通过它可以访问同一个信号量

       num_sems:需要信号量的数目,几乎总是取值1

        sem_flags:低九个比特类似于文件访问权限,和IPC_CREATE按位或来创建一个新的信号量,即使加了IPC_CREATE如果key的键值已经创建了信号量,函数不会创建新信号量,而是返回和key相关的信号量。通过和IPC_EXCL按位或,可以创建一个新的唯一的信号量,如果该信号量已经存在,返回一个错误。

  返回值:返回一个正数值,是其他信号量函数用到的信号量标识符

  int semop(int sem_id,struct sembuf *sem_ops,size_t num_sem_ops);

  作用:用于改变信号量的值

  参数:sem_id:semget的返回值

            sem_ops: struct sembuf数组指针

               struct sembuf至少包含以下成员:

               short sem_num;//信号量编号,如果不是使用一组信号量,值为0

               short sem_op;//一次操作需要改变的值一般为-1(P操作)或+1

//(V操作)

           short sem_flg;//通常被设置为SEM_UNDO,表示当进程在没有释放//信号量被终止的情况下,操作系统释放信号量

        num_sem_ops:数组成员个数,大于等于1

int semctl(int sem_id,int sem_num,int commod,…);

作用:直接设置信号量信息,比如信号量的值

参数:sem_id,semget函数的返回值

      sem_num,如果不是信号量组,该值为0

      commod:SETVAL,用来把信号量初始化为某个值

                       IPC_RMID,删除信号量

      如果还有第四个参数,一般为union semun 联合体

union semun {

   int val;//信号量的值

   struct semid_ds *buf;

   unsigned short * array;

}

该联合体一般有程序员自己定义

注意:文章结尾附有进程信号量的demo程序

 

/////////////////////////////////

以下为linux内核设备驱动中并发控制用的信号量,和进程信号量,线程信号量概念和实际应用效果是一样的,但实现的函数是不同的。

信号量

信号量的使用

信号量(semaphore)是用于保护临界区的一种常用方法,他的用法和自旋锁类似,但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转,而是进入休眠等状态。

Linux中信号量的操作主要有

1.定义信号量

struct semaphore sem;

2.初始化信号量

void sema_init(struct semaphore *sem, int val);

该函数初始化信号量,并设置信号量sem的值为val,尽管信号量可以被初始化为大于1的值而成为一个计数信号量,但是通常不建议这么去做。

#define init_MUTEX(sem)    sema_init(sem, 1)

这个宏用于初始化一个互斥的信号量,把sem的值设置为1.

#define init_MUTEX_LOCKED(sem)   sema_init(sem, 0)

初始化一个信号量,值设置为0;

也可以使用如下快捷方式初始化

DELCEAR_MUTEX(name)

DELCEAR_MUTEX_LOCKED(name)

3.获得信号量

void down(struct semaphore *sem);

该函数用于获得信号量sem,它会导致睡眠,因此不能再中断上下文中使用;

int down_interruptible(struct semaphore *sem);

该函数与down类似,因为down进入睡眠状态的进程不能被信号打断,但因为down_interruptible而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,返回非0;

int down_trylock(struct semaphone *sem);

该函数尝试获得信号量sem,如果能立即获得返回0,否则返回非0,不会导致调用者睡眠,可以再中断上下文中使用。

在使用down_interruptible()获得信号量时,对返回值一般会进行检查,如果非0,通常立即返回 -ERESTARTSYS

  1. if(down_interruptible(&sem))  
  2.     return -ERESTARTSYS;  


4.释放信号

void up(struct semaphore *sem);

该函数释放信号量sem,唤醒等待者。

用法

  1. DECLARE_MUTEX(mount_sem);  
  2. down(&mount_sem);   //get semaphore  
  3. ...  
  4. critical section  
  5. ...  
  6. up(&mount_sem);     //release semaphore  


举例使用信号量实现设备只能被一个进程打开

  1. static DECLARE_MUTEX(xxx_lock); //declare mutex lock  
  2.   
  3. static int xxx_open(struct ...)  
  4. {  
  5.     ...  
  6.     if(down_trylock(&xxx_lock))  
  7.         return -EBUSY;  
  8.     ...  
  9.     return 0;  
  10. }  
  11.   
  12. static int xxx_release(struct ...)  
  13. {  
  14.     up(&xxx_lock);  
  15.     return 0;  
  16. }  


信号量用于同步

如果信号量初始化为0, 则它可以用于同步,同步意味着一个执行单元的继续执行需要另外一个执行单元完成某事,保证执行的先后顺序。

下面一图说明此事

                                    执行单元A                                                                     执行单元B

                                    struct semphore sem;                                               

                                    init_MUTEX_LOCKED(&sem);                                 代码区域c

                                     代码区域a                         

                                     down(&sem);              <---------激活------                   up(&sem)

                                     代码区域b


在执行单元A中,因为互斥量被设置为0,所以在down的时候试图获得信号量,因为得不到而进入休眠,所以代码区域b一直无法执行到,当执行单元B 发生,执行up来释放互斥量,使得执行单元A被唤醒,从而继续往下执行。

 

 

以下为进程信号量的demo程序

Demo:(源自Linux程序设计)

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

#include"semun.h"

static int set_semvalue(void);

static void del_semvalue(void);

static int semaphore_p(void);

static int semaphore_v(void);

static int sem_id;

int main(int argc, char **argv)

{

    int i;

    int pause_time;

    char op_char = 'O';

    srand((unsigned int)getpid());

    sem_id = semget((key_t)1234, 1, 0666 |IPC_CREAT);

    if(argc > 1)

    {

        if(!set_semvalue())

        {

            fprintf(stderr, "Failed toinitialize semaphore/n");

            exit(EXIT_FAILURE);

        }

        op_char = 'X';

        sleep(2);

    }

2 然后我们使用一个循环代码进入并且离开临界区10次。此时会调用semaphore_p函数,这个函数会设置信号量并且等待程序进入临界区。

    for(i=0;i<10;i++)

    {

        if(!semaphore_p()) exit(EXIT_FAILURE);

        printf("%c", op_char);fflush(stdout);

        pause_time = rand() % 3;

        sleep(pause_time);

        printf("%c", op_char);fflush(stdout);

3 在临界区之后,我们调用semaphore_v函数,在随机的等待之后再次进入for循环之后,将信号量设置为可用。在循环之后,调用del_semvalue来清理代码。

        if(!semaphore_v()) exit(EXIT_FAILURE);

         pause_time = rand() % 2;

        sleep(pause_time);

    }

 

    printf("/n%d - finished/n",getpid());

 

    if(argc > 1)

    {

        sleep(10);

        del_semvalue();

    }

 

    exit(EXIT_SUCCESS);

    }

 

4 函数set_semvalue在一个semctl调用中使用SETVAL命令来初始化信号量。在我们使用信号量之前,我们需要这样做。

static int set_semvalue(void)

{

    union semun sem_union;

    sem_union.val = 1;

    if(semctl(sem_id, 0, SETVAL, sem_union) ==-1) return 0;

    return 1;

}

5 del_semvalue函数几乎具有相同的格式,所不同的是semctl调用使用IPC_RMID命令来移除信号量ID:

static void del_semvalue(void)

{

    union semun sem_union;

    if(semctl(sem_id, 0, IPC_RMID, sem_union)== -1)

        fprintf(stderr, "Failed to deletesemaphore/n");

}

6 semaphore_p函数将信号量减1(等待):

static int semaphore_p(void)

{

    struct sembuf sem_b;

    sem_b.sem_num = 0;

    sem_b.sem_op = -1;

    sem_b.sem_flag = SEM_UNDO;

    if(semop(sem_id, &sem_b, 1) == -1)

    {

        fprintf(stderr, "semaphore_pfailed/n");

        return 0;

    }

    return 1;

}

 

7 emaphore_v函数将sembuf结构的sem_op部分设置为1,从而信号量变得可用。

staticint semaphore_v(void)

{

    struct sembuf sem_b;

    sem_b.sem_num = 0;

    sem_b.sem_op = 1;

    sem_b.sem_flag = SEM_UNDO;

    if(semop(sem_id, &sem_b, 1) == -1)

    {

        fprintf(stderr, "semaphore_vfailed/n");

        return 0;

    }

    return 1;

}

$./sem1 1 &

[1]1082

$./sem1

OOXXOOXXOOXXOOXXOOXXOOOOXXOOXXOOXXOOXXXX

1083- finished

1082- finished

$