栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

操作系统杂项笔记

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

操作系统杂项笔记

目录

RS232和RS485通讯接口有什么区别

用串口发送十个字节,丢失一个两个你会怎么检查

中断能不能睡眠?

中断为什么不能嵌套?

linux中系统调用过程?

Linux中的同步机制

ARM处理器的寄存器

复位后,ARM处理器处于 SVC 模式,ARM 状态

在ARM Linux系统中,中断处理程序进入C代码以后,ARM处于 超级用户(SVC) 工作模式

在ARM系统结构中,MMU映射最小的单元空间是 1KB ,映射最大的单元空间是 1MB

协处理器主要控制:片内的MMU、指令和数据缓存(IDC)、写缓冲(Write Buffer)

当一个异常出现以后,ARM微处理器会执行哪几步操作?

段错误处理

Linux中RCU原理

为什么uboot要关掉cache?

FIQ的什么特点使得它处理的速度比IRQ快?

32位bit位反转

container_of宏

ARM系统中,在函数调用的时候,参数是通过哪种方式传递的?

ARM有几种工作模式?

如何判断计算机处理器是大端还是小端?

为什么要区分内核态和用户态?

进程有几种状态?

进程间通信方式有哪些?

线程间同步方法?

死锁的四个必要条件是什么

内存管理有哪几种方式

Readn

Writen

fcntl

strace工具的实现原理

fork后子进程保留了父进程的什么?

etc配置文件

Linux启动过程

/etc/skel

new/delete和malloc/free的区别

数值溢出的判断方法(必会)

RCU机制

锁与进程间通信

内核锁机制

进程间通信


RS232和RS485通讯接口有什么区别
  1. 传输方式不同。RS232采取不平衡传输方式,即所谓单端通讯。而RS485则采用平衡传输,即差分传输方式。

  2. 传输距离不同。RS232适合本地设备之间的通信,传输距离一般不超过20m。而RS485的传输距离为几十米到上千米。

  3. RS232 只允许一对一通信,而RS485接口在总线上是允许连接多达128个收发器。RS232/RS485,是两种不同的电气协议,也就是说,是对电气特性以及物理特性的规定,作用于数据的传输通路上,它并不内含对数据的处理方式。比如,最显著的特征是: RS232使用3 - 15v有效电平,而UART,因为对电气特性并没有规定,所以直接使用CPU使用的电平,就是所谓的TTL电平(可能在0~3.3V之间)。更具体的,电气的特性也决定了线路的连接方式,比如RS232, 规定用电平表示数据,因此线路就是单线路的,用两根线才能达到全双工的目的;而RS485,使用差分电平表示数据,因此,必须用两根线才能达到传输数据的基本要求,要实现全双工,必需用4根线。但是,无论使用RS232还是RS485,它们与UART是相对独立的,但是由于电气特性的差别,必须要有专用的器件和UART接驳,才能完成数据在线路和UART之间的正常流动。

用串口发送十个字节,丢失一个两个你会怎么检查
  1. 发送方自己发自己收,看有无问题

  2. 接收方自己发自己收,看有无问题

  3. 都没问题就是在发送过程中出现问题,发送和接收的参数是否一致,比如波特率和奇偶校验位、打印发送buffer的数据和接收buffer的数据

  4. 检查丢失的字节有什么规律

  5. 逻辑分析仪查看发送和接收的时序,导出发送的数据和接收的数据

中断能不能睡眠?
  1. 中断处理的时候,不应该发生进程切换,因为在中断上下文中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断,如果在中断上下文中休眠,则没有办法唤醒它,因为所有的wake_up_xxx都是针对某个进程而言的,而在中断上下文中,没有进程的概念,没有一个task_struct,因此真的休眠了。

  2. schedule()在切换进程时, 保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复) ;

  3. 2.4内核中schedule()函数本身在进来的时候判断是否处于中断上下文。

中断为什么不能嵌套?

官方资料:中断处理不能嵌套

