精华内容
下载资源
问答
  • Linux】Posix信号量

    2021-04-26 16:31:36
    该变量的值表示允许同时访问共享资源的最大进程。 wait() 等待一个信号量该操作会检查信号量的值,如果其值小于或等于0,那就阻塞,直到该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限...

    1. 信号量的概念和作用

    1.1信号量的概念和原理:

    信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段。它是一个特殊的变量,只允许对它进行等待wait()和发送信号post(),并且这两种操作都是原子操作。该变量的值表示允许同时访问共享资源的最大进程数。
    wait() 等待一个信号量该操作会检查信号量的值,如果其值小于或等于0,那就阻塞,直到该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限。这整个操作必须是一个原子操作。
    post() 挂出一个信号量。该操作将信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒,调用这个函数会使其中一个进程不在阻塞,选择机制是有进程的调度策略决定的。该操作是一个原子操作。

    1.2信号量,互斥锁,条件变量的区别

    1. 条件变量和信号量都可以实现进程之间和线程之间的同步,不过条件变量实现进程间的同步需要借助共享内存。
    2. 使用条件变量时,条件满足到阻塞,不是一个原子操作,所以判断条件是否满足前应该先加锁,如果不先加锁可能出现唤醒信号丢失的情况(对于消费者,阻塞条件满足就是,缓冲池为空)。信号量使用时信号量为零到阻塞的过程是原子的。所以wait() 操作前不需要加锁。
    3. 当信号量的初始值为为1时,可以把他当做互斥锁使用。

    1.3有名信号量和无名信号量

    POSIX信号量有两种:有名信号量和无名信号量,无名信号量也被称作基于内存的信号量。有名信号量通过IPC名字进行进程间的同步,而无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。

    2. POSIX信号量的接口

    在这里插入图片描述

    2.1信号量的创建

    (1)无名信号量的创建
    无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初始化该无名信号量,之后就可以放心使用了。初始化函数如下:

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

    参数:
    sem: 要初始化的信号量
    pshared: pshared == 0 用于同一多线程的同步;若pshared > 0 用于多个相关进程间的同步(即由fork产生的)。
    value: 信号量的初始值。
    (2)有名信号量的创建

    #include <semaphore.h>
    
    sem_t *sem_open(const char *name, int oflag);
    sem_t *sem_open(const char *name, int oflag,
                      mode_t mode, unsigned int value);
                                  //成功返回信号量指针,失败返回SEM_FAILED
    

    name:是文件的路径名;
    oflag :取0,O_CREAT,O_EXCL。如果为0表示打开一个已存在的信号量,如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回。此时mode和value需要指定。如果为O_CREAT | O_EXCL,表示如果信号量已存在会返回错误。
    mode_t:控制新的信号量的访问权限;
    value:指定信号量的初始化值。

    2.2信号量的删除

    #include <semaphore.h> 
    int sem_close(sem_t *sem);//关闭信号量
    int sem_unlink(const char *name);//删除信号量
                                  //成功返回0,失败返回-1
    

    int sem_close(sem_t *sem); // 关闭信号量

    1. 当一个进程打开有名信号量时,系统会记录进程与信号的关联关系。调用 sem_close 时,会终止这种关联关系,同时信号量的进程数的引用计数减 1 。
    2. 进程终止时,进程打开的有名信号量会自动关闭。当进程执行 exec 系列函数时,进程打开的有名信号量会自动关闭。
    3. 关闭不等同于删除.

    int sem_unlink(const char *name); // 删除信号量

    1. 将有名信号量的名字作为参数,传递给 sem_unlink ,该函数会负责将该有名信号量删除。由于系统为信号量维护了引用计数,所以只有当打开信号量的所有进程都关闭了之后,才会真正地删除。如果没有全部关闭sem_unlink调用没有任何作用。

    2.3信号量的等待(P操作)

    int sem_wait (sem_t *sem);
    

    如果调用 sem_wait 函数时,信号量的当前值大于 0 ,那么sem_wait 函数立刻返回。否则 sem_wait 函数陷入阻塞,待信号量的值大于 0 之后,再执行减 1 操作,然后成功返回。

    2.4信号量的释放(V操作)

     int sem_post(sem_t *sem);
    

    表示资源已经使用完毕,可以归还资源了。该函数会使信号量的值加 1 。如果发布信号量之前,信号量的值是 0 ,有多个进程正等待在信号量上,那么将无法确认哪个进程会被唤醒。

    2.5获取信号量的值

    int sem_getvalue(sem_t *sem, int *sval);
    

    返回当前信号量的值,并将值写入 sval 指向的变量当 sem_getvalue 返回时,其返回的值可能已经过时了。从这个意义上讲,该接口的意义并不大。

    3. 同步实例生产者消费者问题

    #include<cstdio>
    #include<iostream>
    #include<vector>
    #include<semaphore.h>
    using namespace std;
    #define QUEUE_MAX 3
    static int i = 0;
    class RingQueue{
    public:
      RingQueue(int capacity = QUEUE_MAX)
        :_capacity(capacity)
        ,_step_read(0)
        ,_step_write(0){
        
        _queue.resize(_capacity,0);
        sem_init(&_lock, 0, 1);//信号量用于线程间互斥初始值为1.
        sem_init(&_sem_idle, 0, capacity);//线程同步,初始值为空闲空间的个数
        sem_init(&_sem_data, 0 , 0);//线程间同步,初始值为已用空间个数:0;
    
      }
      ~RingQueue(){
        sem_destroy(&_sem_data);
        sem_destroy(&_sem_idle);
        sem_destroy(&_lock);
      }
      bool push(int data){//生产者
        //1. wait() 判断空闲个数_sem_idle,大于零 -1 继续,等于零 阻塞
        sem_wait(&_sem_idle);
        //2. wait()判断队列是否有人访问。有人阻塞,无人访问
        sem_wait(&_lock);
        //3. 访问共享资源 push也就是放入数据
        _queue[_step_write] = data;
        _step_write = (_step_write + 1) % _capacity;
        //4. 解锁
        sem_post(&_lock);
        //5. 释放信号,_sem_data + 1,唤醒等待队列上一个阻塞的线程(消费者线程)
        sem_post(&_sem_data);
        return true;
      }
      bool pop(int *data){
        // 1. wait() a判断已经有数据节点的个数_sem_data,大于0 减一继续,等于零阻塞
        sem_wait(&_sem_data);
        // 2. wait() 互斥判断是否有线程访问队列
        sem_wait(&_lock);
        // 3. 访问共享资源,改变_step_read的值
        *data = _queue[ _step_read ];
        _queue[_step_read] = 0;
        _step_read = (_step_read + 1) % _capacity;
        // 4. 解锁
        sem_post(&_lock);
        // 5. 释放信号,_sem_idle + 1唤醒一个等待队列上的线程,(生产者线程)
        sem_post(&_sem_idle);
        return true;
      }
    private:
        std::vector<int> _queue;// 数组
        int _capacity;//这是队列的容量
        int _step_read;//读数据的位置
        int _step_write;//写数据的位置
    
        sem_t _lock; //用于实现互斥的信号量
        sem_t _sem_idle; // 用于对空闲空间进行计数,大于0才可以写数据
        sem_t _sem_data; //对具有数据的空间进行计数,大于0才能读数据
    
    };
    void* thr_product(void* grm){
        RingQueue *ptr = (RingQueue*)grm;
        while(1){
              //生产者不断地写入数据
              ptr ->push(i++);
              printf("写入数据%d\n",i);//注意printf()和上边的push不是原子操作
                        
        }
          return NULL;
    
    }
    void* thr_consumer(void* grm){
        RingQueue *ptr = (RingQueue*)grm;
          int date;
          while(1){
                //消费者不断地读出数据
                ptr ->pop(&date);
                printf("读出数据%d\n",date);          
          }
          return NULL;
    }
    int main(){
          RingQueue q(3);//阻塞队列的容量为3
          pthread_t pro_tid[4];//4个生产者
            pthread_t con_tid[4];//4个消费者
            for(int i = 0; i < 4; i++){
                  int res = pthread_create(&pro_tid[i],NULL, thr_product, (void*)&q);
                  if(res != 0){
                          printf("创建生产者线程失败");
                                return -1;
                                    
                  }
                    
            }
            for(int i = 0; i < 4; i++){
                  int res = pthread_create(&con_tid[i],NULL, thr_consumer, (void*)&q);
                  if(res != 0){
                      printf("创建消费者线程失败");
                      return -1;                             
                  }
                    
            }
              pthread_join(pro_tid[0],NULL);
                pthread_join(con_tid[0],NULL);
                  return 0;
    
    }
    
    展开全文
  • Linux内核源码之信号量的实现

    千次阅读 2016-10-30 14:07:50
    之前的一片博客介绍了用于Linux内核同步的...首先,自旋锁和信号量都使用了计数器来表示允许同时访问共享资源的最大进程,但自旋锁的共享计数值是1,也就是说任意时刻只有一个进程在共享代码区运行;信号量却允许使用

    之前的一片博客介绍了用于Linux内核同步的自旋锁,即使用自旋锁来保护共享资源,今天介绍另外一种Linux内核同步机制——信号量。信号量在内核中的使用非常广泛,用于对各种共享资源的保护。信号量与自旋锁的实现机制是不一样的,用处也是不一样的。首先,自旋锁和信号量都使用了计数器来表示允许同时访问共享资源的最大进程数,但自旋锁的共享计数值是1,也就是说任意时刻只有一个进程在共享代码区运行;信号量却允许使用大于1的共享计数,即共享资源允许被多个不同的进程同时访问,当然,信号量的计数器也能设为1,这时信号量也称为互斥量。其次,自旋锁用于保护短时间能够完成操作的共享资源,使用期间不允许进程睡眠和进程切换;信号量常用于暂时无法获取的共享资源,如果获取失败则进程进入不可中断的睡眠状态,只能由释放资源的进程来唤醒。最后,自旋锁可以用于中断服务程序之中;信号量不能在中断服务程序中使用,因为中断服务程序是不允许进程睡眠的。关于信号量的基本知识已经讲解完毕,接下来看看信号量在内核里面的实现,本文讲解的内核版本是linux-2.6.24。
    1 数据结构

    struct semaphore {
        atomic_t count;
        int sleepers;
        wait_queue_head_t wait;
    };

    信号量使用的数据结构是struct semaphore,包含三个数据成员:count是共享计数值、sleepers是等待当前信号量进入睡眠的进程个数、wait是当前信号量的等待队列。
    2 信号量使用
    使用信号量之前要进行初始化,其实只是简单的设置共享计数和等待队列,睡眠进程数一开始是0。本文重点讲解信号量的使用和实现。信号量操作的API:

    static inline void down(struct semaphore * sem)//获取信号量,获取失败则进入睡眠状态
    static inline void up(struct semaphore * sem)//释放信号量,并唤醒等待队列中的第一个进程

    信号量的使用方式如下:

    down(sem);
    ...临界区...
    up(sem);

    内核保证正在访问临界区的进程数小于或等于初始化的共享计数值,获取信号量失败的进程将进入不可中断的睡眠状态,在信号量的等待队列中进行等待。当进程释放信号量的时候就会唤醒等待队列中的第一个进程。
    3 信号量的实现
    3.1 down(sem)
    首先看函数的定义:

    static inline void down(struct semaphore * sem)
    {
        might_sleep();
        __asm__ __volatile__(
            "# atomic down operation\n\t"
            LOCK_PREFIX "decl %0\n\t"     /* --sem->count */
            "jns 2f\n"
            "\tlea %0,%%eax\n\t"
            "call __down_failed\n"
            "2:"
            :"+m" (sem->count)
            :
            :"memory","ax");
    }

    这里面包含了一些汇编代码,%0代表sem->count。也就是说先将sem->count减1,LOCK_PREFIX表示执行这条指令时将总线锁住,保证减1操作是原子的。减1之后如果大于或等于0就转到标号2处执行,也就跳过了__down_failed函数直接到函数尾部并返回,成功获取信号量;否则减1之后sem->count小于0则顺序执行后面的__down_failed函数。接下来看__down_failed函数的定义:

    ENTRY(__down_failed)
        CFI_STARTPROC
        FRAME
        pushl %edx
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET edx,0
        pushl %ecx
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET ecx,0
        call __down
        popl %ecx
        CFI_ADJUST_CFA_OFFSET -4
        CFI_RESTORE ecx
        popl %edx
        CFI_ADJUST_CFA_OFFSET -4
        CFI_RESTORE edx
        ENDFRAME
        ret
        CFI_ENDPROC
        END(__down_failed)

    pushl和popl是用于保存和恢复寄存器的,CFI前缀的指令用于指令对齐调整。重点在函数__down,下面来看该函数的定义:

    fastcall void __sched __down(struct semaphore * sem)
    {
        struct task_struct *tsk = current;
        DECLARE_WAITQUEUE(wait, tsk);
        unsigned long flags;
    
        tsk->state = TASK_UNINTERRUPTIBLE;
        spin_lock_irqsave(&sem->wait.lock, flags);
        add_wait_queue_exclusive_locked(&sem->wait, &wait);
    
        sem->sleepers++;
        for (;;) {
            int sleepers = sem->sleepers;
    
            /*
             * Add "everybody else" into it. They aren't
             * playing, because we own the spinlock in
             * the wait_queue_head.
             */
            if (!atomic_add_negative(sleepers - 1, &sem->count)) {
                sem->sleepers = 0;
                break;
            }
            sem->sleepers = 1;  /* us - see -1 above */
            spin_unlock_irqrestore(&sem->wait.lock, flags);
    
            schedule();
    
            spin_lock_irqsave(&sem->wait.lock, flags);
            tsk->state = TASK_UNINTERRUPTIBLE;
        }
        remove_wait_queue_locked(&sem->wait, &wait);
        wake_up_locked(&sem->wait);
        spin_unlock_irqrestore(&sem->wait.lock, flags);
        tsk->state = TASK_RUNNING;
    }

    fastcall表示一种快速调用方式,函数的前两个参数由寄存器ecx和edx来传递,其余参数仍使用堆栈传递。首先将进程设为不可中断睡眠状态,即不能通过信号来唤醒,只能是内核亲自唤醒。同时将进程的TASK_EXCLUSIVE标志设为1,则wake_up()只会唤醒等待队列中的第一个进程。然后将睡眠等待数加1,之后进入for循环。函数atomic_add_negative(sleepers - 1, &sem->count)将相当于sem->count += sleepers-1,然后返回sem->count,通过该函数进行信号量获取情况测试,返回结果为0则获取资源,小于0则没有获取。这段代码使用sleepers和sem->count共同表示当前资源的使用情况。进入for循环后有两种情况,一种是atomic_add_negative执行结果为0,即获取了信号量,此时将sleepers设为0并退出循环,同时唤醒等待队列的第一个进程进行信号量获取测试;另一种是没有获取信号量,将sleepers设为1并运行schedule()进入睡眠,被唤醒之后继续执行for循环进行信号量获取测试。
    注意,运行完执行一遍for指令后sleepers的值有两种结果,一种是0,一种是1。如果0则表示有一个进程通过了信号量获取的测试,则atomic_add_negative(sleepers - 1, &sem->count)实际上是将sem->count执行了减1操作,这个操作会在下一个进程进行信号量获取测试的时候执行。如果是1则表示进程没有通过信号领获取的测试,则atomic_add_negative(sleepers - 1, &sem->count)操作不会影响sem->count的值。也就是说,当进程进入__down时,sleepers只会有两个值,一个是0,一个是1。0表示之前的进程获取了信号量,1表示之前的进程没有获取信号量。如果之前进程获取了信号量,执行atomic_add_negative(sleepers - 1, &sem->count)时就会将sem->count的值减1;否则sem->count的值将保持不变。但是这个减1操作延迟到了下一个进程的执行期间,考虑到获取信号量之后进程会唤醒等待队列里的第一个进程,这个减1操作应该会很快就得到执行。
    细心地小伙伴可能会注意到,首次获取信号量失败的进程不是会执行sem->sleepers++操作吗,这样不就改变了sem->count的值了吗?仔细回想获取信号量的过程,获取失败的时候会执行sem->count–操作的,因此刚好和sem->sleeper++相互呼应,结果就是不会改变sem->count的结果。即只有进程获取信号量后才会对sem->count进行减1操作,这个操作并不是马上执行,而是后续进程进行信号量获取检测的时候进行的
    3.2 up(sem)
    先看函数定义:

    static inline void up(struct semaphore * sem)
    {
        __asm__ __volatile__(
            "# atomic up operation\n\t"
            LOCK_PREFIX "incl %0\n\t"     /* ++sem->count */
            "jg 1f\n\t"
            "lea %0,%%eax\n\t"
            "call __up_wakeup\n"
            "1:"
            :"+m" (sem->count)
            :
            :"memory","ax");
    }

    首先将sem->count加1,是原子操作,如果加1后sem->count大于0则说明没有进程在等待信号量资源,无须唤醒队列中进程,直接跳转到标号1处返回;否则运行__up_wakeup唤醒等待队列中的进程。

    ENTRY(__up_wakeup)
        CFI_STARTPROC
        FRAME
        pushl %edx
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET edx,0
        pushl %ecx
        CFI_ADJUST_CFA_OFFSET 4
        CFI_REL_OFFSET ecx,0
        call __up
        popl %ecx
        CFI_ADJUST_CFA_OFFSET -4
        CFI_RESTORE ecx
        popl %edx
        CFI_ADJUST_CFA_OFFSET -4
        CFI_RESTORE edx
        ENDFRAME
        ret
        CFI_ENDPROC
        END(__up_wakeup)

    同样,我们只关注函数__up的定义:

    fastcall void __up(struct semaphore *sem)
    {
        wake_up(&sem->wait);
    }

    可以看到,__up的的工作就是唤醒等待队列中的所有进程,但是由于sem等待队列中的进程 的TASK_EXCLUSIVE标志为 1,因此不会唤醒后续进程了。也就是说up(sem)操作实际上是将sem->count自增1,然后唤醒等待队列中的第一个进程(如果有的话)。
    4 小结
    信号量作为一种基础的内核同步机制,使用非常广泛。本文基于linux-2.6.24内核版本介绍了信号量使用的数据结构和实现机制,同时介绍了信号量与自旋锁的区别。

    展开全文
  • 1、相似点每个内核中的IPC结构(消息队列、信号量、共享存储)都用一个非负整数的标识符加以引用,与文件描述符不同,当一个IPC结构被创建,以后又被删除时,与这种结构相关的标识符连续加1,直至达到一个整型的...

    消息队列、信号量和共享存储是IPC(进程间通信)的三种形式,它们功能不同,但有相似之处,下面先介绍它们的相似点,然后再逐一说明。

    1、相似点

    每个内核中的IPC结构(消息队列、信号量和共享存储)都用一个非负整数的标识符加以引用,与文件描述符不同,当一个IPC结构被创建,以后又被删除时,与这种结构相关的标识符连续加1,直至达到一个整型数的最大正直,然后又回转到0。标识符是IPC对象的内部名,还有一个外部名称为键,数据类型是key_t,通常在头文件

    #include <sys/ipc.h> 
    key_t ftok(const char *pathname, int proj_id);

    消息队列、信号量和共享存储都有自己的get函数,msgget、semget和shmget,用于创建IPC对象,它们都设置了自己的ipc_perm结构,在头文件

    struct ipc_perm { 
                   key_t          __key;       /* Key supplied to msgget(2) */ 
                   uid_t          uid;         /* Effective UID of owner */ 
                   gid_t          gid;         /* Effective GID of owner */ 
                   uid_t          cuid;        /* Effective UID of creator */ 
                   gid_t          cgid;        /* Effective GID of creator */ 
                   unsigned short mode;        /* Permissions */ 
                   unsigned short __seq;       /* Sequence number */ 
    };

    消息队列、信号量和共享存储都有自己的内置限制,这些限制的大多数可以通过重新配置内核而加以更改,如sysctl命令,可以配置运行时内核参数,在Linux(Ubuntu)上,运行命令“ipcs -l”可查看相关限制,如下:

    $ ipcs -l
    
    ------ Messages Limits --------
    max queues system wide = 32000
    max size of message (bytes) = 8192
    default max size of queue (bytes) = 16384
    
    ------ Shared Memory Limits --------
    max number of segments = 4096
    max seg size (kbytes) = 18014398509465599
    max total shared memory (kbytes) = 18014398442373116
    min seg size (bytes) = 1
    
    ------ Semaphore Limits --------
    max number of arrays = 32000
    max semaphores per array = 32000
    max semaphores system wide = 1024000000
    max ops per semop call = 500
    semaphore max value = 32767
    

    需要注意的是,IPC对象是在系统范围内起作用的,没有访问计数,不同于普通文件。例如,如果进程创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不会被删除,它们余留在系统中直至出现下述情况:由某个进程调用msgrcv读消息或msgctl删除消息队列;或某个进程执行ipcrm命令删除消息队列;或由正在再启动的系统删除消息队列。将此与管道相比,当最后一个访问管道的进程被终止时,管道就被完全地删除了。对于FIFO而言,虽然当最后一个引用FIFO的进程终止时其名字仍保留在系统中,直至显式地删除它,但是留在FIFO中的数据却在此时被全部删除。

    2、消息队列

    消息队列即message queue,存放在内核中并由消息队列标识符标识,涉及如下函数和数据结构。

    #include <sys/types.h> 
    #include <sys/ipc.h> 
    #include <sys/msg.h> 
    
    int msgget(key_t key, int msgflg);
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 
    ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    
    struct msqid_ds { 
                   struct ipc_perm msg_perm;     /* Ownership and permissions */ 
                   time_t          msg_stime;    /* Time of last msgsnd(2) */ 
                   time_t          msg_rtime;    /* Time of last msgrcv(2) */ 
                   time_t          msg_ctime;    /* Time of last change */ 
                   unsigned long   __msg_cbytes; /* Current number of bytes in 
                                                    queue (nonstandard) */ 
                   msgqnum_t       msg_qnum;     /* Current number of messages 
                                                    in queue */ 
                   msglen_t        msg_qbytes;   /* Maximum number of bytes 
                                                    allowed in queue */ 
                   pid_t           msg_lspid;    /* PID of last msgsnd(2) */ 
                   pid_t           msg_lrpid;    /* PID of last msgrcv(2) */ 
    };
    
    struct msgbuf {
                   long mtype;       /* message type, must be > 0 */
                   char mtext[1];    /* message data */
    };

    msgget用于创建一个新队列或打开一个现存的队列,参数key可自定义或通过ftok生成,或者使用IPC_PRIVATE,需要保证的是key值没有与现存的队列相关联,msgflag为O_CREAT时创建新队列,排它性使用O_EXCL。msgsnd将新消息添加到队列尾端,参数msqid为消息队列id,msgp比较特殊,需要包括两部分内容,消息类型和实际的消息数据,如上面的msgbuf结构,msgsz指定消息长度,msgflag可以设置为IPC_NOWAIT,表示非阻塞。msgrcv用于从队列中取消息,参数msgsz表示缓冲区长度,当消息长度大于msgsz时,若msgflg设置了MSG_NOERROR则截短消息,否则出错E2BIG,msgtyp为0时获取第一个消息,大于0时获取类型为msgtyp的第一个消息,小于0时获取类型小于等于msgtyp的类型值最小的第一个消息。每个消息队列都有一个msqid_dt结构与其相关联,规定了队列的当前状态,msgctl则可以对消息队列的这种结构进行操作,参数cmd可以是IPC_STAT、IPC_SET、IPC_RMID,分别表示获取状态、设置状态、移除消息队列。

    下面例子说明消息队列的用法,msgsnd.c发送消息,msgrcv.c接收消息,当输入“quit”时结束。

    // msgsnd.c 
    #include <stdio.h> 
    #include <stdlib.h> 
    #include <stdbool.h> 
    #include <string.h> 
    #include <errno.h> 
    #include <sys/msg.h> 
    #include <unistd.h> 
    
    struct msg_st 
    { 
        long int msg_type; 
        char text[BUFSIZ]; 
    }; 
    
    int main(void) 
    { 
        struct msg_st data; 
        data.msg_type = 1; 
        char buf[BUFSIZ]; 
        key_t akey = 1000; 
        int msgid = -1; 
        bool running = true; 
    
        // create message queue 
        msgid = msgget(akey, 0666 | IPC_CREAT); 
        if (-1 == msgid) { 
            fprintf(stderr, "msgget failed with error: %d\n", errno); 
            exit(EXIT_FAILURE); 
        } 
    
        // loop for sending data to message queue 
        while (running) { 
            printf("Input text: "); 
            fgets(buf, BUFSIZ, stdin); 
            strcpy(data.text, buf); 
            // send data 
            if (-1 == msgsnd(msgid, (void*)&data, BUFSIZ, 0)) 
            { 
                fprintf(stderr, "msgsnd failed\n"); 
                exit(EXIT_FAILURE); 
            } 
            // input "quit" to finish 
            if(0 == strncmp(buf, "quit", 4)) { 
                running = false; 
            } 
            sleep(1); 
        } 
    
        exit(EXIT_SUCCESS); 
    } 
    
    // msgrcv.c 
    #include <stdio.h> 
    #include <stdlib.h> 
    #include <stdbool.h> 
    #include <string.h> 
    #include <errno.h> 
    #include <sys/msg.h> 
    #include <unistd.h> 
    
    struct msg_st 
    { 
        long int msg_type; 
        char text[BUFSIZ]; 
    }; 
    
    int main(void) 
    { 
    
        struct msg_st data; 
        data.msg_type = 0; 
        key_t akey = 1000; 
        int msgid = -1; 
        bool running = true; 
    
        // create messge queue 
        msgid = msgget(akey, 0666 | IPC_CREAT); 
        if (-1 == msgid) { 
            fprintf(stderr, "msgget failed with error: %d\n", errno); 
            exit(EXIT_FAILURE); 
        } 
    
        // loop for getting data from message queue 
        while (running) { 
            // receive data 
            if(-1 == msgrcv(msgid, (void*)&data, BUFSIZ, data.msg_type, 0)) 
            { 
                fprintf(stderr, "msgrcv failed with errno: %d\n", errno); 
                exit(EXIT_FAILURE); 
            } 
            printf("Receive text: %s\n",data.text); 
            // receive "quit" to finish 
            if(0 == strncmp(data.text, "quit", 4)) { 
                running = false; 
            } 
        } 
    
        // delete message queue 
        if (-1 == msgctl(msgid, IPC_RMID, 0)) 
        { 
            fprintf(stderr, "msgctl(IPC_RMID) failed\n"); 
            exit(EXIT_FAILURE); 
        } 
    
        exit(EXIT_SUCCESS); 
    } 

    3、信号量

    信号量semaphore确切的说是一种同步方式,涉及如下函数和数据结构。

    #include <sys/types.h> 
    #include <sys/ipc.h> 
    #include <sys/sem.h> 
    
    int semget(key_t key, int nsems, int semflg);
    int semctl(int semid, int semnum, int cmd, ...); 
    int semop(int semid, struct sembuf *sops, unsigned nsops);
    
    struct semid_ds {
                   struct ipc_perm sem_perm;  /* Ownership and permissions */
                   time_t          sem_otime; /* Last semop time */
                   time_t          sem_ctime; /* Last change time */
                   unsigned long   sem_nsems; /* No. of semaphores in set */
    };
    
    union semun {
                   int              val;    /* Value for SETVAL */
                   struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
                   unsigned short  *array;  /* Array for GETALL, SETALL */
                   struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                               (Linux-specific) */
    };
    
    struct sembuf
    {
      unsigned short int sem_num;   /* semaphore number */
      short int sem_op;     /* semaphore operation */
      short int sem_flg;        /* operation flag */
    };
    
    struct
    {
        unsigned short  semval;   /* semaphore value */
               unsigned short  semzcnt;  /* # waiting for zero */
               unsigned short  semncnt;  /* # waiting for increase */
               pid_t           sempid;   /* ID of process that did last op */
    }

    信号量是一个计数器,用于多进程对共享数据对象的访问。为了获得共享资源,进程需要执行下列操作:
    (1)测试控制该资源的信号量。
    (2)若此信号量的值为正,则进程可以使用该资源,进程将信号量值减1,表示它使用了一个资源单位。
    (3)若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0,进程被唤醒后,它返回第(1)步。

    当进程不再使用由一个信号量控制的共享资源时,该信号量值增1,如果有进程正在休眠等待此信号量,则唤醒它们。为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作,为此,信号量通常是在内核中实现的。常用的信号量形式被成为二元信号量或双态信号量,它控制单个资源,初始值为1,但是一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享使用。需要注意的是,信号量并非是单个非负值,为一个包含了一个或多个信号量值的信号量集,创建信号量需要指定信号量集中的信号个数。

    semget用于获取信号量集标识符,参数nsems表示信号量个数,创建新的信号量集时必须大于0,获取已有的则为0。semctl对信号量进行操作,第四个参数可选,类型为semun联合,semnum指定信号量集中的某个信号,cmd同消息队列的msgctl一样也可以是IPC_STAT、IPC_SET、IPC_RMID,还有形如GETXXX的值。semop函数是个原子操作,自动执行信号量集合上的操作数组sops,sops为sembuf结构体,成员sem_op可以为0、正数、负数,sem_flg为IPC_NOWAIT或SEM_UNDO,后者表示进程终止时自动处理还未处理的信号量,参数nsops规定该数组中操作的数量。

    先来看一个不使用信号量的例子:

    // semaphore2.c 
    #include <stdio.h> 
    #include <stdlib.h> 
    
    int main(int argc, char *argv[]) 
    { 
        char message = 'X'; 
        int i = 0;   
    
        if (argc > 1) { 
            message = argv[1][0]; 
        } 
    
        for (i = 0; i < 10; ++i) { 
            printf("%c", message); 
            fflush(stdout); 
            sleep(rand() % 3); 
            printf("%c", message); 
            fflush(stdout); 
            sleep(rand() % 3); 
        } 
    
        sleep(10); 
        printf("\n%d - finished\n", getpid()); 
    
        exit(EXIT_SUCCESS); 
    }

    编译运行:

    $gcc -o sem semaphore2.c
    $./sem A & ./sem
    [1] 5647 
    AXAXAXAXXAAXAXAXAAXXAXXAXAAXAXAXAXAXAXXA 
    
    5648 - finished 
    5647 - finished 
    [1]+  Done                    ./sem A 

    一个进程在for循环中连续两次输出A,并启动到后台运行,另一个进程在for循环中连续两次输出X,从上面的结果可以看出,它们相互竞争,结果是乱序的,并不是两个连续的A或者X,下面用信号量改写上面的例子:

    #include <unistd.h> 
    #include <sys/types.h> 
    #include <sys/stat.h> 
    #include <fcntl.h> 
    #include <stdlib.h> 
    #include <stdio.h> 
    #include <string.h> 
    #include <sys/sem.h> 
    
    union semun 
    { 
        int val; 
        struct semid_ds *buf; 
        unsigned short *arry; 
    }; 
    
    static int sem_id = 0; 
    
    int main(int argc, char *argv[]) 
    { 
        key_t akey = 1000; 
        char message = 'X'; 
        int i = 0; 
    
        // create semaphore 
        sem_id = semget(akey, 1, 0666 | IPC_CREAT); 
        if (-1 == sem_id) { 
            fprintf(stderr, "Failed to create semaphore\n"); 
            exit(EXIT_FAILURE); 
        } 
    
        if (argc > 1) { 
            // semaphore initialization, must 
            union semun sem_union; 
            sem_union.val = 1; 
            if (-1 == semctl(sem_id, 0, SETVAL, sem_union)) { 
                fprintf(stderr, "Failed to initialize semaphore\n"); 
                exit(EXIT_FAILURE); 
            } 
    
            message = argv[1][0]; 
            sleep(1); 
        } 
    
        for (i = 0; i < 10; ++i) { 
            // go into critical zone 
            struct sembuf sem_i; 
            sem_i.sem_num = 0; 
            sem_i.sem_op = -1; 
            sem_i.sem_flg = SEM_UNDO; 
            if (-1 == semop(sem_id, &sem_i, 1)) 
            { 
                perror("semop in failed\n"); 
                exit(EXIT_FAILURE); 
            } 
    
            printf("%c", message); 
            fflush(stdout); 
            sleep(rand() % 3); 
            printf("%c", message); 
            fflush(stdout); 
    
            // leave critical zone 
            struct sembuf sem_o; 
            sem_o.sem_num = 0; 
            sem_o.sem_op = 1; 
            sem_o.sem_flg = SEM_UNDO; 
            if (-1 == semop(sem_id, &sem_o, 1)) { 
                perror("semop out failed\n"); 
                exit(EXIT_FAILURE); 
            } 
    
            sleep(rand() % 3); 
        } 
    
        sleep(10); 
        printf("\n%d - finished\n", getpid()); 
    
        if (argc > 1) { 
            // delete samaphore 
            sleep(3); 
            union semun sem_union; 
            if (-1 == semctl(sem_id, 0, IPC_RMID, sem_union)) { 
                fprintf(stderr, "Failed to delete semaphore\n"); 
            } 
        } 
    
        exit(EXIT_SUCCESS); 
    } 

    执行结果如下:

    XXAAXXAAXXAAXXAAXXAAXXAAXXAAXXAAXXXXAAAA 

    可见,使用了信号量,输出结果符合预期,两个A或者两个X连在了一起。

    4、共享存储

    共享存储允许两个或多个进程共享一给定的存储区,因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。使用共享存储时需要掌握的唯一窍门是多个进程之间对一给定存储区的同步访问,若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应当去取这些数据,通常,信号量被用来实现对共享存储访问的同步。下面是相关的几个函数和数据结构:

    #include <sys/ipc.h> 
    #include <sys/shm.h> 
    
    int shmget(key_t key, size_t size, int shmflg);
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    int shmdt(const void *shmaddr);
    
    struct shmid_ds {
                   struct ipc_perm shm_perm;    /* Ownership and permissions */
                   size_t          shm_segsz;   /* Size of segment (bytes) */
                   time_t          shm_atime;   /* Last attach time */
                   time_t          shm_dtime;   /* Last detach time */
                   time_t          shm_ctime;   /* Last change time */
                   pid_t           shm_cpid;    /* PID of creator */
                   pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
                   shmatt_t        shm_nattch;  /* No. of current attaches */
                   ...
    };

    shmget用于获取共享存储标识符,参数size为共享存储区的长度,单位是字节,实现通常将其向上取为系统页长的整数倍,若size并非系统页长的整数倍,那么最后一页的余下部分是不可用的,创建一个新的共享存储区时size需要大于0,引用已有的共享存储区则将size设置为0。shmctl可操作共享存储区,同样可以是IPC_STAT、IPC_SET、IPC_RMID等。
    shmat用于将共享存储段连接到调用进程指定的地址shmaddr上,但一般应指定shmaddr为0,内核会自动选择合适的地址,shmflg可选SHM_RND即地址取整,SHM_RDONLY只读,默认读写。当对共享存储段的操作结束时,调用shmdt取消当前进程与共享存储段的连接。

    下面是一个使用了shm的例子,程序中fork之后,子进程sleep保证父进程先执行,父进程取得共享存储区以后写入“hello world”,子进程同样也取得了这个共享存储区,然后访问同一块地址,读到了“hello world”。

    #include <fcntl.h> 
    #include <stdlib.h> 
    #include <stdio.h> 
    #include <string.h> 
    #include <sys/shm.h> 
    
    #define SIZE 1024 
    #define exit_err(str) do { perror(str); exit(EXIT_FAILURE); } while (0); 
    #define uint32 unsigned long 
    
    int main(void) 
    { 
        int shmid; 
        char *shmptr; 
        key_t key; 
        pid_t pid; 
    
        if ((pid = fork()) < 0) { 
            exit_err("fork error"); 
        } 
    
        if(0 == pid) { 
            printf("child process\n"); 
    
            if ((key = ftok("/dev/null", O_RDWR)) < 0) { 
                exit_err("ftok error"); 
            } 
    
            if ((shmid = shmget(key, SIZE, 0600 | IPC_CREAT)) < 0) { 
                exit_err("shmget error"); 
            } 
    
            if ((shmptr = (char*)shmat(shmid, 0, 0)) == (void*)-1) { 
               exit_err("shmat error"); 
            } 
    
            sleep(1); 
            printf("child pid is %d, share memory from %lx to %lx, content: %s\n",getpid(), (uint32)shmptr, (uint32)(shmptr + SIZE), shmptr); 
            sleep(1); 
    
            if ((shmctl(shmid, IPC_RMID, 0) < 0)) { 
                 exit_err("shmctl error"); 
            } 
            exit(EXIT_SUCCESS); 
        } 
        else { 
            printf("parent process\n"); 
    
            if ((key = ftok("/dev/null", O_RDWR)) < 0) { 
                exit_err("ftok error");    
            } 
    
            if ((shmid = shmget(key, SIZE, 0600 | IPC_CREAT | IPC_EXCL)) < 0) { 
              exit_err("shmget error"); 
            } 
    
            if((shmptr = (char*)shmat(shmid, 0, 0)) == (void*)-1) { 
              exit_err("shmat error"); 
            } 
    
            memcpy(shmptr, "hello world", sizeof("hello world")); 
            printf("parent pid is %d, share memory from %lx to %lx, content: %s\n",getpid(),(uint32)shmptr, (uint32)(shmptr + SIZE), shmptr); 
        } 
    
        waitpid(pid, NULL, 0); 
        exit(EXIT_SUCCESS); 
    } 

    执行结果如下:

    parent process 
    parent pid is 7275, share memory from 7fb2ebf4a000 to 7fb2ebf4a400, content: hello world 
    child process 
    child pid is 7276, share memory from 7fb2ebf4a000 to 7fb2ebf4a400, content: hello world
    展开全文
  • 同样的,信号量的查看命令为 # ipcs -ls 各字段含义说明: SHMMAX 含义:单个共享内存段最大字节 设置:比SGA略大 查看:# cat /proc/sys/kernel/shmmax 1073741824 修改: # sysctl -w kernel.shmmax=...

    首先通过命令来查看共享内存的限制情况:

    # ipcs -lm

    同样的,信号量的查看命令为

    # ipcs -ls

    各字段含义说明:

    SHMMAX

    含义:单个共享内存段最大字节数
    设置:比SGA略大
    查看:# cat /proc/sys/kernel/shmmax
    1073741824

    修改:
    # sysctl -w kernel.shmmax=1073741824
    # echo “kernel.shmmax=1073741824″ >> /etc/sysctl.conf

    SHMMNI

    含义:共享内存段最大个数
    设置:至少4096
    查看:# cat /proc/sys/kernel/shmmni
    4096

    修改:
    # sysctl -w kernel.shmmni=4096
    # echo “kernel.shmmni=4096″ >> /etc/sysctl.conf

    SHMALL

    含义:系统中共享内存页总数
    设置:至少ceil(shmmax/PAGE_SIZE);ORACLE DOC 默认值:2097152*4096=8GB
    查看:# cat /proc/sys/kernel/shmall
    4096

    修改:
    # sysctl -w kernel.shmall=2097152
    # echo “kernel.shmall=2097152″ >> /etc/sysctl.conf

     

    同样的,信号量的设置可以仿照共享内存的设置来进行。

    展开全文
  • semget() 创建一个新的信号量集,或者使用一个已经存在的信号量集。 系统调用:semget(); 原型:int semget(key_t key, int nsems, int semflg);...最大定义在linux/sem.h,我的ub
  • 当oracle11g修改最大连接后启动报如下错误时,需要调整linux信号量的内核参数: ORA-27154: post/wait create failedCause: internal error, multiple post/wait creates attempted simultaneouslyAction: ...
  • Linux--linux下常用内核参数

    千次阅读 2014-06-09 21:31:02
    kernel.sem=250 50000 100 200 按顺序一次设置SEMMSL SEMMNS SEMOPM SEMMNI ...SEMOPM semop函数中允许操作的信号量最大 SEMMNI  系统范围内信号集合的最大个 kernel.msgmni  系统范围内允许的最
  • 3.具体实现的限制(整数的长度、文件名中所允许的最大字符)。 二、临界资源:临界资源是一次仅允许一个进程使用的共享资源。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是...
  • //fs.file-max 最大打开文件 //fs.nr_open=20480000 单个...//kernel.sem 信号量: 每个信号集中最大信号量数目 系统范围最大信号量数目 每个信号发生时的最大系统操作 系统范围内最大信号集总数目 第一列*第...
  • ipcs -l命令可以查看各个资源的系统限制信息,可以看到系统允许的最大信号量集及信号量限制、最大的消息队列中消息个等信息。 ipcs -u命令可以查看各个资源的使用总结信息,其中可以看到使用的信号量集的个数...
  • sem:是semaphores的缩写,该参数表示设置的信号量。它包含四个值:semmsl、semmns、semopm、semmni。常规设置 kernel.sem = 250 32000 ...SEMMSL * SEMMNIsemopm:系统调用允许的信号量最大。至少100;或者等于...
  • linux线程-sysconf系统变量

    千次阅读 2011-02-16 21:11:00
    了解系统的线程资源限制是使得应用程序恰当地管理它们的关键。前面已经讨论了利用系统资源的示例。...系统中定义了同线程、进程和信号量相关的多个变量和常量。在表6-8中,列出了部分变量和常量。变 量名字值(Name V
  • 16.1 最大文件描述符 16.2 调整内核参数 16.2.1 procsysfs目录下的部分文件 16.2.2 procsysnet目录下的部分文件 16.3 gdb调试 16.3.1 用gdb调试多进程程序 16.3.2 用gdb调试多线程程序 16.4 压力...
  • 控制进程资源的变量 ...RLIMIT_CPU CPU时间的最大量值(秒),当超过此软限制时,向该进程发送SIGXCPU信号 RLIMIT_DATA 数据段的最大字节长度,以字节计算,不包括程序分配的动态存储空间 RLIMIT_FSIZE
  • Linux 进程间通信(五)IPC的特性

    千次阅读 2018-09-24 20:47:47
    每个内核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标识符 (identifier)加以引用。 例如,要向一个消息队列发送消息或者从一个消息队列取消息,只需要知道其队列标识符。 当一个IPC结构被创建...
  • Linux /Unix 共享内存

    2010-08-19 14:00:40
    在IPC中,我们经常用用key_t的值来创建或者打开信号量,共享内存和消息队列。 函数原型: key_t ftok(const char *pathname, int proj_id); Keys: 1)pathname一定要在系统中存在并且进程能够访问的 3)proj_id是一...
  • 我从菜鸟到现在两个多月,中间被打扰次。仍然搞定了PCB设计与制版,RAM调试,FLASH下载,UBOOT移植和下载UCLINUX(没剪裁,用现成的),LCD驱动。 再次坦诚的说:我确实比较菜,说这些不是让大家羡慕,只是告诉...
  • [Linux基础]PCI带宽计算?

    千次阅读 2011-12-18 21:43:14
    总线是一组进行互连和传输信息(指令、数据和地址)的信号线。主要参数有总线位宽、总线时钟...※总线传输速率是总线上每秒钟所能传输的最大字节。通过总线宽度和总线时钟频率来计算总线传输速率。 一. 并行总线。
  • 2.3不要用读写锁和信号量. . . . . . . . . . . . . . . . .. . . . . . . . . . . 43 2.4封装MutexLock、MutexLockGuard、Condition. . . . . . . . . . . . . . 44 2.5线程安全的Singleton 实现.. . . . . . . . ....
  • 网络编程常见面试题

    千次阅读 2017-07-08 23:35:25
    线程同步有四种方式:临界区、内核对象、互斥量、信号量Linux 线程同步有最常用的是:互斥锁、条件变量和信号量。 2:epoll与select的区别 select在一个进程中打开的最大fd是有限制的,由FD_SETSIZE设置,默认值...
  • MTS模式配置

    2020-08-04 15:55:09
    1.当oracle的processes设置的很大时,必须首先将linux信号量设置的比processes大才行,否则oracle无法启动 vi /etc/sysctl.conf kernel.sem = 20010 2561280 20010 128 执行命令 sysctl -p生效,上面每个参数的...
  • 多线程操作

    2012-06-20 09:36:29
    windows c++中的sleep(3000)是3000毫秒,就是3秒 而linux中sleep(3000)是3000秒。 ...信号量与Mutex最大的不同是,Mutex只能被acquire它的线程release, 可semaphore可以被不同的线程release
  • c++学习--线程同步

    2018-06-12 23:00:38
    https://blog.csdn.net/caianye/article/details/5912172Linux 线程同步的三种...linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。一、互斥锁(mutex)通过锁机制实现线程间的同步。初始...
  • timing designer9,1

    2013-02-25 12:25:41
    为了简化分析,节省调试时间,设计人员还可以为其图表选定使用的最小或最大值(而不是同时选定最大和最小值),以便执行最佳或最差时序分析。 TimingDesigner 还提供了波形分配器的信号可视化分组、字体修改器能更好地...
  • 3.2.2 信号量参数 74 3.2.3 最大文件句柄数 75 3.2.4 网络参数 76 3.2.5 Oracle用户能够打开的文件句柄的最大数 77 3.2.6 Oracle用户能够执行的进程的最大数 78 3.3 磁盘管理 79 3.3.1 磁盘分区 79 3.3.2 逻辑盘卷...
  • oracle 11g安装配置

    2015-07-13 14:08:25
     250是参数semmsl的值,表示一个信号量集合中能够包含的信号量最大数目。  32000是参数semmns的值,表示系统内可允许的信号量最大数目。  100是参数semopm的值,表示单个semopm()调用在一个信号量集合上可以执行...
  • 当父进程发现请求 >= 子进程时,父进程创建新的子进程,并把子进程加1(当然子进程有个预先上限);当父进程发现子进程大于请求数加1时,父进程杀死多余的子进程。 总的来说,思想是让子进程accept并处理...
  • 可删除指定时间范围内的数据,支持自动清理早期数据,设置最大保存记录。 支持报警短信转发,支持多个接收手机号码,可设定发送间隔,比如即时发送或者6个小时发送一次所有的报警信息,短信内容过长,自动拆分多条...
  • 信号量的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程...
  • 操作系统实验报告

    2019-01-10 19:24:18
    否则,认为出错,因为它所请求的资源已超过它当前的最大需求。 2. 如果Request i ≤Available,则转向步骤3;否则,表示系统中尚无足够的资源满足Pi的申请,Pi必须等待。 3. 系统试探性地把资源分配给进程Pi,...

空空如也

空空如也

1 2 3
收藏数 43
精华内容 17
关键字:

linux信号量最大数

linux 订阅