屏幕切换页 操作系统 - CSDN
  • Android之屏幕切换使用技巧文章链接:知识点: android:screenOrientation及属性介绍; 屏幕切换时加载不同布局; setRequestedOrientation手动设置布局方向; 设置屏幕布局注意的点; 切换屏幕布局保存页面状态的...

    Android之屏幕切换使用技巧(setRequestedOrientation)

    文章链接:

    知识点:

    1. android:screenOrientation及属性介绍;
    2. 屏幕切换时加载不同布局;
    3. setRequestedOrientation手动设置布局方向;
    4. 设置屏幕布局注意的点;
    5. 切换屏幕布局保存页面状态的方法onRetainCustomNonConfigurationInstance;
    6. 新名词记录{onRetainCustomNonConfigurationInstance():屏幕切换时保存页面状态的新方法;onRetainNonConfigurationInstance():旧的保存页面状态方法}

    activity横竖屏

    android:screenOrientation

    属性有下述可选值:

    • unspecified:默认值 由系统来判断显示方向.判定的策略是和设备相关的,所以不同的设备会有不同的显示方向.
    • landscape:横屏显示
    • portrait:竖屏显示
    • user:用户当前首选的方向
    • behind:和该Activity下面的那个Activity的方向一致
    • sensor:由物理的感应器来决定,如果用户旋转设备这屏幕会横竖屏切换
    • nosensor:忽略物理感应器,这样就不会随着用户旋转设备而更改了(“unspecified”设置除外)

    横竖屏切换时,加载不同的布局方法
    - 准备两套不同的布局,系统会根据横竖屏加载不同布局:
    创建两个布局文件夹:layout-land横屏,layout-port竖屏,比如文件名为main.xml,分别放到两个文件夹中去,当执行oncreate()方法的时候,系统就会自行判断应该要加载哪一个文件夹下面的main.xml布局。

    • 在代码中进行判断,一般是在onCreate()方法中加载布局文件的时候,对横竖屏的状态做下判断,代码如下所示:

          if (Configuration.ORIENTATION_LANDSCAPE == this.getResources().getConfiguration().orientation) {
              setContentView(R.layout.layout_land);//设置横屏
          } else if (Configuration.ORIENTATION_PORTRAIT == this.getResources().getConfiguration().orientation) {
              setContentView(R.layout.layout_port); //设置竖屏
          }
    • 手动设置横竖屏

      出了随系统或者是传感器或者是用户而进行横竖屏切换,我们也可以自己调用系统提供的方法进行横竖屏的切换,比如我们在uc里面进行看视频的时候,UC的视频播放器会自动的进行横屏。手动调用的时候,在Androidmanifest文件对应activity文件下声明的screenOrientation就会失效了。调用的就是下面的方法。

      //定义为竖屏
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

      需要的参数是在ActivityInfo类里面的定义好的参数。

      • SCREEN_ORIENTATION_UNSPECIFIED
      • SCREEN_ORIENTATION_LANDSCAPE
      • SCREEN_ORIENTATION_PORTRAIT
      • SCREEN_ORIENTATION_USER
      • SCREEN_ORIENTATION_BEHIND
      • SCREEN_ORIENTATION_SENSOR
      • SCREEN_ORIENTATION_NOSENSOR
      • ···

      如果进行了横竖屏的切换,当前的activity会被销毁,并且重新创建一个,因为此时页面的宽高都已经改变了,所以需要重新测量和布局,就需要销毁重建。此时,activity调用的周期方法是:onPause()-> onStop()-> onDestory()-> onCreate()->onStart()->onResume()。结合页面保存的对象,在onCreate()/onResume()方法里面获取到保存的对象,然后重写将值设置到切换了屏幕方向的新布局去。

      但是有时候,横屏模式并不是说屏幕的宽度一定比高度要长,竖屏也不一定高度比宽度要长。所以,为了更加保险,我们可以不需要根据屏幕的方向来显示,而是根据屏幕的实际宽高来进行。代码如下所示:

            Display display = this.getWindowManager().getDefaultDisplay();
            int width = display.getWidth();
            int height = display.getHeight();
            if (width > height){
                //横屏,加载横屏布局
            }else {
                //竖屏,加载竖屏布局
            }
    • 注意的问题

      1、在Androidmanifest文件的< activity >节点下面,设置了android:screenOrientation=”portrait/landscape/sensor”等属性,那么屏幕就确定不会再改变了。除非手动调用setRequestedOrientation()方法,同理,调用此方法之后设置的屏幕方向的属性也是确定的了。

      2、在横竖屏切换的时候,销毁重建activity会消耗资源,如果没有保存屏幕的数据,重建之后屏幕的数据又得重新设置,用户体验就不好了。在activity的节点下面还有这个属性:android:configChanges=”orientation|keyboardHidden”。如果我们设置了以上两个属性,那么在切换屏幕方向的时候,不再是销毁重建activity,而是会调用onConfigurationChanged(Configuration newConfig)方法。
      我们就可以在这个方法里面,获取到新的屏幕方向,然后加载对应的布局。具体的代码如下所示:

      @Override
      public void onConfigurationChanged(Configuration newConfig) {
          super.onConfigurationChanged(newConfig);
          switch (newConfig.orientation) {
              case Configuration.ORIENTATION_PORTRAIT:
                  break;
              case Configuration.ORIENTATION_LANDSCAPE:
                  break;
              default:
                  break;
          }
      }

    补充一点

    关于在横竖屏切换,activity被销毁和重新创建的情况下,我们需要保存页面上的一些数据,待到切换完成之后,再将数据重新放到页面上去,给用户一个无缝的体验。

    有的人说,可以使用onRetainNonConfigurationInstance()方法进行数据的保存,但是系统是推荐不重写这个方法的,推荐使用的是一个新的方法:onRetainCustomNonConfigurationInstance(),对应的获取保存的对象的方法是:getLastCustomNonConfigurationInstance()。

    在跟踪源码之后发现,信息都是用NonConfigurationInstances类进行保存的。这个位于FragmentActivity里的内部静态类里面有3个参数,Object:我们保存的对象,FragmentManagerNonConfig:在activity重建事件中,保存fragment的实例,SimpleArrayMap:一个实现ArrayMap接口类的用于保存键值对的类,此内部静态类里面放入的之时LoaderManager类对象。

    具体的使用示例如下:

    //保存
    @Override
    public Object onRetainCustomNonConfigurationInstance() {
       //保存一个bean
        return new UserBean("yaojt", "1123456", 23);
    }
    
    //获取
    //在oncreate方法中,获取保存的bean对象
    //这里只是获取到NonConfigurationInstances的custom对象,即我们在onRetainCustomNonConfigurationInstance()
    //保存的对象
    UserBean userBean = (UserBean) getLastCustomNonConfigurationInstance();

    注意:
    如果在没有重写onRetainCustomNonConfigurationInstance()方法保存对象,那么利用getLastCustomNonConfigurationInstance()方法获取的是一个null值。

    总结

    对于横竖屏,还是比较简单的,但是也是比较难把握的一个知识点,这就要求我们对于activity的生命周期要比较熟悉,才能够比较熟练的在横竖屏切换的时候,做到更好的用户体验。

    以上就是所有内容,如有任何问题,请及时与我联系,谢谢!

    展开全文
  • 文章目录为什么要说线程的切换线程...操作系统是多进程的,我们关注的应该是进程之间的切换,那为什么关注线程的切换呢?因为理解了线程的切换之后可以更好的理解进程的切换,换句话说线程的切换是进程切换的基础。...

    为什么要说线程的切换

    操作系统是多进程的,我们关注的应该是进程之间的切换,那为什么关注线程的切换呢?因为理解了线程的切换之后可以更好的理解进程的切换,换句话说线程的切换是进程切换的基础。

    每一个进程都包含一个映射表,如果进程切换了,那么程序选择的映射表肯定也不一样;进程的切换其实是包含两个部分的,第一个指令的切换,第二个映射表的切换。指令的切换就是从这段程序跳到另外一段程序执行,映射表切换就是执行不同的进程,所选择的映射表不一样。线程的切换只有指令的切换,同处于一个进程里面,不存在映射表的切换。进程的切换就是在线程切换的基础上加上映射表的切换。

    线程的引入

    多个进程可以“同时”执行,其实也就是换着执行,那么在同一个进程里面,不同的代码段能不能换着执行呢?比如在进程A里面有代码段1、代码段2、代码段3;能不能先执行一下代码段1,然后执行一下代码段3,再执行一下代码段2呢?答案是可以的。进程的切换包括指令的切换和映射表的切换,那么同一个进程里面就没必要进行映射表的切换了,即只需要切换指令就可以了。上面所说的代码段其实就称为“线程”。

    前面说了多线程只需要进行指令的切换就可以了;这样相对于进程来说,多线程保留了多进程的优点:并发。避免了进程切换的代价(切换映射表需要耗费比较多的时间)。如果能够将多线程的切换弄明白,那么多进程的切换其实也就直剩下了映射表的切换,这是典型的“分而治之”。

    用户级线程

    用户级线程作用举例

    以前网速比较慢的时候,打开浏览器访问一个网页,首先弹出来的是网页的文字部分,然后是一些图片,最后才是一些小视频之类的。为什么呢?浏览器向服务器发起访问的程序是一个进程,它包含若干线程,比如:一个线程用来从服务器接收数据,一个线程用来显示文本,一个线程用来显示文本,一个线程用来显示图片等等。在网速比较慢的时候用来从服务器接收数据的线程要执行的时间比较长,因为一些图片和视频都比较大。如果要等这个线程运行完了之后再显示,那么电脑屏幕就会有一段时间什么东西都没有,这样用户体验就会比较差;一个比较合理的办法是:接受数据的线程接受完文本东西之后,就调用显示文本的线程将数据显示出来,然后再接受图片再显示,再接受视频再显示;这样至少可以保证电脑屏幕上始终有东西;相比前面的方法好很多,当然最根本的办法还是提高网速。

    还有一个问题,为什么浏览器
    向服务器请求数据的程序是一个进程,而不是多个?浏览器接受服务器的数据肯定都是存储在一个缓冲区里面的,并且这个缓冲区是共享的,如果是多个进程,那么肯定有多个映射表,也就是说如果程序里面存储数据的地址是连续的,经过不同的映射表之后,就会分布在内存的不同区域,这样肯定没有在一块地方好处理呀。

    上面这个例子就牵涉到线程(用户级线程)的切换,也可以看出线程并不是一个无意义的概念,而是有实际作用的。
    下面说一下线程之间到底是如何切换的,其实主要是切过去之后还要能够切回来。

    两个线程与一个栈。。。

    线程一

    100:A()
    {
    	B();
    	104:
    }
    200: B()
    {
    	Yield1();	// 切换线程
    	204:
    }
    

    线程二

    300:C()
    {
    	D();
    	304:
    }
    400: D()
    {
    	Yield2();
    	404:
    }
    
    

    按照这个执行一下,首先从线程一的A函数开始,调用B函数,将B函数的返回地址:104压栈,然后进入B函数;在B函数内部使用Yield1切换到线程二的C()函数里面去,同时将Yield1的返回地址压栈,此时栈中的数据如下:

    104 204
    

    Yield的伪代码应该是:

    void Yield1()
    {
    	find 300;
    	jmp 300;
    }
    

    现在执行到了线程二,计划是在D函数里面通过Yield2跳到线程一的204这个地址,完成线程的切换。调用c函数,同时将304这个地址压栈,跳到D函数里面执行,在D函数里面调用Yield2,同时将404压栈。Yield2的伪代码应该是:

    void Yield2()
    {
    	find 204;
    	jmp 204;
    }
    

    目前栈里面的数据应该是:

    104 204 304 404
    

    跳到204之后,接着执行B函数剩下的内容,执行完内容之后,执行函数B的"}"相当于ret,弹栈,此时栈顶的地址是404,B函数应该是返回到104处,而不是404处;这里就出现了问题。怎么处理?

    从一个栈到两个栈。。。

    处理方法是使用两个栈,在不同的线程里面使用不同的栈。在线程一中使用栈一,线程二中使用栈二。这是一个伟大的发明。

    重新执行一下上面那个程序,从A函数开始执行,在B函数里面调用Yield1进入线程二的C函数之后,线程一对应的栈一中的内容应该是:

    104  204
    

    执行到D函数的Yield2之后,线程二对应的栈二的内容应该是:

    304 404
    

    在Yield2里面做的第一件事就应该是切换栈,如何切换?肯定需要一个数据结构将原来栈一的地址保存起来,这个数据结构就是TCB(Thread control block ) ;当前栈的栈顶地址是存放在cpu里面的esp寄存器里面的, 因此只需要改变esp的值就可以切换栈了。

    void Yield2()
    {
    	TCB2.esp = esp;			// 保存当前栈顶地址
    	esp = TCB1.esp;			// 切换栈
    	jmp 204;				
    }
    

    jmp到204之后,执行完B函数剩下的代码之后执行B函数的"}",即弹栈,这时栈顶是204,也就是又跳到204去了,显然有问题,但是比前面已经好很多了,因为不会跳到另外一个线程里去。那现在为什么会这样呢?原因是Yield2()直接跳到204之后,而没有将栈中的204弹出去,如果Yield2跳到204这个位置,同时将栈中的204弹出去就好了。其实这个可以实现,修改
    Yield2如下:

    void Yield2()
    {
    	TCB2.esp = esp;			// 保存当前栈顶地址
    	esp = TCB1.esp;			// 切换栈
    }
    

    没错,就是将jmp 204去掉就可以了,利用Yield2的"}“弹栈同时跳到204地址处, 执行完B函数之后, 通过B函数的”}"再次弹栈到104处,完美。

    核心级线程

    首先来看第一个问题:

    多处理器和多核的区别:

    在这里插入图片描述
    多处理器每一个cpu都有一套自己的MMU。多核是所有的CPU共用一套MMU,也就是多个CPU的内存映射关系是一致的。

    多核就有种单个进程的概念,在这个进程内部所有的线程都是共用一套MMU的。多处理器就有种多进程的概念,每个CPU的MMU都不一样。因此对于同一个进程来说,多核可以同时执行这个进程里面的线程,但是多处理器不行,只有多线程才能将多核利用起来,因为现在电脑都是多核的,所以这是多线程的一大用处。这里的线程指的是核心级线程。核心级线程可以将每一个线程对应到具体的cpu上。

    核心级线程与用户级线程有什么区别呢?

    核心级线程需要在用户态和核心态里面跑,在用户态里跑需要一个用户栈,在核心态里面跑需要一个核心栈。用户栈和核心栈合起来称为一套栈,这就是核心级线程与用户级线程一个很重要的区别,从一个栈变成了一套栈。用户级线程用TCB切换栈的时候是在一个栈与另外一个栈之间切换,核心级线程就是在一套栈与另外一套栈之间的切换(核心级线程切换),核心级线程的TCB应该是在内核态里面。

    用户栈与内核栈之间的关联:

    内核栈什么时候出现?当线程进入内核的时候就应该建立一个属于这个线程的内核栈,那么线程是如何进入系统内核的?通过INT中断。当线程下一次进入内核的时候,操作系统可以根据一些硬件寄存器来知道这个哪个线程,它对应的内核栈在哪里。同时会将用户态的栈的位置(SS、SP)和程序执行到哪个地方了(CS、IP)都压入内核栈。等线程在内核里面执行完(也就是IRET指令)之后就根据进入时存入的SS、SP的值找到用户态中对应栈的位置,根据存入的CS、IP的值找到程序执行到哪个地方。
    在这里插入图片描述
    看一个例子:

    100:A()
    {
    	B();
    	104:
    }
    
    200:B()
    {
    	read();
    	204:
    }
    
    300:read()
    {
    	int 0x80;
    	304:
    }
    
    ---------------------------------
    
    system_call:
    	call sys_read;
    1000:
    2000:sys_read(){}
    

    上面的“-----”表示用户态和核心态的分界;首先该线程调用B函数,将104压栈(用户栈),进入B函数之后调用read()这个系统调用,同时将204压栈(用户栈),进入read()系统调用通过int0x80这个中断号进入内核态,执行到

    sys_read()
    {	
    	读磁盘;
    	将自己变成阻塞状态;
    	找到next(下一个执行的线程);
    	调用switch_to(cur,next);
    }
    

    switch_to()方法就是切换线程,形参cur表示当前线程的TCB,next表示下一个执行线程的TCB。这个函数首先将目前esp寄存器的值存入cur.TCB.esp,将next.TCB.esp放入esp寄存器里面;其实就是从当前线程的内核栈切换到next线程的内核栈。这里要明白一件事,内核级线程自己的代码还是在用户态的,只是进入内核态完成系统调用,也就是逛一圈之后还是要回去执行的。因此切换到next线程就是要根据next线程的内核栈找到这个线程阻塞前执行到的位置,并接着执行。所以切换到next线程的内核栈之后应该通过一条包含IRET指令的语句进入到用户态执行这个线程的代码。这样就从cur线程切换到next线程。
    在这里插入图片描述

    核心级线程的实现实例

    核心级线程的切换就是切换两套表,核心是内核栈的切换。

    main()
    {
    	A();
    	B();
    }
    A()
    {
    	fork();		// 系统调用
    }
    

    执行到A()调用的时候会将A函数的返回地址也就是B()函数的起始地址压入当前线程的用户栈中,然后转入fork()这个系统调用,在fork()中肯定会通过INT0x80这个中断号进入操作系统内核;进入内核的时候会将用户栈的位置以及当前程序的执行地址都压入到内核栈中,然后开始执行fork()。根据前面讲的系统调用知识可以知道,INT0x80的中断服务程序是_system_call这个宏定义,对于不同的系统调用_system_call执行的语句不同,对于fork()来说,如下:

    _system_call:
    	push %ds..%fs
    	push1 %edx...
    	call sys_fork
    	push1 %eax
    
    

    首先将一堆寄存器的值push到栈中,因为刚刚进入内核态的时候,这些寄存器的值都是用户态的内容,所以需要将这些值push到栈中为下一次这个线程的执行做准备。接着执行sys_fork,这个通过sys_call_table这张表找到的,这部分知识可以参考系统调用部分的东西。在sys_fork执行过程中可能需要切换到另外一个线程,它是如何切换的?其实也就是通过判断

    movl _current;%eax
    cmpl $0,state(%eax)
    jne reschedule
    cmp 1$0,counter(%eax)
    je  reschedule
    ret_from sys_call:
    

    _current 指的是当前线程的TCB。

    cmpl $0,state(%eax)
    jne reschedule
    

    判断当前线程的状态是不是0,如果不是就调度,即执行jne reschedule。

    cmp1 $0,counter(%eax)
    je  reschedule
    

    判断当前线程的时间片是不是用完了,如果用完了也需要调度。

    reschedule:
      pushl $ret_from_sys_call
      jmp schedule
    

    调度程序首先将返回地址 ret_from_sys_call 压栈,接着执行调度函数 _schedule。具体的调度算法忽略,主要看
    线程切换的过程。调度函数应该是下面这个样子,首先找到需要执行的下一个线程,然后转到该线程执行。

    void schedule(void)
    {
    	next=i;				// 找到需要执行的下一个线程
    	switch_ to(next);	// 转到该线程执行
    }
    

    执行完这个线程之后就会转到ret_from_sys_call执行

    ret_from_sys_call:
    	popl %eax...
    	pop %fs
    	iret
    

    首先将进入内核时存储的寄存器的值弹栈,也就是将当前状态的寄存器值恢复到执行本线程的状态。接着利用iret转到用户态,接着执行。

    到目前为止,线程的切换已经说完了,但是从线程一切换到线程二执行这部分没讲,也就是switch_to这个宏定义

    #define  switch_ to(n)
      {struct{long a,b;}
      __asm__(
      movW %%dx,%1\n\t”
      ljmp %0\n\t"
      ::"m"(*&_tmp.a),
      "m(*&_tmp.b),
      "d"(_TSS(n))}
    

    这是段内嵌汇编,是利用tss进行切换的,这种方式代码简单但是效率不高,因此window和稍高版本的linux都不是用的这种方法。这种方法主要就是通过

     ljmp %o\n\t
    

    这条语句来实现的,这里牵涉到一个寄存器TR,保存的是gdt的选择子,也就是根据TR的值可以在gdt表中找到对应的tss描述符,tss描述符会指向一个段,这个段足够存储当前CPU的所有寄存器的值,当线程一执行到这里,会将cpu当前寄存器所有的值都存放在这个tss表中,然后

    "d"(_TSS(n))
    

    这条语句就是将线程二的tss段中的值赋给cpu的寄存器。其实就和看电视一样,如果从节目一切换到节目二,就要先将节目一切换前的一幕存在脑海里面,以便于下次再看这个节目的时候能“不间断”的看。

    参考资料

    哈工大李志军操作系统

    展开全文
  • 原创作品转载请注明出处:《Linux操作系统分析》MOOC课程 http://www.xuetangx.com/courses/course-v1:ustcX+USTC001+_/about ##################################### 1. 前言 上一篇我们详细地分析一
    #####################################
    作者
    :张卓
    原创作品转载请注明出处:《Linux操作系统分析》MOOC课程 http://www.xuetangx.com/courses/course-v1:ustcX+USTC001+_/about
    #####################################

    1. 前言
    上一篇我们详细地分析一段x86汇编程序的执行过程,知道了计算机是如何工作的,总结来说就是三个关键点:存储程序计算机、函数调用堆栈、和中断机制。
    现在我们要简单分析一下操作系统是如何工作的,这里我将重点分析中断机制和进程上下文切换。
    2. C代码中嵌入汇编代码
    在分析操作系统中断机制和进程上下文之前,我们先了解一下C内嵌汇编,因为在操作系统进行进程上下文切换的时候,就是用内嵌汇编代码实现的。了解它,有助于我们深入理解代码执行流程。
    2.1 内嵌汇编语法
    __asm__(
        汇编语句模块:
        输出部分:
        输入部分:
        破坏描述部分);
        即格式为asm("statements": output_regs: input_regs: clobbered_regs)
    同时 “asm” 也可以由 “__asm__” 来代替,“asm” 是 “__asm__” 的别名。在 “asm” 后面有时也会加上 “__volatile__” 表示编译器不要优化代码,后面的指令保留原样,“volatile” 是它的别名,在这里值得注意的是无论 “__asm__” 还是 “__volatile__” 中的每个下划线都不是一个单独的下划线,而是两个短的下划线拼成的。在后面括号里面的便是汇编指令。
    2.2 C中内嵌汇编代码示例

    #include<stdio.h>
    int main()
    {
    	/* val1+val2=val3 */
    	unsigned int val1 = 1;
    	unsigned int val2 = 2;
    	unsigned int val3 = 0;
    	printf("val1:%d,val2:%d,val3:%d\n",val1,val2,val3);
    	asm volatile(
    	"movl $0, %%eax\n\t"  /* clear %eax to 0 */
    	"addl %1,%%eax\n\t"   /* %eax += val1 */
    	"addl %2 %%eax\n\t"   /* %eax += val2 */
    	"movl %%eax,%0\n\t"   /* val2 =%eax*/
    	:"=m"(val3)           /* output=m mean only write output memory variable*/
    	:"c"(val1),"d"(val2)  /* input c or d mean %ecx/%edx */
    	);
    	printf("val1:%d+val2:%d=val3:%d\n",val1,val2,val3);
    	return 0;
    }
    解释:
    1). 内嵌汇编指令中寄存器前面有两个%,是因为%在C语言中是特殊字符,需要转义才能输出%;C语言规定要输出%,需要两个%连用。
    2). 内嵌汇编指令中%0,%1,%2...是代表输出输入部所用寄存器的编号。
    3). 输出输入部,变量前面引号中的内容为限定符。
    3. 中断机制
    3.1 由CPU和内核代码共同实现了保存现场和恢复现场

    当中断发生时,CPU将当前的eip,ebp,esp等压人内核堆栈空间,再将eip指向中断处理程序入口,执行中断服务程序。执行完毕后,恢复现场,继续刚才的程序。
    3.2 借助实验楼环境模拟计算机工作模型及时钟中断
    我们进入实验楼环境,使用实验楼的虚拟机打开shell
    1). cd LinuxKernel/linux-3.9.4
    2). qemu -kernel arch/x86/boot/bzImage
    然后cd mykernel 您可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c

    执行后,可以看到屏幕上交替出现my_timer_handler和my_start_kernel。
    下面来看看mymain.c的代码:
    ...
    void __init my_start_kernel(void)
    {
        int i = 0;
        while(1)
        {
            i++;
            if(i%100000 == 0)
                printk(KERN_NOTICE "my_start_kernel here  %d \n",i);
                
        }
    }
    ...
    
    这段代码就是操作系统的入口,系统从这里开始启动;只是在这个地方被我们重写。
    myinterrupts.c:
    ...
    void my_timer_handler(void)
    {
    	printk(KERN_NOTICE "\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n");
    }
    ...
    
    上面的一段就是发生时钟中断时,执行的代码。在这里我们对它执行的过程有一个初步的了解即可,在后面我们会详细分析中断发生时,操作系统做了那些工作。
    4. 进程上下文切换
    我们将在上面一个mykernel的实验环境下,模拟构建一个简单操作系统内核,完成一个简单的时间片轮转多道程序实验,通过这个实验来了解进程上下文是如何切换的。
    同样我们进入实验楼环境,执行下面的命令即可看到一个模拟内核的执行:
    cd LinuxKernel/linux-3.9.4
    rm -rf mykernel
    patch -p1 < ../mykernel_for_linux3.9.4sc.patch
    make allnoconfig
    make #编译内核请耐心等待
    qemu -kernel arch/x86/boot/bzImage

    源代码在mykernel中,现在我们查看并分析代码:
    在mypcb.h 文件中,定义进程管理相关的数据结构
    mypcb.h
    ...
    /* CPU specific state of this task */
    struct Thread{
    	unsigned long ip;
    	unsigned long sp;
    };
    typedef struct PCB{
    	int pid;
    	volatile long state;
    	char stack[KERNEL_STACK_SIZE];
    	/* CPU-specific state of this task */
    	struct Thread thread;
    	unsigned long task_entry;
    	struct PCB *next;
    }tPCB;
    ...
    在mymain.c文件中,内核初始化和0号进程启动
    ...
    tPCB task[MAX_TASK_NUM];
    tPCB *my_current_task = NULL;
    volatile int my_need_sched = 0;
    ...
    /* 初始化0号进程的数据结构 */
    void __init my_start_kernel(void)
    {
    	int pid = 0;
    	int i;
    	
    	task[pid].pid = pid;
    	task[pid].state = 0; /* -1:unrunnable, 0: runnable, >0; stopped */
    	task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process; /* my_process函数的地址赋值给进程的入口地址 */
    	task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    	task[pid].next = &task[pid];
    	/* fork more process */
    	for(i=1; i<MAX_TASK_NUM;i++)
    	{
    		memcpy(&task[i],&task[0],sizeof(tPCB)); /* 创建的子进程,这里直接复制0号进程,只是为了方便 */
    		task[i].pid = i;
    		task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; 
    		*(task[i].thread.sp - 1) = task[i].thread.sp; 
    		task[i].thread.sp -= 1; 
    		task[i].next = task[i-1].next; / * 设置PCB指针指向下一个进程 */
    		task[i-1].next = &task[i]; 
    	} 
    	/* start process 0 by task[0] */ 
    	pid = 0; / *指向下面的内嵌汇编代码后,0号进程正式启动 */
    	my_current_task = &task[pid]; 
    	asm volatile( 
    		"movl %1,%%esp\n\t" 	/* set task[pid].thread.sp to esp */ 
    		"pushl %1\n\t" 	        /* push ebp */ 
    		"pushl %0\n\t" 	        /* push task[pid].thread.ip */ 
    		"ret\n\t" 	            /* pop task[pid].thread.ip to eip */ /* ret之后0号进程正式启动了 */
    		"popl %%ebp\n\t" 
    		:  
    		: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/ 
    	); 
    }
    0号进程启动后,执行my_process,在里面执行1千万次后,主动检查my_need_sched的值,看是否需要调到其他进程。my_need_sched是由时间片控制的。在myinterrrupt.c中,设置时间片的大小,时间片用完时设置一下调度标志:
    void my_timer_handler(void) 
    { 
    #if 1 
    	if(time_count%1000 == 0 && my_need_sched != 1) 
    	{ 
    	printk(KERN_NOTICE ">>>my_timer_handler here<<<\n"); 
    	my_need_sched = 1; 
    	}  
    	time_count ++ ;   
    #endif 
    return;  	 
    } 
    当需要进程调度时,也是在my_process中调用my_schedule:
    /* 两个正在运行的进程之间做进程上下文切换 */
    ...
    if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ 
    {         
    	my_current_task = next;  
    	printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);   
    	/* switch to next process */ 
    	asm volatile(	 
    	   	"pushl %%ebp\n\t" 	    /* save ebp */ 
    	   	"movl %%esp,%0\n\t" 	/* save esp */ 
    	   	"movl %2,%%esp\n\t"     /* restore  esp */ 
    	   	"movl $1f,%1\n\t"       /* save eip */	 /*$1f是指接下来的标号1:的位置*/
    	   	"pushl %3\n\t"  
    	   	"ret\n\t" 	            /* restore  eip */ 
    	   	"1:\t"                  /* next process start here */ 
    	   	"popl %%ebp\n\t" 
    	   	: "=m" (prev->thread.sp),"=m" (prev->thread.ip) 
    	   	: "m" (next->thread.sp),"m" (next->thread.ip) 
    	);  
    }
    else    /* 切换到一个新进程的方法 */
    {    
    	next->state = 0;    
    	my_current_task = next;    
    	printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);    
    	/* switch to new process */    
    	asm volatile(	    
    	    	"pushl %%ebp\n\t" 	    /* save ebp */    
    	    	"movl %%esp,%0\n\t" 	/* save esp */    
    	    	"movl %2,%%esp\n\t"     /* restore  esp */    
    	    	"movl $1f,%1\n\t"       /* save eip */	    
    	    	"pushl %3\n\t"     
    	    	"ret\n\t" 	            /* restore  eip */    
    	    	"movl %2,%%ebp\n\t"     /* restore  ebp */    
    	    	: "=m" (prev->thread.sp),"=m" (prev->thread.ip)    
    	    	: "m" (next->thread.sp),"m" (next->thread.ip)    
    		);              
    }     
    ...
    可以看出,切换进程的汇编程序和函数调用堆栈执行相似,其实原来也是相通的;但是切换进程确实要复杂得多。
    5.总结
    操作系统通过断上下文和进程上下文的切换完成多任务并行执行,各个任务的调度是通过时间片控制的。
    展开全文
  • Linux操作系统基础

    2018-05-27 12:16:53
    Linux是一种自由和开放源码的操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、台式计算机 Linux介绍 Linux出现于1991年,是...

    Linux简介

    Linux是一种自由和开放源码的操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、台式计算机
    这里写图片描述

    Linux介绍

    Linux出现于1991年,是由芬兰赫尔辛基大学学生Linus Torvalds和后来加入的众多爱好者共同开发完成

    Linux特点

    多用户,多任务,丰富的网络功能,可靠的系统安全,良好的可移植性,具有标准兼容性,良好的用户界面,出色的速度性能
    开源

    CentOS

    • 主流:目前的Linux操作系统主要应用于生产环境,主流企业级Linux系统仍旧是RedHat或者CentOS
    • 免费:RedHat 和CentOS差别不大,基于Red Hat Linux 提供的可自由使用源代码的企业CentOS是一个级Linux发行版本
    • 更新方便:CentOS独有的yum命令支持在线升级,可以即时更新系统,不像RedHat 那样需要花钱购买支持服务!

    Linux目录结构

    这里写图片描述

    • bin (binaries)存放二进制可执行文件
    • sbin (super user binaries)存放二进制可执行文件,只有root才能访问
    • etc (etcetera)存放系统配置文件
    • usr (unix shared resources)用于存放共享的系统资源
    • home 存放用户文件的根目录
    • root 超级用户目录
    • dev (devices)用于存放设备文件
    • lib (library)存放跟文件系统中的程序运行所需要的共享库及内核模块
    • mnt (mount)系统管理员安装临时文件系统的安装点
    • boot 存放用于系统引导时使用的各种文件
    • tmp (temporary)用于存放各种临时文件
    • var (variable)用于存放运行时需要改变数据的文件

    Linux常用命令

    命令格式:命令 -选项 参数

    如:ls  -la  /usr
    
    ls:显示文件和目录列表(list)

    常用参数:

    -l      (long)
    -a  (all)         注意隐藏文件、特殊目录.和..   
    -t      (time)

    Linux命令的分类

    内部命令:属于Shell解析器的一部分

    cd 切换目录(change directory)
    pwd 显示当前工作目录(print working directory)
    help 帮助

    外部命令:独立于Shell解析器之外的文件程序

    ls 显示文件和目录列表(list)
    mkdir 创建目录(make directoriy)
    cp 复制文件或目录(copy

    查看帮助文档

    内部命令:help + 命令(help cd)
    外部命令:man + 命令(man ls)

    操作文件或目录常用命令

    pwd 显示当前工作目录(print working directory)
    touch 创建空文件                                 
    mkdir 创建目录(make directoriy)
    -p 父目录不存在情况下先生成父目录 (parents)            
    cp 复制文件或目录(copy)
    -r 递归处理,将指定目录下的文件与子目录一并拷贝(recursive)     
    mv 移动文件或目录、文件或目录改名(move)
    
    rm 删除文件(remove)
    -r 同时删除该目录下的所有文件(recursive)
    -f 强制删除文件或目录(force)
    rmdir 删除空目录(remove directoriy)
    cat显示文本文件内容 (catenate)
    more、less 分页显示文本文件内容
    head、tail查看文本中开头或结尾部分的内容
    haed  -n  5  a.log 查看a.log文件的前5行
    tail  -F b.log 循环读取(follow)
    

    常用命令

    wc 统计文本的行数、字数、字符数(word count)
    -m 统计文本字符数
    -w 统计文本字数
    -l 统计文本行数
    find 在文件系统中查找指定的文件
    find /etc/ -name "aaa"
    grep 在指定的文本文件中查找指定的字符串
    ln 建立链接文件(link)
    -s 对源文件建立符号连接,而非硬连接(symbolic)
    
    top 显示当前系统中耗费资源最多的进程 
    ps 显示瞬间的进程状态
    -e /-A 显示所有进程,环境变量
    -f 全格式
    -a 显示所有用户的所有进程(包括其它用户)
    -u 按用户名和启动时间的顺序来显示进程
    -x 显示无控制终端的进程
    kill 杀死一个进程
    kill -9 pid
    df 显示文件系统磁盘空间的使用情况
    
    du 显示指定的文件(目录)已使用的磁盘空间的总
    -h文件大小以KMG为单位显示(human-readable)
    -s只显示各档案大小的总合(summarize)
    free 显示当前内存和交换空间的使用情况 
    netstat 显示网络状态信息
    -a 显示所有连接和监听端口
    -t (tcp)仅显示tcp相关选项
    -u (udp)仅显示udp相关选项
    -n 拒绝显示别名,能显示数字的全部转化成数字。
    -p 显示建立相关链接的程序名
    ifconfig 网卡网络配置详解 
    ping 测试网络的连通性 
    
    

    备份压缩命令

    gzip 压缩(解压)文件或目录,压缩文件后缀为gz 
    bzip2 压缩(解压)文件或目录,压缩文件后缀为bz2 
    tar 文件、目录打(解)包
    

    gzip命令

    命令格式:gzip [选项] 压缩(解压缩)的文件名
    -d将压缩文件解压(decompress)
    -l显示压缩文件的大小,未压缩文件的大小,压缩比(list)
    -v显示文件名和压缩比(verbose)
    -num用指定的数字num调整压缩的速度,-1或--fast表示最快压缩方法(低压缩比),-9或--best表示最慢压缩方法(高压缩比)。系统缺省值为6
    

    bzip2命令

    命令格式:bzip2 [-cdz] 文档名
    -c将压缩的过程产生的数据输出到屏幕上
    -d解压缩的参数(decompress)
    -z压缩的参数(compress)
    -num 用指定的数字num调整压缩的速度,-1或--fast表示最快压缩方法(低压缩比),-9或--best表示最慢压缩方法(高压缩比)。系统缺省值为6

    tar命令

    -c 建立一个压缩文件的参数指令(create)
    -x 解开一个压缩文件的参数指令(extract)
    -z 是否需要用 gzip 压缩
    -j 是否需要用 bzip2 压缩
    -v 压缩的过程中显示文件(verbose)
    -f 使用档名,在 f 之后要立即接档名(file)
    

    关机/重启命令

    shutdown系统关机 
    -r 关机后立即重启
    -h 关机后不重新启动
    halt 关机后关闭电源 shutdown -h
    reboot 重新启动 shutdown -r

    学习Linux的好习惯

    • 善于查看man page(manual)等帮助文档
    • 利用好Tab键
    • 掌握好一些快捷键

      ctrl + c(停止当前进程)
      ctrl + r(查看命令历史)
      ctrl + l(清屏,与clear命令作用相同)
      
    展开全文
  • 操作系统——线程

    2018-10-29 22:20:44
    在传统的操作系统中,进程是系统进行资源分配的基本单位,按进程为单位分给存放其映象所需要的虚地址空间、执行所需要的主存空间、完成任务需要的其他各类外围设备资源和文件。同时,进程也是处理器调度的基本单位,...
  • 在学习OS时,对于多任务操作系统的任务切换,一直不能理解:控制权是怎么么回到调度程序上的?记得在描述任务切换时,一般都是这么描述的:在每一个时钟滴答,都将检查当前进程是否是一个运行超过100毫秒的用户进程...
  • 刚实现了App内手机横/竖放置时,屏幕横/竖屏的切换。记录一下中间需要的关键信息和实现过程。 开门见山的说,实现屏幕自动/手动旋转的方式有两种: 一种是在工程的代码中定义,这种方式在横竖屏切换时执行的操作...
  • Android屏幕横竖屏切换和生命周期管理的详细总结 一般的我们去切换屏幕方向都是不希望Activity被重新创建,这时就需要对一些属性进行设置,或者使用代码设置。  今天想学一下Android屏幕横竖屏切换,但是网上很多...
  • PC端操作系统的巨人微软公司似乎也看上了移动端的操作生态社区,推出了WindowsPhone系统,也想在谷歌和苹果公司中分一杯羹。 WindowsPhone(下简称为wp系统)作为一个迟到的竞争者,刚刚开始似乎给其他两大操作系统...
  • 前言 操作系统是物联网时代的战略制高点,今天 PC 和手机时代的操作系统霸主未必能在物联网时代延续霸业。操作系统产业的规律是,当垄断已经形成,后来者就很难颠覆,只有等待下一次产业浪潮。如今,一个全新的、...
  • android-横向屏幕切换

    2011-09-21 19:12:14
    屏幕切换指的是在同一个Activity内屏幕见的切换,最长见的情况就是在一个FrameLayout内有多个页面,比如一个系统设置页面;一个个性化设置页面。 通过查看OPhone API文档可以发现,有个android.widget....
  • Android1.6平台屏幕横向纵向切换快捷键在 Windows 操作系统上按下 「Ctrl」和「F12」键 ,或是在 Mac OS X 操作系统上同时按下「fn」 和「7」键,屏幕就会从预设的直式显示改成横式显示,再按一次则切换回原来的直式...
  • 一:操作系统概述。  操作系统:管理系统资源、控制程序运行、改善人机界面、提供各种服务,并合理组织计算机工作流程和为用户方便而有效的使用计算机提供良好的运行环境的最基本的系统软件 。  传统的操作系统...
  • 文章目录主流操作系统介绍目录如下:简介:更多信息中文名外文名英文简称组成部分主流操作系统及其优缺点:85~95年主要操作系统- DOSMS-DOS的发展历史MS-DOS 1.0MS-DOS 2.0MS-DOS 3.XMS-DOS 4.0MS-DOS 5.0MS...
  • 操作系统 现代操作系统由一个或多个处理器、主存、打印机、键盘、鼠标、显示器、网络接口以及各种输入/输出设备构成。计算机操作系统是一个复杂的系统。 然而,程序员不会直接和这些硬件打交道,而且每位程序员不...
  • 一、Linux操作系统概述 二、Linux操作系统安装 三、Linux文件系统及文件基础 四、Linux操作系统命令使用基础 五、Linux应用程序的安装与卸载基础 五、用户及进程 六、相关信息查询 七、网络配置 八、Linux...
  • 多数是根据英文版原版答案翻译过来,少部分加了个人的理解 1、操作系统的两大主要作用是...2、在1.4节中描述了9中不同类型的操作系统,列举每种操作系统的应用(每种系统一种应用) 1.大型操作系统(Mainf...
  • 马上要考操作系统了,第一章 操作系统引论1、操作系统是什么?操作系统为用户完成所有“硬件相关,应用无关“的工作,以给用户方便、高效、安全的使用环境1.1、定义: 操作系统是一个大型的程序系统,它负责计算机...
  • (2)应用切换语言 二、屏幕适配 (1)图片适配 (2)、XML布局适配 (3)、横竖屏适配 三、适配不同系统版本 前言 智能手机的用户分布在不同国家,且偏好各异,这就要求开发阶段兼容适配;由于各厂家生产出...
  • 之前在网上看到有网友说,国产操作系统的界面不好看,很简陋很粗糙,就像是Windows XP的那种年代久远的操作界面一样。也有网友反驳说,国产操作系统界面友好,看起来很舒服。 那么事实上是怎样的呢?到底是国产操作...
1 2 3 4 5 ... 20
收藏数 125,938
精华内容 50,375
热门标签
关键字:

屏幕切换页 操作系统