中断处理函数需要调用C函数,这就需要用到栈。中断A正在处理的过程中,假设又发生了中断B,那么在栈里要保存A的现场,然后处理B。在处理B的过程中又发生了中断C,那么在栈里要保存B的现场,然后处理C。如果中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。所以,为了防止这种情况发生,也是为了简单化中断的处理,在Linux系统上中断无法嵌套:即当前中断A没处理完之前,不会响应另一个中断B(即使它的优先级更高)。

linux中系统调用过程?
  1. 当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。

  2. 根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。这里以字符设备为例。在Linux操作系统中每个字符设备都有一个struct cdev结构体。此结构体描述了字符设备所有信息,其中最重要的一项就是字符设备的操作函数接口。

  3. 找到struct cdev结构体后,Linux内核就会将struct cdev结构体所在的内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中的记录的函数操作接口地址记录在struct file结构体的f_ops成员中。

  4. 任务完成,VFS层会给应用返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层应用程序就可以通过fd找到struct file,然后在由struct file找到操作字符设备的函数接口了。

Linux中的同步机制

原子操作、信号量、读写信号量、自旋锁、顺序锁

ARM处理器的寄存器

ARM处理器共有37个寄存器。它包含31个通用寄存器和6个状态寄存器。

 

复位后,ARM处理器处于 SVC 模式,ARM 状态

在ARM Linux系统中,中断处理程序进入C代码以后,ARM处于 超级用户(SVC) 工作模式

在ARM系统结构中,MMU映射最小的单元空间是 1KB ,映射最大的单元空间是 1MB

协处理器主要控制:片内的MMU指令和数据缓存(IDC)写缓冲(Write Buffer)

当一个异常出现以后,ARM微处理器会执行哪几步操作?
  1. 将下一条指令的地址存入相应的连接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行。若异常是从ARM状态进入,则LR寄存器中保存的是下一条指令的地址;若异常是从Thumb状态进入,则在LR寄存器中保存当前PC的偏移量,这样,异常处理程序就不需要确定异常是从何种状态进入的。

  2. 将CPSR复制到相应的SPSR中。

  3. 根据异常类型,强制设置CPSR的运行模式位。

  4. 强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处。

段错误处理

在开发板上 ulimit -c unlimited 生成core文件

在pc上 /bin/arm-linux-gdb ./test_debug ./core backtrace

Linux中RCU原理

答: rcu是2 .6出现的一种读写锁,可以说是老的读写锁的升级版,要用在链表这种数据结构上,经典使用场景是多读者少写者的情况,rcu允许多个读者一个写者共同操作数据而不必加锁,这是经典用法,若出现多个写者时,写者与写者之间就得自己手动同步。当要删除一个节点时,删除后并不会马上释放节点,而是会等待在删除动作之前已经开始读该节点的读者都完成读操作之后才会释放此节点,这段时间被称为宽限期。

为什么uboot要关掉cache?

Caches是CPU内部的一个2级缓存,它的作用是将常用的数据和指令放在CPU内部。Caches是通过CP15管理的,刚上电的时候,CPU还不能管理Caches。上电的时候指令Cache可关闭,也可不关闭,但数据Cache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从Cache里面取,而这时候RAM中数据还没有Cache过来,导致数据预取异常。

FIQ的什么特点使得它处理的速度比IRQ快?
  1. FIQ优先级比IRQ高,不会被中断

  2. FIQ有自己的专属寄存器: r8~r12, 不用对通用寄存器入栈保护,可以加快速度

  3. FIQ位于异常向量表的末尾0x1C,故无需跳转,可以在这里直接放置异常处理函数

32位bit位反转
unsigned reversebit(uint32 val)
{
    uint32 ret = 0, loop = 32;
    while(loop--)
    {
        ret <<= 1;
        ret |= val & 0x1;
        val >>= 1;
    }
}

container_of宏
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
#define container_of(ptr, type, member) ({          
        const typeof(((type *)0)->member)*__mptr = (ptr);    
    (type *)((char *)__mptr - offsetof(type, member)); })

ARM系统中,在函数调用的时候,参数是通过哪种方式传递的?

当参数小于等于4的时候是通过r0-r3寄存器来进行传递的,当参数大于4的时候是通过压栈的方式进行传递的。

