精华内容
下载资源
问答
  • Linux睡眠唤醒机制--Kernel态

    首先给大家分享一个巨牛巨牛的人工智能教程,是我无意中发现的。教程不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵段子,像看小说一样,哈哈~我正在学习中,觉得太牛了,所以分享给大家!点这里可以跳转到教程

                   

    一、对于休眠(suspend)的简单介绍
       在Linux中,休眠主要分三个主要的步骤:
       1) 冻结用户态进程和内核态任务
       2) 调用注册的设备的suspend的回调函数, 顺序是按照注册顺序
       3) 休眠核心设备和使CPU进入休眠态, 冻结进程是内核把进程列表中所有的进程的状态都设置为停止,并且保存下所有进程的上下文. 当这些进程被解冻的时候,他们是不知道自己被冻结过的,只是简单的继续执行。
       如何让Linux进入休眠呢?用户可以通过读写sys文件/sys /power/state 是实现控制系统进入休眠. 比如
       # echo mem > /sys/power/state
       命令系统进入休眠. 也可以使用

       # cat /sys/power/state
       来得到内核支持哪几种休眠方式.

    二、Linux Suspend 的流程
    1. 相关代码

         • kernel/kernel/power/main.c
         • kernel/arch/arm/mach-xxx/pm.c
         • kernel/driver/base/power/main.c

         接下来让我们详细的看一下Linux是怎么休眠/唤醒的:
         用户对于/sys/power/state 的读写会调用到 kernel/kernel/power/main.c中的state_store(), 用户可以写入 const char * const pm_states[] 中定义的字符串, 比如"mem", "standby"。

    const char *const pm_states[PM_SUSPEND_MAX] = {
    #ifdef CONFIG_EARLYSUSPEND
     [PM_SUSPEND_ON]  = "on",
    #endif
     [PM_SUSPEND_STANDBY] = "standby",
     [PM_SUSPEND_MEM] = "mem",
    };
          常见有standby(suspend to RAM)、mem(suspend to RAM)和disk(suspend to disk),只是standby耗电更多,返回到正常工作状态的时间更短。     

         然后state_store()会调用enter_state()<注:这是经典Linux调用流程, 在Android系统中,Kernel将调用request_suspend_state,而不是enter_state>,它首先会检查一些状态参数,然后同步文件系统。
     

    /** * enter_state - Do common work of entering low-power state. * @state:  pm_state structure for state we're entering. * * Make sure we're the only ones trying to enter a sleep state. Fail * if someone has beat us to it, since we don't want anything weird to * happen when we wake up. * Then, do the setup for suspend, enter the state, and cleaup (after * we've woken up). */int enter_state(suspend_state_t state){ int error; if (!valid_state(state))  return -ENODEV; if (!mutex_trylock(&pm_mutex))  return -EBUSY;#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE suspend_sys_sync_queue();#else printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); printk("done.\n");#endif pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); error = suspend_prepare(); if (error)  goto Unlock; if (suspend_test(TEST_FREEZER))  goto Finish; pr_debug("PM: Entering %s sleep\n", pm_states[state]); pm_restrict_gfp_mask(); error = suspend_devices_and_enter(state); pm_restore_gfp_mask(); Finish: pr_debug("PM: Finishing wakeup.\n"); suspend_finish(); Unlock: mutex_unlock(&pm_mutex); return error;}


    2. 准备, 冻结进程
           当进入到suspend_prepare()中以后, 它会给suspend分配一个虚拟终端来输出信息, 然后广播一个系统要进入suspend的Notify, 关闭掉用户态的helper进程, 然后一次调用suspend_freeze_processes()冻结所有的进程, 这里会保存所有进程当前的状态, 也许有一些进程会拒绝进入冻结状态, 当有这样的进程存在的时候, 会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程。

     

    /** * suspend_prepare - Do prep work before entering low-power state. * * This is common code that is called for each state that we're entering. * Run suspend notifiers, allocate a console and stop all processes. */static int suspend_prepare(void){ int error; if (!suspend_ops || !suspend_ops->enter)  return -EPERM; pm_prepare_console(); error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); if (error)  goto Finish; error = usermodehelper_disable(); if (error)  goto Finish; error = suspend_freeze_processes(); if (!error)  return 0; suspend_thaw_processes(); usermodehelper_enable(); Finish: pm_notifier_call_chain(PM_POST_SUSPEND); pm_restore_console(); return error;}

    3. 让外设进入休眠
            现在, 所有的进程(也包括workqueue/kthread) 都已经停止了,内核态人物有可能在停止的时候握有一些信号量, 所以如果这时候在外设里面去解锁这个信号量有可能会发生死锁,所以在外设的suspend()函数里面作lock/unlock锁要非常小心,这里建议设计的时候就不要在suspend()里面等待锁。而且因为suspend的时候,有一些Log是无法输出的,所以一旦出现问题,非常难调试。

          然后kernel在这里会尝试释放一些内存。

          最后会调用suspend_devices_and_enter()来把所有的外设休眠, 在这个函数中, 如果平台注册了suspend_ops(通常是在板级定义中定义和注册,在kernel/arch/arm/mach-xx/pm.c中调用suspend_set_ops), 这里就会调用 suspend_ops->begin(); 然后调用dpm_suspend_start,他们会依次调用驱动的suspend() 回调来休眠掉所有的设备。

         当所有的设备休眠以后, suspend_ops->prepare()会被调用, 这个函数通常会作一些准备工作来让板机进入休眠。 接下来Linux,在多核的CPU中的非启动CPU会被关掉,通过注释看到是避免这些其他的CPU造成race condio,接下来的以后只有一个CPU在运行了。

         suspend_ops 是板级的电源管理操作, 通常注册在文件 arch/arch/mach-xxx/pm.c 中.

         接下来, suspend_enter()会被调用, 这个函数会关闭arch irq, 调用 device_power_down(), 它会调用suspend_late()函数, 这个函数是系统真正进入休眠最后调用的函数,通常会在这个函数中作最后的检查。 如果检查没问题, 接下来休眠所有的系统设备和总线,并且调用 suspend_pos->enter() 来使CPU进入省电状态,这时就已经休眠了。代码的执行也就停在这里了。

    /** * suspend_devices_and_enter - suspend devices and enter the desired system *        sleep state. * @state:    state to enter */int suspend_devices_and_enter(suspend_state_t state){ int error; if (!suspend_ops)  return -ENOSYS; trace_machine_suspend(state);         // 如果平台注册了suspend_ops(通常是在板级定义中定义和注册,           // 在kernel/arch/arm/mach-xx/pm.c中调用suspend_set_ops),            // 这里就会调用 suspend_ops->begin(); if (suspend_ops->begin) {  error = suspend_ops->begin(state);  if (error)   goto Close; } suspend_console(); suspend_test_start();         // 依次调用驱动的suspend() 回调来休眠掉所有的设备。 error = dpm_suspend_start(PMSG_SUSPEND); if (error) {  printk(KERN_ERR "PM: Some devices failed to suspend\n");  goto Recover_platform; } suspend_test_finish("suspend devices"); if (suspend_test(TEST_DEVICES))  goto Recover_platform;                 // 这个函数会关闭arch irq, 调用 device_power_down(), 它会调用suspend_late()函数,           // 这个函数是系统真正进入休眠最后调用的函数,通常会在这个函数中作最后的检查。          // 如果检查没问题, 接下来休眠所有的系统设备和总线,并且调用 suspend_pos->enter()          // 来使CPU进入省电状态,这时就已经休眠了。代码的执行也就停在这里了。 error = suspend_enter(state); Resume_devices: suspend_test_start(); dpm_resume_end(PMSG_RESUME); suspend_test_finish("resume devices"); resume_console(); Close: if (suspend_ops->end)  suspend_ops->end(); trace_machine_suspend(PWR_EVENT_EXIT); return error; Recover_platform: if (suspend_ops->recover)  suspend_ops->recover(); goto Resume_devices;}

    三、Linux Resume流程

           如果在休眠中系统被中断或者其他事件唤醒,接下来的代码就会开始执行,这个唤醒的顺序是和休眠的循序相反的,所以系统设备和总线会首先唤醒,使能系统中断,使能休眠时候停止掉的非启动CPU, 以及调用suspend_ops->finish(), 而且在suspend_devices_and_enter()函数中也会继续唤醒每个设备,使能虚拟终端, 最后调用 suspend_ops->end()。

          在返回到enter_state()函数中的,当 suspend_devices_and_enter() 返回以后,外设已经唤醒了,但是进程和任务都还是冻结状态, 这里会调用suspend_finish()来解冻这些进程和任务, 而且发出Notify来表示系统已经从suspend状态退出, 唤醒终端。

          到这里,所有的休眠和唤醒就已经完毕了,系统继续运行了。


    转自:http://bbs.ednchina.com/BLOG_ARTICLE_1784575.HTM

               

    浏览人工智能教程

    展开全文
  • Java线程等待唤醒机制(加深理解)

    万次阅读 多人点赞 2019-08-04 16:28:06
    这里就用到了线程等待唤醒机制,下面具体看一下。 等待唤醒机制示例 下面代码是一个简单的线程唤醒机制示例,主要就是在Activity启动的时候初始化并start线程,线程start后会进入等待状态,在onResume方法中执行...

    今天看源码的时候遇到这样一个场景,某线程里面的逻辑需要等待异步处理结果返回后才能继续执行。或者说想要把一个异步的操作封装成一个同步的过程。这里就用到了线程等待唤醒机制,下面具体看一下。

    等待唤醒机制示例

    下面代码是一个简单的线程唤醒机制示例,主要就是在Activity启动的时候初始化并start线程,线程start后会进入等待状态,在onResume方法中执行notify方法唤醒线程。通过这样的方式模拟异步唤醒线程——线程等待唤醒机制。
     

    public class ThreadDemo extends AppCompatActivity {
        private final static String TAG = ThreadDemo.class.getSimpleName();
    
        private Object mLock = new Object();
        private Thread mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (mLock) {
                    Log.i(TAG,"state 1 = " + mThread.getState());
                    try {
                        mLock.wait(10 * 1000);
                        Log.i(TAG,"state 2 = " + mThread.getState());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Log.i(TAG,"state 3 = " + mThread.getState());
            mThread.start();
            Log.i(TAG,"state = 4 " + mThread.getState());
    
        }
    
        @Override
        protected void onStart() {
            super.onStart();
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            synchronized(mLock) {
                Log.i(TAG,"state = 5 " + mThread.getState());
                mLock.notify();
                Log.i(TAG,"state = 6 " + mThread.getState());
            }
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            Log.i(TAG,"state = 7 " + mThread.getState());
        }
    
        @Override
        protected void onStop() {
            super.onStop();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }

    Log如下:

    09-11 17:31:29.577 32658-32658/com.android.peter.threaddemo I/ThreadDemo: state 3 = NEW
    09-11 17:31:29.578 32658-32658/com.android.peter.threaddemo I/ThreadDemo: state = 4 RUNNABLE
    09-11 17:31:29.578 32658-32695/com.android.peter.threaddemo I/ThreadDemo: state 1 = RUNNABLE
    09-11 17:31:29.588 32658-32658/com.android.peter.threaddemo I/ThreadDemo: state = 5 TIMED_WAITING
    09-11 17:31:29.588 32658-32658/com.android.peter.threaddemo I/ThreadDemo: state = 6 BLOCKED
    09-11 17:31:29.588 32658-32695/com.android.peter.threaddemo I/ThreadDemo: state 2 = RUNNABLE
    09-11 17:31:40.276 32658-32658/com.android.peter.threaddemo I/ThreadDemo: state = 7 TERMINATED

    为什么可以这么用


    我当时看完后产生一个疑问,就是run方法的synchronized代码块不是被锁住了处于TIMED_WAITING状态了么,按理说同步锁应该没有被释放啊,为什么onResume中的synchronized代码块可以被执行从而唤醒线程。 

    这是因为wait是Object类的方法,当被调用时即释放资源也释放锁;而sleep是Thread类的方法,当被调用时只释放资源不释放锁。之前都是用的sleep来使线程进入TIMED_WAITING状态的,所以一直的认知是synchronized代码段执行完毕才会释放锁,然后再执行下一个synchronized代码段。经过本次学习对synchronized同步机制有了新的更深刻的认识。

    java线程的状态


    下面是Thread类中枚举的六种线程状态,借此机会顺便学习一下。
     

        libcore/ojluni/src/main/java/java/lang/Thread.java
        public enum State {
            /**
             * Thread state for a thread which has not yet started.
             */
            NEW,
    
            /**
             * Thread state for a runnable thread.  A thread in the runnable
             * state is executing in the Java virtual machine but it may
             * be waiting for other resources from the operating system
             * such as processor.
             */
            RUNNABLE,
    
            /**
             * Thread state for a thread blocked waiting for a monitor lock.
             * A thread in the blocked state is waiting for a monitor lock
             * to enter a synchronized block/method or
             * reenter a synchronized block/method after calling
             * {@link Object#wait() Object.wait}.
             */
            BLOCKED,
    
            /**
             * Thread state for a waiting thread.
             * A thread is in the waiting state due to calling one of the
             * following methods:
             * <ul>
             *   <li>{@link Object#wait() Object.wait} with no timeout</li>
             *   <li>{@link #join() Thread.join} with no timeout</li>
             *   <li>{@link LockSupport#park() LockSupport.park}</li>
             * </ul>
             *
             * <p>A thread in the waiting state is waiting for another thread to
             * perform a particular action.
             *
             * For example, a thread that has called <tt>Object.wait()</tt>
             * on an object is waiting for another thread to call
             * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
             * that object. A thread that has called <tt>Thread.join()</tt>
             * is waiting for a specified thread to terminate.
             */
            WAITING,
    
            /**
             * Thread state for a waiting thread with a specified waiting time.
             * A thread is in the timed waiting state due to calling one of
             * the following methods with a specified positive waiting time:
             * <ul>
             *   <li>{@link #sleep Thread.sleep}</li>
             *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
             *   <li>{@link #join(long) Thread.join} with timeout</li>
             *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
             *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
             * </ul>
             */
            TIMED_WAITING,
    
            /**
             * Thread state for a terminated thread.
             * The thread has completed execution.
             */
            TERMINATED;
        }

    结合上面代码运行的log来理解一下线程的各个状态。

    • NEW(初始化状态) 

    线程通过new初始化完成到调用start方法前都处于等待状态。

    • RUNNABLE(可执行状态) 

    线程执行start方法后就处于可以行状态。

    • BLOCKED(阻塞状态) 

    notify方法被调用后线程被唤醒,但是这时notify的synchronized代码段并没有执行完,同步锁没有被释放,所以线程处于BLOCKED状态。直到notify的synchronized代码段执行完毕锁被释放,线程才回到wait所在的synchronized代码段继续执行。

    • WAITING(等待状态) 

    调用sleep或是wait方法后线程处于WAITING状态,等待被唤醒。

    • TIMED_WAITING(等待超时状态) 

    调用sleep或是wait方法后线程处于TIMED_WAITING状态,等待被唤醒或时间超时自动唤醒。

    • TERMINATED(终止状态) 

    在run方法结束后线程处于结束状态。

    线程的状态图

    1. 初始状态

    实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

    2.1. 就绪状态

    就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
    调用线程的start()方法,此线程进入就绪状态。
    当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
    当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
    锁池里的线程拿到对象锁后,进入就绪状态。
    2.2. 运行中状态

    线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

    3. 阻塞状态

    阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

    4. 等待

    处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

    5. 超时等待

    处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

    6. 终止状态

    1. 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
    2. 在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

    等待队列

    • 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
    • 与等待队列相关的步骤和图
       

    1. 线程1获取对象A的锁,正在使用对象A。
    2. 线程1调用对象A的wait()方法。
    3. 线程1释放对象A的锁,并马上进入等待队列。
    4. 锁池里面的对象争抢对象A的锁。
    5. 线程5获得对象A的锁,进入synchronized块,使用对象A。
    6. 线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入同步队列。若线程5调用对象A的notify()方法,则唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入同步队列。
    7. notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
    8. 同步队列的线程争抢对象锁,但线程1什么时候能抢到就不知道了。 

    同步队列状态

    • 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁的线程。
    • 当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。
    • 同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列。
    • 线程等待时间到了或被notify/notifyAll唤醒后,会进入同步队列竞争锁,如果获得锁,进入RUNNABLE状态,否则进入BLOCKED状态等待获取锁。

    几个方法的比较

    1. Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
    2. Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
    3. thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
    4. obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
    5. obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
    6. LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。

    sleep()方法与yield()方法区别:

    • sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程优先级。yield()方法只会给同优先级或更高优先级线程执行机会。
    • sleep()方法将当前线程转入阻塞状态,而yield()强制将当前线程转入就绪状态,因此完全可能某个线程调用yield()后立即再次获得CPU资源。
    • sleep()方法申明抛出InterruptException异常,要么捕捉要么显示抛出,而yield()没有申明抛出任何异常。
    • sleep()比yield()有更好的移植性,不建议yield()控制并发线程执行。

     

    疑问

    等待队列里许许多多的线程都wait()在一个对象上,此时某一线程调用了对象的notify()方法,那唤醒的到底是哪个线程?随机?队列FIFO?or sth else?Java文档就简单的写了句:选择是任意性的(The choice is arbitrary and occurs at the discretion of the implementation)。
     

    线程的底层实现


    在Window系统和Linux系统上,Java线程的实现是基于一对一的线程模型,所谓的一对一模型,实际上就是通过语言级别层面程序去间接调用系统内核的线程模型,即我们在使用Java线程时,Java虚拟机内部是转而调用当前操作系统的内核线程来完成当前任务。

    这里需要了解一个术语,内核线程(Kernel-Level Thread,KLT),它是由操作系统内核(Kernel)支持的线程,这种线程是由操作系统内核来完成线程切换,内核通过操作调度器(Scheduler)进而对线程执行调度,并将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这也就是操作系统可以同时处理多任务的原因。

    由于我们编写的多线程程序属于语言层面的,程序一般不会直接去调用内核线程,取而代之的是一种轻量级的进程(Light Weight Process),也是通常意义上的线程,由于每个轻量级进程都会映射到一个内核线程,因此我们可以通过轻量级进程调用内核线程,进而由操作系统内核将任务映射到各个处理器,这种轻量级进程与内核线程间1对1的关系就称为一对一的线程模型。如下图 

    如图所示,每个线程最终都会映射到CPU中进行处理,如果CPU存在多核,那么就可以并行执行多个线程任务。


    延伸:Java并发编程——线程同步和等待唤醒机制

    原文:https://blog.csdn.net/huaxun66/article/details/77942389

    线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。 

    因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个名称叫互斥锁,即能达到互斥访问目的的锁。

    线程同步

    synchronized

    在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能),这点确实也是很重要的。

    synchronized 修饰方法

    方法可以是实例方法,也可以是静态方法。
     

    public class AccountingSync implements Runnable{
        //共享资源(临界资源)
        static int i=0;
        /**
         * synchronized 修饰实例方法
         */
        public synchronized void increase(){
            i++;
        }
        @Override
        public void run() {
            for(int j=0;j<1000000;j++){
                increase();
            }
    
        public static void main(String[] args) throws InterruptedException {
            AccountingSync instance=new AccountingSync();
            Thread t1=new Thread(instance);
            Thread t2=new Thread(instance);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
        /**
         * 输出结果:
         * 2000000
         */
    }

    上面synchronized修饰的是实例方法increase(),这样的情况下,当前线程的锁便是实例对象instance,注意Java中的线程同步锁可以是任意对象。

    当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法,毕竟一个对象只有一把锁。但其他线程还是可以访问该实例对象的其他非synchronized方法。

    如果我们改下代码:

    //new新实例
    Thread t1=new Thread(new AccountingSync());
    Thread t2=new Thread(new AccountingSync());

    虽然我们使用synchronized修饰了increase()方法,但却new了两个不同的实例对象,这也就意味着存在着两个不同的实例对象锁,因此t1和t2线程使用的是不同的锁,因此线程安全是无法保证的。解决这种困境的方式是将synchronized作用于静态的increase()方法,这样对象锁就是当前类对象,无论创建多少个实例对象,但对于类的对象有且只有一个,所以在这样的情况下对象锁就是唯一的。
     

    public class AccountingSync implements Runnable{
        static int i=0;
        /**
         * 作用于静态方法,锁是当前class对象,也就是
         * AccountingSync类对应的class对象
         */
        public static synchronized void increase(){
            i++;
        }
    
        @Override
        public void run() {
            for(int j=0;j<1000000;j++){
                increase();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            //new新实例
            Thread t1=new Thread(new AccountingSync());
            Thread t2=new Thread(new AccountingSync());
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
    }

    synchronized 同步代码块

    除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块:

    public class AccountingSync implements Runnable{
        static AccountingSync instance=new AccountingSync();
        static int i=0;
        @Override
        public void run() {
            //省略其他耗时操作....
            //使用同步代码块对变量i进行同步操作,锁对象为instance
            synchronized(instance){
                for(int j=0;j<1000000;j++){
                     i++;
                  }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread t1=new Thread(instance);
            Thread t2=new Thread(instance);
            t1.start();t2.start();
            t1.join();t2.join();
            System.out.println(i);
        }
    }

    当然除了instance作为对象外,我们还可以使用this对象(代表当前实例)或者当前类的class对象作为锁,如下代码:

    //this,当前实例对象锁
    synchronized(this){
        for(int j=0;j<1000000;j++){
            i++;
        }
    }
    
    //class对象锁
    synchronized(AccountingSync.class){
        for(int j=0;j<1000000;j++){
            i++;
        }
    }

    在Java中synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。

    Lock

    synchronized属于隐式锁,即锁的持有与释放都是隐式的,我们无需干预,接下来我们讲讲显式锁,即锁的持有和释放都必须由我们手动编写。在Java 1.5中,官方在concurrent并发包中加入了Lock接口,该接口中提供了lock()方法和unLock()方法对显式加锁和显式释放锁操作进行支持,简单了解一下代码编写,如下:
     

    Lock lock = new ReentrantLock();
    lock.lock();
    try{
        //临界区......
    }finally{
        lock.unlock();
    }

    正如代码所示(ReentrantLock是Lock的实现类),当前线程使用lock()与unlock()对临界区进行包围,其他线程由于无法持有锁将无法进入临界区直到当前线程释放锁,注意unlock()操作必须在finally代码块中,这样可以确保即使临界区执行抛出异常,线程最终也能正常释放锁,Lock接口还提供了锁以下相关方法:
     

    public interface Lock {
        //加锁
        void lock();
    
        //解锁
        void unlock();
    
        //可中断获取锁,与lock()不同之处在于可响应中断操作,即在获
        //取锁的过程中可中断,注意synchronized在获取锁时是不可中断的
        void lockInterruptibly() throws InterruptedException;
    
        //尝试非阻塞获取锁,调用该方法后立即返回结果,如果能够获取则返回true,否则返回false
        boolean tryLock();
    
        //根据传入的时间段获取锁,在指定时间内没有获取锁则返回false,如果在指定时间内当前线程未被中并断获取到锁则返回true
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
        //获取等待通知组件,该组件与当前锁绑定,当前线程只有获得了锁
        //才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
        Condition newCondition();
    }

    可见Lock对象锁还提供了synchronized所不具备的其他同步特性,如等待可中断锁的获取(synchronized在等待获取锁时是不可中断的),超时中断锁的获取,可实现公平锁,等待唤醒机制的多条件变量Condition等,这也使得Lock锁在使用上具有更大的灵活性。下面进一步分析Lock的实现类重入锁ReetrantLock。

    重入锁ReetrantLock,JDK 1.5新增的类,实现了Lock接口,作用与synchronized关键字相当,但比synchronized更加灵活。ReetrantLock本身也是一种支持重进入的锁,即该锁可以支持一个线程对资源重复加锁,同时也支持公平锁与非公平锁。所谓的公平与非公平指的是在请求先后顺序上,先对锁进行请求的就一定先获取到锁,那么这就是公平锁,反之,如果对于锁的获取并没有时间上的先后顺序,如后请求的线程可能先获取到锁,这就是非公平锁,一般而言非,非公平锁机制的效率往往会胜过公平锁的机制,但在某些场景下,可能更注重时间先后顺序,那么公平锁自然是很好的选择。
     

    public class ReenterLock implements Runnable{
        public static ReentrantLock lock=new ReentrantLock();
        public static int i=0;
        @Override
        public void run() {
            for(int j=0;j<10000000;j++){
                //支持重入锁
                lock.lock();
                lock.lock();
                try{
                    i++;
                }finally{
                    //执行两次解锁
                    lock.unlock();
                    lock.unlock();
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            ReenterLock tl=new ReenterLock();
            Thread t1=new Thread(tl);
            Thread t2=new Thread(tl);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            //输出结果:20000000
            System.out.println(i);
        }
    }

    等待唤醒机制


    进入临界区时,却发现在某一个条件满足之后,它才能执行。要使用一个条件对象来管理那些已经获得了一个锁但是却不能做有用工作的线程,条件对象又称作条件变量。

    synchronized


    synchronized等待唤醒机制主要指的是notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常。
     

    synchronized (obj) {
           obj.wait();
           obj.notify();
           obj.notifyAll();         
     }

    需要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被阻塞,wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。

    notify/notifyAll方法只是解除了等待线程的阻塞,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。

    假设一个场景我们需要用银行转账:
     

    public class Bank {
    private double[] accounts;
    
        //我们首先写了银行的类,它的构造函数需要传入账户数量和账户金额
        public Bank(int n,double initialBalance){
            accounts=new double[n];
            for (int i=0;i<accounts.length;i++){
                accounts[i]=initialBalance;
            }
        }
    
        //转账方法,from是转账方,to是接收方,amount转账金额
        public synchronized void transfer(int from,int to,int amount) throws InterruptedException{
        while (accounts[from]<amount) {
            wait();
        }
        //转账的操作
        accounts[from] = accounts[from] - amount;
        accounts[to] = accounts[to] + amount;
        notifyAll();   
        } 
    }

    Lock


    Lock等待唤醒机制与synchronized有点不同,需要用到Condition条件对象。一个锁对象拥有多个相关的条件对象,可以用newCondition方法获得一个条件对象,调用条件对象的signal/signalAll和await方法。同样这三个方法必须被Lock包住。
     

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    lock.lock();
    try{
         condition.await();
         condition.signal();
         condition.signalAll();
    }finally{
        lock.unlock();
    }

    使用Lock等待唤醒机制修改上面的例子:

    public class Bank {
    private double[] accounts;
        private Lock bankLock;
        private Condition condition;
        //构造函数
        public Bank(int n,double initialBalance){
            accounts=new double[n];
            //得到锁
            bankLock=new ReentrantLock();
            //得到条件对象
            condition=bankLock.newCondition();
            for (int i=0;i<accounts.length;i++){
                accounts[i]=initialBalance;
            }
        }
    
        //转账方法
        public void transfer(int from,int to,int amount) throws InterruptedException {
            bankLock.lock();
            try{
                while (accounts[from]<amount){
                    //阻塞当前线程,并放弃锁
                    condition.await();
                }
                //转账的操作
                accounts[from] = accounts[from] - amount;
                accounts[to] = accounts[to] + amount;
                condition.signalAll(); 
            } finally {
                bankLock.unlock();
            }
        }
    }

    一旦一个线程调用了await方法,它就会进入该条件的等待集。当锁可用时,该线程不能马上解锁,相反他处于阻塞状态,直到另一个线程调用了同一个条件上的signalAll方法时为止。当另一个线程转账给我们此前的转账方时,需要调用condition.signalAll();该调用会重新激活因为这一条件而等待的所有线程。

    当一个线程调用了await方法他没法重新激活自身,并寄希望于其他线程来调用signalAll方法来激活自身,如果没有其他线程来激活等待的线程,那么就会产生死锁现象,如果所有的其他线程都被阻塞,最后一个活动线程在解除其他线程阻塞状态前调用await,那么它也被阻塞,就没有任何线程可以解除其他线程的阻塞,程序就被挂起了。

    当调用signalAll方法时并不是立即激活一个等待线程,它仅仅解除了等待线程的阻塞,以便这些线程能够在当前线程释放锁后,通过竞争实现对对象的访问。还有一个方法是signal,它则是随机解除某个线程的阻塞。

    Lock相比synchronized优劣


    优势: 
    Lock对象锁提供了synchronized所不具备的同步特性

    • 等待可中断锁的获取 

    等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断特性对处理执行时间非常长的同步块很有帮助。

    • 可实现公平锁 

    公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁;而非公平锁则不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁是非公平的,ReentrantLock默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。

    • 等待唤醒机制的多条件变量Condition 

    锁绑定多个条件是指一个ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁对象的wait()和notify()或notifyAll()方法只能实现一个隐含的条件,如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无须这样做,只需要多次调用newCondition()方法即可。

    Lock可通过多个Condition实例对象建立更加精细的线程控制。使用condition.signalAll(); 可以指定解除哪些等待线程的阻塞,是通过condition关联的。而notifyAll只会解除所有等待线程的阻塞。

    如下面例子:银行账户只能有取款和存款操作,每次取款和存款金额必须相同,当账户有钱时只允许取款,不允许存款,账户没钱时只允许存款,不允许取款。
     

    public class Bank {
        private double[] accounts;
        private amount;
        private Lock bankLock;
        private Condition drawCondition; //取款条件
        private Condition depositeCondition; //存款条件
        //构造函数
        public Bank(int n,double initialBalance){
            accounts=new double[n];
            //得到锁
            bankLock=new ReentrantLock();
            //分别得到条件对象
            drawCondition=bankLock.newCondition();
            depositeCondition=bankLock.newCondition();
    
            for (int i=0;i<accounts.length;i++){
                accounts[i]=initialBalance;
            }
            amount=initialBalance;
        }
    
        //取款方法
        public void draw(int from) throws InterruptedException {
            bankLock.lock();
            try{
                while (accounts[from]<amount){
                    //阻塞当前线程,并放弃锁
                    drawCondition.await();
                }
                //取款的操作
                accounts[from] = accounts[from] - amount;
                depositeCondition.signalAll();
            } finally {
                bankLock.unlock();
            }
        }
    
        //存款方法
        public void deposite(int to) throws InterruptedException {
            bankLock.lock();
            try{
                while (accounts[to]>=amount){
                    //阻塞当前线程,并放弃锁
                    depositeCondition.await();
                }
                //存款的操作
                accounts[to] = accounts[to] + amount;
                drawCondition.signalAll();
            } finally {
                bankLock.unlock();
            }
        }
    }

    我们通过两个Condition对象单独控制取款线程与存款线程,这样可以避免取款线程在唤醒线程时唤醒的还是取款线程,如果是通过synchronized的等待唤醒机制实现的话,就可能无法避免这种情况,毕竟同一个锁,对于synchronized关键字来说只能有一组等待唤醒队列,而不能像Condition一样,同一个锁拥有多个等待队列。

    缺点: 
    编程稍复杂。

    synchronized底层实现

    Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现, 无论是显式同步(有明确的 monitorenter 和 monitorexit 指令,即同步代码块)还是隐式同步(同步方法)都是如此。在 Java 语言中,同步用的最多的地方可能是被 synchronized 修饰的同步方法。同步方法并不是由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的。

    下面先来了解一个概念Java对象头,这对深入理解synchronized实现原理非常关键。

    理解Java对象头与Monitor

    在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。如下: 

    • 实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
    • 填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。
    • 对象头:它实现synchronized的锁对象的基础,一般而言,synchronized使用的锁对象是存储在Java对象头里的,jvm中采用2个字来存储对象头(如果对象是数组则会分配3个字,多出来的1个字记录的是数组长度),其主要结构是由Mark 

    Word 和 Class Metadata Address 组成,其结构说明如下表:

    由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间: 

    这里我们主要分析一下重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

    ObjectMonitor() {
        _header       = NULL;
        _count        = 0; //记录个数
        _waiters      = 0,
        _recursions   = 0;
        _object       = NULL;
        _owner        = NULL; //持有ObjectMonitor对象的线程
        _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _Responsible  = NULL ;
        _succ         = NULL ;
        _cxq          = NULL ;
        FreeNext      = NULL ;
        _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
        _SpinFreq     = 0 ;
        _SpinClock    = 0 ;
        OwnerIsThread = 0 ;
      }

    ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示: 

    由此看来,monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因,下面我们将进一步分析synchronized在字节码层面的具体语义实现。

    synchronized同步代码块


    现在我们重新定义一个synchronized修饰的同步代码块,在代码块中操作共享变量i,如下:

    public class SyncCodeBlock {
       public int i;
       public void syncTask(){
           //同步代码块
           synchronized (this){
               i++;
           }
       }
    }

    编译上述代码并使用javap反编译后得到字节码如下:

    Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncCodeBlock.class
      Last modified 2017-6-2; size 426 bytes
      MD5 checksum c80bc322c87b312de760942820b4fed5
      Compiled from "SyncCodeBlock.java"
    public class com.zejian.concurrencys.SyncCodeBlock
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
      //........省略常量池中数据
      //构造函数
      public com.zejian.concurrencys.SyncCodeBlock();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 7: 0
      //===========主要看看syncTask方法实现================
      public void syncTask();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=3, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter  //注意此处,进入同步方法
             4: aload_0
             5: dup
             6: getfield      #2             // Field i:I
             9: iconst_1
            10: iadd
            11: putfield      #2            // Field i:I
            14: aload_1
            15: monitorexit   //注意此处,退出同步方法
            16: goto          24
            19: astore_2
            20: aload_1
            21: monitorexit //注意此处,退出同步方法
            22: aload_2
            23: athrow
            24: return
          Exception table:
          //省略其他字节码.......
    }
    SourceFile: "SyncCodeBlock.java"

    我们主要关注字节码中的如下代码

    3: monitorenter  //进入同步方法
    //..........省略其他  
    15: monitorexit   //退出同步方法
    16: goto          24
    //.........省略其他
    21: monitorexit //退出同步方法

    从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor,重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。

    synchronized同步方法


    方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor,然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。下面我们看看字节码层面如何实现:

    public class SyncMethod {
       public int i;
       public synchronized void syncTask(){
               i++;
       }
    }

    使用javap反编译后的字节码如下:

    Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncMethod.class
      Last modified 2017-6-2; size 308 bytes
      MD5 checksum f34075a8c059ea65e4cc2fa610e0cd94
      Compiled from "SyncMethod.java"
    public class com.zejian.concurrencys.SyncMethod
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool;
    
       //省略没必要的字节码
      //==================syncTask方法======================
      public synchronized void syncTask();
        descriptor: ()V
        //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: dup
             2: getfield      #2                  // Field i:I
             5: iconst_1
             6: iadd
             7: putfield      #2                  // Field i:I
            10: return
          LineNumberTable:
            line 12: 0
            line 13: 10
    }
    SourceFile: "SyncMethod.java"

    从字节码中可以看出,synchronized修饰的方法并没有monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。这便是synchronized锁在同步代码块和同步方法上实现的基本原理。

    Java虚拟机对synchronized的优化


    锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

    偏向锁


    偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。

    轻量级锁


    倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

    自旋锁


    轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了。

    锁消除


    消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如下StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。
     

    public class StringBufferRemoveSync {
    
        public void add(String str1, String str2) {
            //StringBuffer是线程安全,由于sb只会在append方法中使用,不可能被其他线程引用
            //因此sb属于不可能共享的资源,JVM会自动消除内部的锁
            StringBuffer sb = new StringBuffer();
            sb.append(str1).append(str2);
        }
    
        public static void main(String[] args) {
            StringBufferRemoveSync rmsync = new StringBufferRemoveSync();
            for (int i = 0; i < 10000000; i++) {
                rmsync.add("abc", "123");
            }
        }
    }

    无锁同步

    在谈论无锁概念时,总会关联起乐观派与悲观派,对于乐观派而言,他们认为事情总会往好的方向发展,总是认为坏的情况发生的概率特别小,可以无所顾忌地做事,但对于悲观派而已,他们总会认为发展事态如果不及时控制,以后就无法挽回了,即使无法挽回的局面几乎不可能发生。这两种派系映射到并发编程中就如同加锁与无锁的策略,即加锁是一种悲观策略,无锁是一种乐观策略,因为对于加锁的并发程序来说,它们总是认为每次访问共享资源时总会发生冲突,因此必须对每一次数据操作实施加锁策略。而无锁则总是假设对共享资源的访问没有冲突,线程可以不停执行,无需加锁,无需等待,一旦发现冲突,无锁策略则采用一种称为CAS的技术来保证线程执行的安全性,这项CAS技术就是无锁策略实现的关键,下面我们进一步了解CAS技术的奇妙之处。

    CAS


    CAS的全称是Compare And Swap 即比较交换,CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。其算法核心思想如下 
    执行函数:CAS(V,E,N) 
    其包含3个参数

    • V表示要更新的变量
    • E表示预期值
    • N表示新值


    如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行CAS操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。

    由于Unsafe类不是提供给用户程序调用的类(Unsafe.getUnsafe()的代码中限制了只有启动类加载器(Bootstrap ClassLoader)加载的Class才能访问它),因此,如果不采用反射手段,我们只能通过其他的Java API来间接使用它,如J.U.C包里面的整数原子类AtomicInteger,其中的compareAndSet()和getAndIncrement()等方法都使用了Unsafe类的CAS操作。

    public class Test {
        public AtomicInteger inc = new AtomicInteger();
    
        public void increase() {
            inc.getAndIncrement();
        }
    
        public static void main(String[] args) {
            final Test test = new Test();
            for(int i=0;i<10;i++){
                new Thread(){
                    public void run() {
                        for(int j=0;j<1000;j++)
                            test.increase();
                    };
                }.start();
            }
    
            while(Thread.activeCount()>1)  //保证前面的线程都执行完
                Thread.yield();
            System.out.println(test.inc);
        }
    }
    //输出10000

    ABA


    尽管CAS看起来很美,但显然这种操作无法涵盖互斥同步的所有使用场景,并且CAS从语义上来说并不是完美的,存在这样的一个逻辑漏洞:如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然为A值,那我们就能说它的值没有被其他线程改变过了吗?如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。J.U.C包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。不过目前来说这个类比较“鸡肋”,大部分情况下ABA问题不会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。
     

    展开全文
  • 驱动中每个接口函数都有一个file指针,通过这个参数就能取得应用程序打开方式(阻塞或非阻塞) 等待队列 内核对这种阻塞提供等待队列机制来实现,这样可以改善实时性问题。 等待队列头数据结构 内核使用这个结构来...

    阻塞与非阻塞的概念:

    阻塞IO: 当数据不可读或不可写,进程休眠,直到得到数据可读或可写时才返回。阻塞效率高,实时性比较好。

    非阻塞IO:不管数据是否可读可写,都马上返回。

    进程和线程

    我们先从 Linux 的进程谈起,操作系统要运行一个可执行程序,首先要将程序文件加载到内存,然后 CPU 去读取和执行程序指令,而一个进程就是“一次程序的运行过程”,内核会给每一个进程创建一个名为task_struct的数据结构,而内核也是一段程序,系统启动时就被加载到内存中了。

    进程在运行过程中要访问内存,而物理内存是有限的,比如 16GB,那怎么把有限的内存分给不同的进程使用呢?跟 CPU 的分时共享一样,内存也是共享的,Linux 给每个进程虚拟出一块很大的地址空间,比如 32 位机器上进程的虚拟内存地址空间是 4GB,从 0x00000000 到 0xFFFFFFFF。但这 4GB 并不是真实的物理内存,而是进程访问到了某个虚拟地址,如果这个地址还没有对应的物理内存页,就会产生缺页中断,分配物理内存,MMU(内存管理单元)会将虚拟地址与物理内存页的映射关系保存在页表中,再次访问这个虚拟地址,就能找到相应的物理内存页。每个进程的这 4GB 虚拟地址空间分布如下图所示:

    内核如何阻塞与唤醒进程?

     

    进程的虚拟地址空间总体分为用户空间和内核空间,低地址上的 3GB 属于用户空间,高地址的 1GB 是内核空间,这是基于安全上的考虑,用户程序只能访问用户空间,内核程序可以访问整个进程空间,并且只有内核可以直接访问各种硬件资源,比如磁盘和网卡。那用户程序需要访问这些硬件资源该怎么办呢?答案是通过系统调用,系统调用可以理解为内核实现的函数,比如应用程序要通过网卡接收数据,会调用 Socket 的 read 函数:

    ssize_t read(int fd,void *buf,size_t nbyte)

    CPU 在执行系统调用的过程中会从用户态切换到内核态,CPU 在用户态下执行用户程序,使用的是用户空间的栈,访问用户空间的内存;当 CPU 切换到内核态后,执行内核代码,使用的是内核空间上的栈。

    从上面这张图我们看到,用户空间从低到高依次是代码区、数据区、堆、共享库与 mmap 内存映射区、栈、环境变量。其中堆向高地址增长,栈向低地址增长。

    请注意用户空间上还有一个共享库和 mmap 映射区,Linux 提供了内存映射函数 mmap, 它可将文件内容映射到这个内存区域,用户通过读写这段内存,从而实现对文件的读取和修改,无需通过 read/write 系统调用来读写文件,省去了用户空间和内核空间之间的数据拷贝,Java 的 MappedByteBuffer 就是通过它来实现的;用户程序用到的系统共享库也是通过 mmap 映射到了这个区域。

    我在开始提到的task_struct结构体本身是分配在内核空间,它的vm_struct成员变量保存了各内存区域的起始和终止地址,此外task_struct中还保存了进程的其他信息,比如进程号、打开的文件、创建的 Socket 以及 CPU 运行上下文等。

    在 Linux 中,线程是一个轻量级的进程,轻量级说的是线程只是一个 CPU 调度单元,因此线程有自己的task_struct结构体和运行栈区,但是线程的其他资源都是跟父进程共用的,比如虚拟地址空间、打开的文件和 Socket 等。

    阻塞与唤醒

    我们知道当用户线程发起一个阻塞式的 read 调用,数据未就绪时,线程就会阻塞,那阻塞具体是如何实现的呢?

    Linux 内核将线程当作一个进程进行 CPU 调度,内核维护了一个可运行的进程队列,所有处于TASK_RUNNING状态的进程都会被放入运行队列中,本质是用双向链表将task_struct链接起来,排队使用 CPU 时间片,时间片用完重新调度 CPU。所谓调度就是在可运行进程列表中选择一个进程,再从 CPU 列表中选择一个可用的 CPU,将进程的上下文恢复到这个 CPU 的寄存器中,然后执行进程上下文指定的下一条指令。

    内核如何阻塞与唤醒进程?

     

     

    而阻塞的本质就是将进程的task_struct移出运行队列,添加到等待队列,并且将进程的状态的置为TASK_UNINTERRUPTIBLE或者TASK_INTERRUPTIBLE,重新触发一次 CPU 调度让出 CPU。

    那线程怎么唤醒呢?线程在加入到等待队列的同时向内核注册了一个回调函数,告诉内核我在等待这个 Socket 上的数据,如果数据到了就唤醒我。这样当网卡接收到数据时,产生硬件中断,内核再通过调用回调函数唤醒进程。唤醒的过程就是将进程的task_struct从等待队列移到运行队列,并且将task_struct的状态置为TASK_RUNNING,这样进程就有机会重新获得 CPU 时间片。

    这个过程中,内核还会将数据从内核空间拷贝到用户空间的堆上。

    内核如何阻塞与唤醒进程?

     

    当 read 系统调用返回时,CPU 又从内核态切换到用户态,继续执行 read 调用的下一行代码,并且能从用户空间上的 Buffer 读到数据了。

    小结

    今天我们谈到了一次 Socket read 系统调用的过程:首先 CPU 在用户态执行应用程序的代码,访问进程虚拟地址空间的用户空间;read 系统调用时 CPU 从用户态切换到内核态,执行内核代码,内核检测到 Socket 上的数据未就绪时,将进程的task_struct结构体从运行队列中移到等待队列,并触发一次 CPU 调度,这时进程会让出 CPU;当网卡数据到达时,内核将数据从内核空间拷贝到用户空间的 Buffer,接着将进程的task_struct结构体重新移到运行队列,这样进程就有机会重新获得 CPU 时间片,系统调用返回,CPU 又从内核态切换到用户态,访问用户空间的数据。

     

    应用程序是否能实现阻塞或非阻塞是取决于驱动程序。实际驱动中应该把阻塞和非阻塞这种选择权交给应用程序来选择。要实现这个效果 ,就必须让驱动程序知道应用程序的选择。这个信息是通过 file 结构来传递的。

    struct file 结构中有成员:
        unsigned int         f_flags;
    存放的就是 open(path,flag);
    中的flag参数。

    驱动中每个接口函数都有一个file指针,通过这个参数就能取得应用程序打开方式(阻塞或非阻塞)


    等待队列

    内核对这种阻塞提供等待队列机制来实现,这样可以改善实时性问题。

    等待队列头数据结构

    内核使用这个结构来给进程一个休眠的地方。
    定义如下:Wait.h (include\linux)
    struct __wait_queue_head {
      spinlock_t lock;
      struct list_head task_list;
    };
    typedef struct __wait_queue_head wait_queue_head_t;

    1.等待队列的睡眠

    wait_event(wq, condition) ;建立不可以杀进程(信号不能唤醒,效果和msleep相同)。
    wait_event_interruptible(wq, condition)    ;它可以被被信号唤醒。休眠过程中,进程可以接收信号,收到后不管条件如何,直接返回。
    wait_event_timeout(wq, condition, timeout)    ;休眠期间效果和 wait_event ,但是有一个超时时间 ,时间到,不管条件如何,直接返回。
    wait_event_interruptible_timeout(wq, condition, timeout);休眠期间效果和 wait_event_interruptible相同,区别是有超时功能。时间 到,不管条件如何,直接返回。

    wq 是变量

    2.等待队列的唤醒

    通过调用 wake_up* 函数(表面上是宏)唤醒进程

    wake_up(wq)  能用于唤醒各种方式进入休眠的进程,只唤醒队列上的一个进程。
    wake_up_all(wq)效果和wake_up相同,只是能唤醒队列上所有的进程。
    wake_up_interruptible(wq)   只能用于唤醒一个 使用wait_event_interruptible*休眠的进程。
    wake_up_interruptible_all(wq)能唤醒队列所有 使用wait_event_interruptible*休眠的进程。
    wq 是指针。

    以上函数并不是说一调用,就一定可以把进程唤醒,其实它会先去检测进程休眠的条件,是否变成真了,如果为真才把它唤醒。

    3. 等待队列 API

    1)定义一个等待不队列头变量 ,并且 初始化。有两种方法:

    方法1:动态定义:
    wait_queue_head_t  wq;   //全局变量

    在模块初始化函数中调用:
    init_waitqueue_head(&wq);   //安装模块时候执行了初始化

    方法2:静态初始化
    DECLARE_WAIT_QUEUE_HEAD(wq)   ;    //这句等效于上面两句。

    DECLARE_WAIT_QUEUE_HEAD(name) 表示定义一个 名字为name 的等待队列头变量,并且进行初始化。

    2)在需要休眠的地方调用 wait_event*类宏让进程休眠

    示例:
    在按键驱动代码基础上修改read函数的休眠代码

    /* 按键驱动read接口 */
    static int buttons_read(struct file *filp,
                                         char __user *buff,
                                         size_t count,
                                         loff_t *offp)
    {
      unsigned long err;
     
      /* count ==0 ,则返回0,不是错误 */
      if(!count) {
        return 0;
      }
     
      /* 没有按键动作 */
      if ( press==0 ) {
        if ( filp->f_flags & O_NONBLOCK) {
          return  -1;
        }
     
        else { /* 用户以阻塞方式打开 */
          wait_event_interruptible(wq, press);
        }
      }
     
      /*清按键动作标记*/
      press = 0;
     
      /* 修正大小 */
      count = min(sizeof(key_values), count);
      /* 复制按键缓冲数据给用户空间 */
      err = copy_to_user((void __user *)buff, (const void *)(&key_values), count);
      if(err) {
        return -EFAULT;
      }
     
      return count;
    }

    3)在需要唤醒的地方(一般是要中断程序中)把 休眠条件设置真,然后调用 wake_up* 类宏唤醒进程。

    接上一步修改 中断服务程序:
    在原来的 press = 1 ;
    后面添加  wake_up_interruptible 代码,如下。

    press = 1;  //设置按键动作标志位
    // wake_up(&wq) ; //唤醒进程
    wake_up_interruptible(&wq) ; //唤醒进程


    现在要解决的问题是给应用程序提供一个接口,告诉它是否有按键动作发生了。这个接口就是驱动 中文件操作结构中的 poll 接口。

    linux 驱动 poll 接口---------- 这个接口是用来告诉应用程序本设备是否可以读,或是否可写。

    unsigned int    xxxx_poll(  struct file *file,struct poll_table_struct *wait)
    poll 接口模板 :

    1) 定义一个等待队列头,并且 初始化
    2)按照以下的代码模板实现poll接口。

    //静态定义名字是button_waitq的等待队列头, 并且对它进行初始化
    	static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
    	
    	/* 按键驱动poll接口 */
    	static unsigned int buttons_poll(  struct file *file,struct poll_table_struct *wait)
    	{
    	    unsigned int mask = 0;
    	    
    	    poll_wait(file, &button_waitq, wait);  //把等待队列加到查询表中,固定方式
    	    
    	   //press 非0表示有按键动作 
    	    if (press)
    	        mask |= POLLIN | POLLRDNORM;
    	
    	  //如存在一个变量wr_flag表示设备可以写,则是mask |=  POLLOUT| POLLWRNORM  
    	/*   if(wr_flag)
    	       mask |=  POLLOUT| POLLWRNORM;
    	  */     
    	    return mask;
    }

    poll 的系统调用接口 select()函数

     

    一、驱动实现select机制的步骤

        1、首先初始化一个等待队列头
        2、在驱动中实现poll函数,该函数只需做两件事情
            a、使用poll_wait()函数将等待队列添加到poll_table中。
            b、返回描述设备是否可读或可写的掩码。
        3、在驱动的相应地方调用wake_up()函数,唤醒等待队列。

        两点说明:
            a、等待队列
                select函数阻塞的原理,实际上是通过等待队列实现的,若对等待队列不熟悉,请看我的另一篇文章《等待队列的简单使用》。否则看以下的 “select机制内核代码走读” 会很吃力。
            b、掩码值及含义
                POLLIN
                如果设备可被不阻塞地读, 这个位必须设置.

                POLLRDNORM
                这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM ).

                POLLRDBAND
                这个位指示带外数据可用来从设备中读取. 当前只用在 Linux 内核的一个地方( DECnet 代码 )并且通常对设备驱动不可用.

                POLLPRI
                高优先级数据(带外)可不阻塞地读取. 这个位使 select 报告在文件上遇到一个异常情况, 因为 selct 报告带外数据作为一个异常情况.

                POLLHUP
                当读这个设备的进程见到文件尾, 驱动必须设置 POLLUP(hang-up). 一个调用 select 的进程被告知设备是可读的, 如同 selcet 功能所规定的.

                POLLERR
                一个错误情况已在设备上发生. 当调用 poll, 设备被报告位可读可写, 因为读写都返回一个错误码而不阻塞.
                
                POLLOUT
                这个位在返回值中设置, 如果设备可被写入而不阻塞.

                POLLWRNORM
                这个位和 POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备返回( POLLOUT|POLLWRNORM).

                POLLWRBAND
                如同 POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有 poll 的数据报实现使用这个位, 因为一个数据报看传送带外数据.

                注:应当重复一下 POLLRDBAND 和 POLLWRBAND 仅仅对关联到 socket 的文件描述符有意义: 通常设备驱动不使用这些标志!

    二、以按键驱动为例

           
            驱动代码button.c

    
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/types.h>
    #include <linux/fcntl.h>
    #include <linux/mm.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/errno.h>
    #include <linux/init.h>
    #include <linux/device.h>
    #include <linux/init.h>
    #include <linux/major.h>
    #include <linux/delay.h>
    #include <linux/io.h>
    #include <asm/uaccess.h>
    #include <linux/poll.h>
    #include <linux/irq.h>
    #include <asm/irq.h>
    #include <linux/interrupt.h>
    #include <asm/uaccess.h>
    #include <linux/platform_device.h>
    #include <linux/cdev.h>
    #include <linux/miscdevice.h>
    #include <linux/sched.h>
    #include <linux/gpio.h>
    #include <asm/gpio.h>
     
    #define BUTTON_NAME "poll_button"
    #define BUTTON_GPIO 140
     
    static int button_major = 0;                              
    static int button_minor = 0;
    static struct cdev button_cdev;                               
    static struct class *p_button_class = NULL;            
    static struct device *p_button_device = NULL;    
     
    static struct timer_list button_timer;
    static volatile int ev_press = 0;
    static volatile char key_value[] = {0};
    static int old_value;
    static int Button_Irq = 0;
    static int flag_interrupt = 1;
     
    static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
     
    static irqreturn_t buttons_interrupt(int irq, void *dev_id)
    {
        if(flag_interrupt) {
            flag_interrupt = 0;
            old_value = gpio_get_value(BUTTON_GPIO);
            mod_timer(&button_timer,jiffies + HZ/100);    //启动消抖定时器,消抖时间10ms
        }
        
        return IRQ_RETVAL(IRQ_HANDLED);
    }
     
    static void button_timer_handle(unsigned long arg)
    {
        int tmp_value;
        
        tmp_value = gpio_get_value(BUTTON_GPIO);
        
        if(tmp_value == old_value) {
            key_value[0] = tmp_value;        
            ev_press= 1;                                 //有按键按下,唤醒等待队列
            wake_up_interruptible(&button_waitq);        
        }
     
        flag_interrupt = 1;        
    }
     
    static int button_open(struct inode *inode,struct file *file)
    {
        Button_Irq = gpio_to_irq(BUTTON_GPIO);
        enable_irq(Button_Irq);
     
        if(request_irq(Button_Irq, buttons_interrupt, IRQF_TRIGGER_FALLING, "BUTTON_IRQ", NULL) != 0) {
            printk("request irq failed !!! \n");
            disable_irq(Button_Irq);
            free_irq(Button_Irq, NULL);
            return -EBUSY;
        }
        
        return 0;
    }
     
    static int button_close(struct inode *inode, struct file *file)
    {
        free_irq(Button_Irq, NULL);
        return 0;
    }
     
     
    static int button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
    {
        unsigned long err; 
     
        if (filp->f_flags & O_NONBLOCK) {        
            /*nothing to do*/
            //如果没有使用select机制,并且应用程序设置了非阻塞O_NONBLOCK,那么驱动这里就不使用等待队列进行等待。
        } else { 
            wait_event_interruptible(button_waitq, ev_press); //如果应用层没有使用select,直接读的话,这里会阻塞,直到按键按下。如果使用select机制,进来这里时ev_press为真,不会阻塞。 
        }
         
        err = copy_to_user(buff, (const void *)key_value, min(sizeof(key_value), count)); 
        key_value[0] = 0; 
        ev_press = 0; 
         
        return err ? -EFAULT : min(sizeof(key_value), count);
    }
     
    static unsigned int button_poll(struct file *file, struct poll_table_struct *wait)
    {
        unsigned int mask = 0;
     
        //将等待队列添加到poll_table中
        poll_wait(file, &button_waitq, wait);
     
        if(ev_press) {    
            //返回描述设备是否可读或可写的掩码    
            mask = POLLIN | POLLRDNORM;
        }
     
        return mask;
    }
     
    static const struct file_operations button_fops = {
        .owner = THIS_MODULE,
        .open = button_open,
        .release = button_close,
        .read = button_read,
        .poll = button_poll,
        //.write = button_write,
        //.ioctl = button_ioctl
    };
     
    static int button_setup_cdev(struct cdev *cdev, dev_t devno)
    {
        int ret = 0;
     
        cdev_init(cdev, &button_fops);
        cdev->owner = THIS_MODULE;
        ret = cdev_add(cdev, devno, 1);
     
        return ret;
    }
     
    static int __init button_init(void)
    {
        int ret;
        dev_t devno;
        
        printk("button driver init...\n");
     
        init_timer(&button_timer);
        button_timer.function = &button_timer_handle;
            
        if(button_major) {
            devno = MKDEV(button_major, button_minor);
            ret = register_chrdev_region(devno, 1, BUTTON_NAME);
        } else {
            ret = alloc_chrdev_region(&devno, button_minor, 1, BUTTON_NAME);
            button_major = MAJOR(devno);        
        }
        
        if(ret < 0) {
            printk("get button major failed\n");
            return ret;
        }
     
        ret = button_setup_cdev(&button_cdev, devno);
        if(ret) {
            printk("button setup cdev failed, ret = %d\n",ret);
            goto cdev_add_fail;
        }
     
        p_button_class = class_create(THIS_MODULE, BUTTON_NAME);
        ret = IS_ERR(p_button_class);
        if(ret) {
            printk(KERN_WARNING "button class create failed\n");
            goto class_create_fail;
        }
        p_button_device = device_create(p_button_class, NULL, devno, NULL, BUTTON_NAME);
        ret = IS_ERR(p_button_device);
        if (ret) {
            printk(KERN_WARNING "button device create failed, error code %ld", PTR_ERR(p_button_device));
            goto device_create_fail;
        }
     
        return 0;
        
    device_create_fail:
        class_destroy(p_button_class);
    class_create_fail:
        cdev_del(&button_cdev);
    cdev_add_fail:
        unregister_chrdev_region(devno, 1);
        return ret;
    }
     
    static void __exit button_exit(void)
    {
        dev_t devno;
     
        printk("button driver exit...\n");
        
        del_timer_sync(&button_timer);    
        devno = MKDEV(button_major, button_minor);    
        device_destroy(p_button_class, devno);
        class_destroy(p_button_class);
        cdev_del(&button_cdev);
        unregister_chrdev_region(devno, 1);
    }
     
    module_init(button_init);
    module_exit(button_exit);
     
    MODULE_AUTHOR("Jimmy");
    MODULE_DESCRIPTION("button Driver");
    MODULE_LICENSE("GPL");       

    驱动Makefile文件

    
    ifneq ($(KERNELRELEASE),)
    obj-m := button.o
    else
    KERNELDIR ?= /ljm/git_imx6/linux-fsl/src/linux-3-14-28-r0
    TARGET_CROSS = arm-none-linux-gnueabi-
    
    PWD := $(shell pwd)
    
    default:
        $(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules
    
    endif
    
    install:
        $(MAKE) ARCH=arm CROSS_COMPILE=$(TARGET_CROSS) -C $(KERNELDIR) M=$(PWD) modules_install
    
    clean:
        rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order

    应用程序main.c

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <linux/ioctl.h>
     
    #define DEV_BUTTON "/dev/poll_button"
     
    int main(void)
    {
        int dev_fd;
        int ret;
        char read_buf[20] = {-1};
        struct timeval rto;
        fd_set read_fds;
     
        rto.tv_sec = 10;
        rto.tv_usec = 0;
        
        dev_fd = open(DEV_BUTTON, O_RDWR /*| O_NONBLOCK*/);
        if ( dev_fd == -1 ) {
            printf("open %s failed, ret = %d\n", DEV_BUTTON, dev_fd);
            return -1;
        }
     
        while(1)
        {
            rto.tv_sec =10;
            rto.tv_usec = 0;
            
            FD_ZERO(&read_fds);
            FD_SET(dev_fd, &read_fds);
            
            ret = select(dev_fd+1, &read_fds, NULL, NULL, &rto);
            if(ret == -1) {
                printf("error\n");
                continue;
            } else if(ret == 0) {
                printf("timeout\n");
                continue;
            } else {
                if(FD_ISSET(dev_fd, &read_fds)) {
                    read(dev_fd, read_buf, 1);
                    printf("button pressed, val = %d\n", read_buf[0]);
                }
            }
        }
        
        printf("clsoe %s\n", DEV_BUTTON);
        close(dev_fd);
        
        return 0;
    }     

     

    应用程序Makefile

    
    WORKDIR  = 
    INCLUDES = -I.
    LIBS     = 
    LINKS    = -lpthread
    
    CC = arm-none-linux-gnueabi-gcc
    TARGET = main
    
    src=$(wildcard *.c ./callback/*.c)
    C_OBJS=$(patsubst %.c, %.o,$(src))
    #C_OBJS=$(dir:%.c=%.o)
    
    compile:$(TARGET)
        
    $(C_OBJS):%.o:%.c
        $(CC) $(CFLAGS) $(INCLUDES) -o $*.o -c $*.c
        
    $(TARGET):$(C_OBJS)
        $(CC) -o $(TARGET) $^ $(LIBS) $(LINKS) 
    
        @echo 
        @echo Project has been successfully compiled.
        @echo
        
    install: $(TARGET)
        cp $(TARGET) $(INSTALL_PATH)
    
    uninstall:
        rm -f $(INSTALL_PATH)/$(TARGET)
    
    rebuild: clean compile
    
    clean:
        rm -rf *.o  $(TARGET) *.log *~

    三、select的整体流程


        应用层的select函数会调用到内核函数do_select,do_select调用驱动的poll函数,若poll函数返回的掩码不可读写,那么do_select进入睡眠阻塞。要从睡眠中醒来并且跳出,有两种情况:a、超时跳出;b、驱动中唤醒等待队列,这时do_select再次调用poll函数,如果poll函数返回的掩码可读写,那么就跳出阻塞,否则继续睡眠。注意:上述是在select函数设成阻塞的情况,select函数可以设置成非阻塞的(将select函数的timeout参数设置成0)。


    四、select机制内核代码走读
        调用顺序如下select() -> core_sys_select() -> do_select() -> fop->poll()
        
        1、select函数解析

    SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
        fd_set __user *, exp, struct timeval __user *, tvp)
    {
     
           struct timespec end_time, *to = NULL;
           struct timeval tv;
           int ret;
     
           if (tvp) {// 如果超时值非NULL
     
                  if (copy_from_user(&tv, tvp, sizeof(tv)))   // 从用户空间取数据到内核空间
                         return -EFAULT;
     
                  to = &end_time;
     
                  // 得到timespec格式的未来超时时间
     
                  if (poll_select_set_timeout(to,
                                tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
                                (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
     
                         return -EINVAL;
     
           }
     
           ret = core_sys_select(n, inp, outp, exp, to);             // 关键函数
     
           ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
     
           /*如果有超时值, 并拷贝离超时时刻还剩的时间到用户空间的timeval中*/
     
           return ret;             // 返回就绪的文件描述符的个数
    }   
    
       2、core_sys_select函数解析
    int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
                            fd_set __user *exp, struct timespec *end_time)
     
    {
        fd_set_bits fds;
     
        /**
        typedef struct {
            unsigned long *in, *out, *ex;
            unsigned long *res_in, *res_out, *res_ex;
        } fd_set_bits;
        这个结构体中定义的全是指针,这些指针都是用来指向描述符集合的。
        **/
     
        void *bits;
        int ret, max_fds;
        unsigned int size;
        struct fdtable *fdt;
     
        /* Allocate small arguments on the stack to save memory and be faster */
     
        long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];
        // 256/32 = 8, stack中分配的空间
     
        /**
            @ include/linux/poll.h
            #define FRONTEND_STACK_ALLOC     256
            #define SELECT_STACK_ALLOC    FRONTEND_STACK_ALLOC
        **/
     
        ret = -EINVAL;
     
        if (n < 0)
            goto out_nofds;
     
        /* max_fds can increase, so grab it once to avoid race */
     
        rcu_read_lock();
        fdt = files_fdtable(current->files); // RCU ref, 获取当前进程的文件描述符表
        max_fds = fdt->max_fds;
        rcu_read_unlock();
     
        if (n > max_fds)                     // 如果传入的n大于当前进程最大的文件描述符,给予修正
            n = max_fds;
     
     
     
        /*
        * We need 6 bitmaps (in/out/ex for both incoming and outgoing),
        * since we used fdset we need to allocate memory in units of
        * long-words.
        */
     
        size = FDS_BYTES(n);
     
        // 以一个文件描述符占一bit来计算,传递进来的这些fd_set需要用掉多少个字
     
        bits = stack_fds;
     
        if (size > sizeof(stack_fds) / 6) {
            // 除6,为什么?因为每个文件描述符需要6个bitmaps
            /* Not enough space in on-stack array; must use kmalloc */
            ret = -ENOMEM;
            bits = kmalloc(6 * size, GFP_KERNEL); // stack中分配的太小,直接kmalloc
            if (!bits)
                goto out_nofds;
        }
     
        // 这里就可以明显看出struct fd_set_bits结构体的用处了。
        fds.in      = bits;
        fds.out     = bits +   size;
        fds.ex      = bits + 2*size;
        fds.res_in  = bits + 3*size;
        fds.res_out = bits + 4*size;
        fds.res_ex  = bits + 5*size;
     
        // get_fd_set仅仅调用copy_from_user从用户空间拷贝了fd_set
     
        if ((ret = get_fd_set(n, inp, fds.in)) ||
            (ret = get_fd_set(n, outp, fds.out)) ||
            (ret = get_fd_set(n, exp, fds.ex)))
            goto out;
     
        zero_fd_set(n, fds.res_in);  // 对这些存放返回状态的字段清0
        zero_fd_set(n, fds.res_out);
        zero_fd_set(n, fds.res_ex);
     
        ret = do_select(n, &fds, end_time);    // 关键函数,完成主要的工作
        if (ret < 0)                           // 有错误
            goto out;
     
        if (!ret) {                            // 超时返回,无设备就绪
            ret = -ERESTARTNOHAND;
            if (signal_pending(current))
                goto out;
            ret = 0;
        }
     
        // 把结果集,拷贝回用户空间
     
        if (set_fd_set(n, inp, fds.res_in) ||
            set_fd_set(n, outp, fds.res_out) ||
            set_fd_set(n, exp, fds.res_ex))
            ret = -EFAULT;
     
    out:
        if (bits != stack_fds)
            kfree(bits);               // 如果有申请空间,那么释放fds对应的空间
     
    out_nofds:
        return ret;                    // 返回就绪的文件描述符的个数
    }
    
    
        3、do_select函数解析
    int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
     
    {
     
           ktime_t expire, *to = NULL;
     
           struct poll_wqueues table;
     
           poll_table *wait;
     
           int retval, i, timed_out = 0;
     
           unsigned long slack = 0;
     
     
     
           rcu_read_lock();
     
           // 根据已经设置好的fd位图检查用户打开的fd, 要求对应fd必须打开, 并且返回
           // 最大的fd。
     
           retval = max_select_fd(n, fds);
     
           rcu_read_unlock();
     
     
     
           if (retval < 0)
     
                  return retval;
     
           n = retval;
     
     
     
           // 一些重要的初始化:
     
           // poll_wqueues.poll_table.qproc函数指针初始化,该函数是驱动程序中poll函数实
     
           // 现中必须要调用的poll_wait()中使用的函数。
     
           poll_initwait(&table);
     
           wait = &table.pt;
     
           if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
     
                  wait = NULL;
     
                  timed_out = 1;     // 如果系统调用带进来的超时时间为0,那么设置
     
                                              // timed_out = 1,表示不阻塞,直接返回。
     
           }
     
     
     
           if (end_time && !timed_out)
                  slack = estimate_accuracy(end_time); // 超时时间转换
     
     
     
           retval = 0;
     
           for (;;) {
     
                  unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
     
                  inp = fds->in; outp = fds->out; exp = fds->ex;
     
                  rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
     
                  // 所有n个fd的循环
     
                  for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
     
                         unsigned long in, out, ex, all_bits, bit = 1, mask, j;
     
                         unsigned long res_in = 0, res_out = 0, res_ex = 0;
     
                         const struct file_operations *f_op = NULL;
     
                         struct file *file = NULL;
     
     
     
                         // 先取出当前循环周期中的32个文件描述符对应的bitmaps
     
                         in = *inp++; out = *outp++; ex = *exp++;
     
                         all_bits = in | out | ex;  // 组合一下,有的fd可能只监测读,或者写,或者e rr,或者同时都监测
     
                         if (all_bits == 0) {  // 这32个描述符没有任何状态被监测,就跳入下一个32个fd的循环中
     
                                i += __NFDBITS; //每32个文件描述符一个循环,正好一个long型数
     
                                continue;
                         }
     
     
     
                         // 本次32个fd的循环中有需要监测的状态存在
                         for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {// 初始bit = 1
     
                                int fput_needed;
     
                                if (i >= n)      // i用来检测是否超出了最大待监测的fd
     
                                       break;
     
                                if (!(bit & all_bits))
     
                                       continue; // bit每次循环后左移一位的作用在这里,用来跳过没有状态监测的fd
     
                                file = fget_light(i, &fput_needed); // 得到file结构指针,并增加引用计数字段f_count
     
                                if (file) {        // 如果file存在
     
                                       f_op = file->f_op;
     
                                       mask = DEFAULT_POLLMASK;
     
                                       if (f_op && f_op->poll) {
     
                                              wait_key_set(wait, in, out, bit);// 设置当前fd待监测的事件掩码
     
                                              mask = (*f_op->poll)(file, wait);
     
                                              /*
                                              调用驱动程序中的poll函数,以evdev驱动中的
                                              evdev_poll()为例该函数会调用函数poll_wait(file, &evdev->wait, wait),
                                              继续调用__pollwait()回调来分配一个poll_table_entry结构体,该结构体有一个内嵌的等待队列项,
                                              设置好wake时调用的回调函数后将其添加到驱动程序中的等待队列头中。
                                              */
     
                                       }
     
                                       fput_light(file, fput_needed);
     
                                       // 释放file结构指针,实际就是减小他的一个引用计数字段f_count。
                                       // mask是每一个fop->poll()程序返回的设备状态掩码。
     
                                       if ((mask & POLLIN_SET) && (in & bit)) {
     
                                              res_in |= bit;         // fd对应的设备可读
     
                                              retval++;
     
                                              wait = NULL;       // 后续有用,避免重复执行__pollwait()
     
                                       }
     
                                       if ((mask & POLLOUT_SET) && (out & bit)) {
     
                                              res_out |= bit;              // fd对应的设备可写
     
                                              retval++;
     
                                              wait = NULL;
     
                                       }
     
                                       if ((mask & POLLEX_SET) && (ex & bit)) {
     
                                              res_ex |= bit;
     
                                              retval++;
     
                                              wait = NULL;
     
                                       }
     
                                }
     
                         }
     
                         // 根据poll的结果写回到输出位图里,返回给上级函数
     
                         if (res_in)
     
                                *rinp = res_in;
     
                         if (res_out)
     
                                *routp = res_out;
     
                         if (res_ex)
     
                                *rexp = res_ex;
     
                         /*
                                这里的目的纯粹是为了增加一个抢占点。
                                在支持抢占式调度的内核中(定义了CONFIG_PREEMPT),
                                cond_resched是空操作。
                         */
     
                         cond_resched();
     
                  }
     
                  wait = NULL;  // 后续有用,避免重复执行__pollwait()
     
                  if (retval || timed_out || signal_pending(current))
     
                         break;
     
                  if (table.error) {
     
                         retval = table.error;
     
                         break;
     
                  }
     
                  /*跳出这个大循环的条件有: 有设备就绪或有异常(retval!=0), 超时(timed_out
                  = 1), 或者有中止信号出现*/
     
     
     
                  /*
                   * If this is the first loop and we have a timeout
                   * given, then we convert to ktime_t and set the to
                   * pointer to the expiry value.
                   */
     
                  if (end_time && !to) {
     
                         expire = timespec_to_ktime(*end_time);
     
                         to = &expire;
     
                  }
     
     
     
                  // 第一次循环中,当前用户进程从这里进入休眠,
     
                  // 上面传下来的超时时间只是为了用在睡眠超时这里而已
     
                  // 超时,poll_schedule_timeout()返回0;被唤醒时返回-EINTR
     
                  if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
     
                                          to, slack))
     
                         timed_out = 1; /* 超时后,将其设置成1,方便后面退出循环返回到上层 */
     
           }
     
           // 清理各个驱动程序的等待队列头,同时释放掉所有空出来的page页(poll_table_entry)
     
           poll_freewait(&table);
     
           return retval; // 返回就绪的文件描述符的个数
    }    
    

     

    展开全文
  • Looper中的睡眠等待唤醒机制

    千次阅读 2013-01-29 13:13:13
    Looper中的睡眠等待唤醒机制 C++类Looper中的睡眠和唤醒机制是通过pollOnce和wake函数提供的,它们又是利用操作系统(Linux内核)的epoll机制来完成的。当被监控的文件(通过epoll_ctl的EPOLL_CTL_ADD添加进去)...

    Looper中的睡眠等待与唤醒机制

    C++类Looper中的睡眠和唤醒机制是通过pollOnce和wake函数提供的,它们又是利用操作系统(Linux内核)的epoll机制来完成的。当被监控的文件(通过epoll_ctl的EPOLL_CTL_ADD添加进去)可I/O时,epoll_wait调用会从睡眠中醒来,这时,可以检查是哪个(或哪些)文件描述符对应的文件可以进行I/O读写了,从而做出进一步处理。使用者利用它们就可以拥有睡眠等待和唤醒机制。下面详述。

    在Looper的构造函数中,会创建一个管道(下面的行73),然后调用epoll_create获取一个epoll的实例的描述符(行88),最后将管道读端描述符作为一个事件报告项添加给epoll(行95)。这样,当管道读端有数据可读时,将会得到报告。Looper的构造函数如下(见文件Looper.cpp):


    Looper的pollOnce函数将最终调用到其pollInner函数。在后者里面,将调用epoll_wait睡眠等待其监控的文件描述符是否有可I/O事件的到来,若有(哪怕只有一个),epoll_wait将会醒来,然后可检查是哪个文件描述符上的可I/O事件。pollInner函数中的相关代码如下(见文件Looper.cpp):



    可见,在线程循环中调用了Looper的pollOnce函数,将导致睡眠等待在上面的行218处的epoll_wait上。当向消息队列发送消息并进行唤醒时,行218将被唤醒,因此从pollOnce函数中返回,可以从消息队列中取出消息进行处理。

    Looper的wake函数用于向管道中写入字符(下面的行367),以唤醒pollOnce:


    下面来看一下Java层的MessageQueue如何利用这种机制。

    前面提到在android.os.MessageQueue的next函数中取出下一个消息时,会调用到native层实现的函数nativePollOnce时,实际调用到了如下native实现(见文件android_os_MessageQueue.cpp):


    上面行157的pollOnce函数代码是(见文件android_os_MessageQueue.cpp):


    这样,它们就通过Looper的pollOnce实现了在Looper中的管道上的读端上的睡眠等待。

    当android.os.MessageQueue的enqueueMessage函数往队列上添加了一个新消息或removeSyncBarrier移除了同步屏障后,可能需要调用nativeWake唤醒,其native实现为:(见文件android_os_MessageQueue.cpp):


    上面的行162调用的又是下面的函数,代码如下(见文件android_os_MessageQueue.cpp):


    这样,Looper将向管道写端写入字符,唤醒其在管道读端上的睡眠等待。

    因此,通过借助于Looper的wake和pollOnce函数,可以让别的消息队列(如Java层的消息队列)拥有睡眠唤醒机制:没有消息时pollOnce调用者将睡眠等待,有消息时让wake函数去唤醒睡眠等待。


    本文节选自《深入剖析Android系统》一书

    杨长刚著

    电子工业出版社出版

    展开全文
  • linux内核等待队列机制: 案例:分析应用程序串口工具操作串口硬件设备的过程。 1.外设的处理速度要远远慢于CPU! 2.应用程序在用户空间没有权利访问硬件设备,只有通过系统调用跑到内核空间才有权限访问硬件设备! ...
  • Linux中提供了等待队列的机制,该机制在内核中应用很广泛。在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。可以使用等待队列在实现阻塞进程的唤醒。它以队列为基础数据结构,与进程...
  • 当进程尝试访问一个边界数据时,有可能由于资源已经被占用而进入睡眠状态。当资源被释放后,就需要把睡眠的进程唤醒
  • 内核对这种阻塞提供等待队列机制来实现,这样可以改善实时性问题。 等待队列头数据结构 内核使用这个结构来给进程一个休眠的地方。 定义如下:Wait.h (include\linux) struct __wait_queue_head {  ...
  • linux 等待队列 PK linux 等待队列头

    千次阅读 2012-02-20 16:06:38
    Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。 在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)...
  • Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在中,等待队列在源代码树中,这是一个通过连接的典型双循环链表,如下图所示。  在这个...
  • 所以,共享内存区天然就是一种进程间通信机制。但是这又是很原始的手段,因为这里有个读出方如何知道共享区的内容已经被写入方改变的问题。轮询,或者定期轮询,当然也是个办法,但是一般而言效率毕竟
  • Linux内核锁机制

    千次阅读 2013-12-04 09:00:48
    linux内核中,有很多同步机制。比较经典的有原子操作、spin_lock(忙等待的锁)、mutex(互斥锁)、semaphore(信号量)等。并且它们几乎都有对应的rw_XXX(读写锁),以便在能够区分读与写的情况下,让读操作相互...
  • linux等待队列

    千次阅读 2014-03-07 13:54:59
    Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型...
  • linux的netlink机制

    千次阅读 2010-02-09 21:16:00
    其实netlink定义了一个框架,人们可以基于这个框架用它来做可以做的任何事情,linux中不乏这些类似的好的框架。它们的共同点就是内核并不管它们能做什么,然而它们真的很强大,往往可以做到的事情很多,这就是内核...
  • 浅析Linux内核同步机制

    千次阅读 2014-07-13 19:43:41
    很早之前就接触过同步这个概念了,但是一直都很模糊,没有深入地学习了解过,近期有时间了,就花时间研习了一下《linux内核标准教程》和《深入linux设备驱动程序内核机制》这两本书的相关章节。趁刚看完,就把相关的...
  • linux 等待队列

    千次阅读 2012-03-22 10:02:27
    Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。 在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)...
  • 文档介绍了前言,一、等待队列定义,二、等待队列作用,三、字段详解,三、操作,1、定义并初始化等待队列头,2、定义等待队列项,3、(从等待队列头中)添加/移出等待队列项,4、等待事件,5、唤醒队列,6、在等待...
  • Linux的信号机制

    千次阅读 2018-08-23 13:53:42
    信号机制 信号机制是一种使用信号来进行进程之间传递消息的方法,信号的全称为软中断信号,简称软中断。信号的本质是软件层次上对中断的一种模拟(软中断)。它是一种异步通信的处理机制,事实上,进程并不知道信号...
  • LINUX 等待队列

    2011-08-30 22:39:51
    (转载) bojan 收录于2010-10-09 阅读数: 公众公开  ...在Linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue很早就作为一种基本的功能单位出现在Linux内核里了,它以队列位基础数据
  • linux 2.6同步机制

    2010-04-16 17:44:00
    操作系统的同步机制是老话题了,不过技术的东西放久不用了就容易忘记,在这里...直到进程B将持有的信号量释放,处于等待队列中的进程A才会被唤醒,并获得信号量。 从信号量的特点所得出的一些结论,如下: 由于等待信号
  • linux任务调度机制

    千次阅读 2016-08-28 17:24:49
    linux调度算法中,将进程分为两种类型,即:I/O消耗型和CPU消耗型。例如文本处理程序与正在执行的Make的程序。文本处理程序大部份时间都在等待I/O设备的输入,而make程序大部份时间都在CPU的处理上。因此为了提高...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 38,294
精华内容 15,317
关键字:

linux等待唤醒机制

linux 订阅