ARM有几种工作模式?

用户模式(USR)、系统模式(SYS)、一般中断模式(IRQ)、快速中断模式(FIQ)、管理模式(SVC)、中止模式(ABT)、未定义模式(UND)

如何判断计算机处理器是大端还是小端?
#include 
int checkCPU()
{
#if 0
    union w
    {
        int a;
        char b;
    }
    c.a = 1;
    return (c.b == 1);
#elseif
    unsigned short usData = 0x1122;
    unsigned char *pucData = (unsigned char *)&usData;
    return (*pucData == 0x22);
#endif
}

int main()
{
    if(checkCPU())
        printf("小端n");
    else
        printf("大端n");
    return 0;
}

为什么要区分内核态和用户态?

对于Linux来说,通过区分内核空间和用户空间的设计,隔离了操作系统代码与应用程序代码。即便是单个应用程序出现错误,也不会影响到操作系统的稳定性,这样其他的程序还可以正常的运行。所以,区分内核空间和用户空间本质上是要提高操作系统的稳定性及可用性。

进程有几种状态?
  1. 创建状态

  2. 就绪状态

  3. 运行状态

  4. 阻塞状态

  5. 终止状态

进程间通信方式有哪些?

管道、信号量、消息队列、信号、共享内存、套接字

线程间同步方法?

临界区、互斥量、信号量、事件

死锁的四个必要条件是什么
  1. 互斥:某种资源一次只允许一个进程访问,即该资源一但分配给某个进程,其他进程就不能再访问,直到该进程访问结束。

  2. 占用且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。

  3. 可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。

  4. 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

内存管理有哪几种方式

常见的内存管理方式有块式管理、页式管理、段式管理和段页式管理。最常用的是段页式管理。

  1. 块式管理:把主存分为一大块一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断载入主存,就算所需的程序片段只有几个字节,也只能把这一块分配给它。 这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。

  2. 页式管理:用户程序的地址空间被划分成若干个固定大小的区域,这个区域被称为页”,相应地,内存空间也被划分为若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一 块中, 从而实现了离散分配。这种方式的优点是页的大小是固定的,因此便于管理;缺点是页长与程序的逻辑大小没有任何关系。这就导致在某个时刻一个程序可能只有一部分在主存中, 而另一部分则在辅存中。这不利于编程时的独立性,并给换入换出处理、存储保护和存储共享等操作造成麻烦。

  3. 段式管理:段是按照程序的自然分界划分的并且长度可以动态改变的区域。使用这种方式,程序员可以把子程序、操作数和不同类型的数据和函数划分到不同的段中。这种方式将用户程序地址空间分成若干个大小不等的段,每段可以义-组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。

  4. 段页式管理:段页式存储组织是分段式和分页式结合的存储组织方法,这样可充分利用分段管理和分页管理的优点 ①用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有自己的段名,再把每段分成固定大小的若干页 ②用分页方法来分配和管理内存,即把整个主存分成与上述页大小相等的存储块,可装入作业的任何一项。程序对内存的调入或调出是按页进行的,但它又可按段实现共享和保护。

Readn
ssize_t Readn(int fd, void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nread;
    char *ptr;
    
    ptr = vptr;
    nleft = n;
    
    while(nleft > 0) {
        if((nread = read(fd, ptr, nleft)) < 0) {
            if(errno == EINTR)
                nread = 0;
            else
                return -1;
        } else if(nread == 0)
            break;
        
        nleft -= nread;
        ptr += nread;
    }
    return n - nleft;
}

Writen
ssize_t Writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;
    
    ptr = vptr;
    nleft = n;
    while(nleft > 0) {
        if((nwritten = write(fd, ptr, nleft)) <= 0) {
            if(nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

fcntl
int flags;
if((flags = fcntl(fd, F_GETFL, 0)) < 0)
    err_sys("F_GETFL error");
flags |= O_NONBLOCK;
if(fcntl(fd, F_SETFL, flags) < 0)
	err_sys("F_SETFL error");
------------------------------------------------------------
if(fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
    err_sys("F_SETFL error");

第二段代码在设置非阻塞标志的同时也清除了所有其他文件状态标志。设置某个文件状态标志的唯一正确的方法是:先取得当前标志,与新标志逻辑或后再设置标志。

strace工具的实现原理

strace工具是一个用户态的应用程序,用来追踪进程的系统调用。它的基础就是ptrace系统调用。安装strace之后,就可以使用strace命令了。

最简单的strace命令的用法就是:strace PROG,PROG是要执行的程序。strace命令执行的结果就是按照调用顺序打印出所有的系统调用,包括函数名、参数列表以及返回值。

使用strace跟踪一个进程的系统调用的基本流程如图所示。

从图中可以看出strace做了以下几件事情:

  1. 设置SIGCHLD 信号的处理函数,这个处理函数只要不是SIG_IGN即可。由于子进程停止后是通过SIGCHLD信号通知父进程的,所以这里要防止SIGCHLD信号被忽略。

  2. 创建子进程,在子进程中调用ptrace(PTRACE_TRACEME,0L, 0L, 0L)使其被父进程跟踪,并通过execv函数执行被跟踪的程序。

  3. 通过wait()等待子进程停止,并获得子进程停止时的状态status。

  4. 通过子进程的状态查看子进程是否已正常退出,如果是,则不再跟踪,随后调用ptrace发送PTRACE_DETACH请求解除跟踪关系。

  5. 子进程停止后,打印系统调用的函数名、参数和返回值。

  6. 通过PTRACE_SYSCALL让子进程继续运行,由于这个请求会让子进程在系统调用的入口处和系统调用完成时都会停止并通知父进程,这样,父进程就可以在系统调用开始之前获得参数,结束之后获得返回值。

在系统调用的入口和结束时子进程停止运行时,这时父进程认为子进程是因为收到SIGTRAP信号而停止的。所以父进程在wait()后可以通过SIGTRAP来与其他信号区分开。

Strace中为每个要跟踪的进程维护了一个TCB(Trace Control Block)结构,定义如下。它保存了当前发生的系统调用的信息。

struct tcb {
    int flags;    
    int pid;      
    int qual_flg; 
    int u_error;  
    long scno;    
    long u_arg[MAX_ARGS];    
    long u_rval;      
    int curcol;       
    FILE *outf;       
    const char *auxstr;
    const struct_sysent *s_ent;
    struct timeval stime;
    struct timeval dtime;    
    struct timeval etime;    
              
    long inst[2];     
};

 上面已经提到,子进程会在系统调用前后各停止一次,所以打印系统调用信息时分为两个阶段:在系统调用开始时可以获取系统调用号和参数,在系统调用结束时可以获取系统调用的返回结果。通过给tcb结构的flags字段清除和添加TCB_INSYSCALL标志位来区分系统调用的开始和结束。

fork后子进程保留了父进程的什么?

使用fork函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。

子进程与父进程的区别在于:

1、父进程设置的锁,子进程不继承(因为如果是排它锁,被继承的话,矛盾了)

2、各自的进程ID和父进程ID不同

3、子进程的未决告警被清除;

4、子进程的未决信号集设置为空集。

etc配置文件

/etc/profile:此文件为系统的为每个用户设置环境信息,当用户第一次登录时,该文件被执行.

并从/etc/profile.d 目录的配置文件中搜集shell的设置. /etc/bashrc :为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取. ~/.bash_profile :每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该 文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件. ~/.bashrc :该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该 该文件被读取.

~/.bash_logout :当每次退出系统(退出bash shell)时,执行该文件.

Linux启动过程

Linux启动流程:

  1. BIOS加电自检;

  2. 从硬盘0柱面 0磁道 第一扇区读512字节的MBR主引导记录;

  3. 运行引导程序Grub并根据其配置加载kernel镜像后初始化;

  4. 根据/etc/inittab中系统初始化配置执行/etc/rc.sysinit脚本;

  5. 根据第3步读到的runlevel值启动对应服务;

  6. 运行/etc/rc.local;

  7. 生成终端待用户登录。

/etc/skel

Linux下的/etc/skel目录往往不被人注意,其实此目录在新建用户时还是很有用的,灵活运用此目录可以节约一定的配置时间。skel是skeleton的缩写,意为骨骼、框架。故此目录的作用是在建立新用户时,用于初始化用户根目录。系统会将此目录下的所有文件、目录都复制到新建用户的根目录,并且将用户属主与用户组调整为与此根目录相同。所以可将用户配置文件预置到/etc/skel目录下,比如说.bashrc、.profile与.vimrc等。

new/delete和malloc/free的区别

1、new、delete是C++中的操作符,而malloc和free是标准库函数。

2、对于非内部数据对象来说,只使用malloc是无法完成动态对象要求的,一般在创建对象时需要调用构造函数,对象消亡时,自动的调用析构函数。而malloc free是库函数而不是运算符,不在编译器控制范围之内,不能够自动调用构造函数和析构函数。而NEW在为对象申请分配内存空间时,可以自动调用构造函数,同时也可以完成对对象的初始化。同理,delete也可以自动调用析构函数。而mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。

3、new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。

数值溢出的判断方法(必会)

一、关于数值越界:合理利用 INT_MAX/10 数值越界,即大于 2147483647,或小于 -2147483648。通过观察程序结构,我们发现,每次循环时 value 的值都会扩大10倍(乘以10),那么,我们是否就可以利用 INT_MAX/10 的值来提前一步判断是否会越界呢?这里我们以正数的越界为例进行解释:

  • 当 value > INT_MAX/10 时,说明本轮扩大10倍后,value 必将越界(超过 INT_MAX);

  • 当 value == INT_MAX/10 时,说明扩大10倍后,value 可能越界,也可能不越界,需要利用当前的加数 digit 来进行进一步的判断:当 digit > 7 时(以正数为例),越界;

  • 否则,当 value < INT_MAX/10 时,本轮循环必不越界(扩大10倍后也小于 INT_MAX);

二、将正数、负数的越界判断合并起来: 为了保证代码简洁高效,这里我们不得不寻求一种方法,使正数、负数的越界判断可以合并起来进行(同样,这里我们也利用了数值化的正负标记位 isNegtive): 我们设置一个变量 overValue 来表示当前的值和 INT_MAX/10 的差,因为 INT_MAX/10 为正数,所以当当前值为负数时,需要统一转化为正数,故而有:

overValue = isNegtive*value - INT_MAX/10;

这样,当 overValue > 0 时,越界,overValue < 0 时,不越界,而当 overValue == 0 时: 正数时(isNegtive == 1),digit > 7 越界,负数时(isNegtive == -1),digit > 8 越界,通过 (isNegtive+1)/2 来将 -1、1转换为0、1,从而使有关 digit 的判断统一转化为:

  • 当 (isNegtive+1)/2 + digit > 8 时,数值越界;

综上,令:

overValue = isNegtive*value - INT_MAX/10 + (((isNegtive+1)/2 + digit > 8) ? 1:0);

RCU机制

RCU的原理:该机制记录了指向共享数据结构的指针的所有使用者。在该结构将要改变时,则首先创建一个副本(或一个新的实例,填充适当的内容,这没什么差别),在副本中修改。在所有进行读访问的使用者结束对旧副本的读取之后,指针可以替换为指向新的、修改后副本的指针。

假定指针ptr指向一个被RCU保护的数据结构。直接反引用指针是禁止的,首先必须调用rcu_dereference(ptr),然后反引用返回的结果。此外,反引用指针并使用其结果的代码,需要用rcu_read_lock和rcu_read_unlock调用保护起来:

rcu_read_lock();
p = rcu_dereference(ptr);
if(p != NULL) {
    awesome_function(p);
}
rcu_read_unlock();

锁与进程间通信

内核锁机制
  • 自旋锁

  • 信号量

  • RCU机制

  • 读写锁

  • 大内核锁

  • 互斥量

进程间通信
  • 信号量

  • 消息队列

  • 共享内存

  • 信号

  • 管道和套接字

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/277190.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号