嵌入式面试常见问题(三)

1.linux下的proc文件夹是干什么的?

  1. 进程信息:/proc文件夹包含有关系统上运行的每个进程的信息。您可以在/proc中找到以进程ID(PID)为名称的子文件夹,每个子文件夹包含有关特定进程的信息,如状态、命令行参数、资源使用情况等。

  2. 系统信息:/proc中还包含了一些文件,提供了有关系统硬件、内核参数和配置的信息。例如,您可以在/proc/cpuinfo中找到有关CPU的信息,而/proc/meminfo包含有关内存使用情况的信息。

  3. 内核参数:/proc文件夹还允许您在运行时访问和修改一些内核参数。这使得您可以在不重新启动系统的情况下对系统行为进行某些调整。

  4. 网络信息:/proc/net子文件夹提供了有关网络配置和连接的信息,允许系统管理员监视和配置网络接口。

  5. 文件系统信息:/proc/mounts包含有关已挂载文件系统的信息,而/proc/filesystems包含了支持的文件系统类型列表。

总之,/proc文件夹在Linux系统中扮演了一个关键的角色,允许用户和系统管理者访问有关内核状态、进程和系统硬件的信息,以及对系统进行诊断和调整。这些信息对于监视和调试系统以及进行性能分析非常有用。

        proc文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口,用户和应用程序可以通过proc文件系统得到系统信息和进程相关信息,对proc文件系统的读写作为与内核进行通信的一种手段。但是与普通文件不同的是,proc文件系统是动态创建的,文件本身并不存在于磁盘当中、只存在于内存当中,与devfs一样,都被称为虚拟文件系统

        最初构建proc文件系统是为了提供有关系统中进程相关的信息,但是由于这个文件系统非常有用,因此内核中的很多信息也开始使用它来报告,或启用动态运行时配置。内核构建proc虚拟文件系统,它会将内核运行时的一些关键数据信息以文件的方式呈现在proc文件系统下的一些特定文件中,这样相当于将一些不可见的内核中的数据结构以可视化的方式呈现给应用层

        proc文件系统挂载在系统的/proc目录下,对于内核开发者(譬如驱动开发工程师)来说,proc文件系统给了开发者一种调试内核的方法:通过查看/proc/xxx文件来获取到内核特定数据结构的值,在添加了新功能前后进行对比,就可以判断此功能所产生的影响是否合理。

        /proc目录下中包含了一些目录和虚拟文件,如下所示: 

嵌入式面试常见问题(三)_第1张图片

        可以看到/proc目录下有很多以数字命名的文件夹,譬如100038、2299、98560,这些数字对应的其实就是一个一个的进程PID号,每一个进程在内核中都会存在一个编号,通过此编号来区分不同的进程,这个编号就是PID号。

        /proc目录下除了文件夹之外,还有很多的虚拟文件,譬如buddyinfo、cgroups、cmdline、version等等,不同的文件记录了不同信息,关于这些文件记录的信息和意思如下: 

⚫ cmdline:内核启动参数;
⚫ cpuinfo:CPU相关信息;
⚫ iomem:IO设备的内存使用情况;
⚫ interrupts:显示被占用的中断号和占用者相关的信息;
⚫ ioports:IO端口的使用情况;
⚫ kcore:系统物理内存映像,不可读取;
⚫ loadavg:系统平均负载;
⚫ meminfo:物理内存和交换分区使用情况;
⚫ modules:加载的模块列表;
⚫ mounts:挂载的文件系统列表;
⚫ partitions:系统识别的分区表;
⚫ swaps:交换分区的利用情况;
⚫ version:内核版本信息;
⚫ uptime:系统运行时间;

        proc文件系统的使用就是去读取/proc目录下的这些文件,获取文件中记录的信息,可以直接使用cat命令读取,也可以在应用程序中调用open()打开、然后再使用read()函数读取。 

使用read()函数读取

#include  
#include  
#include  
#include  
#include  
#include  
int main(int argc, char *argv[]) 
{ 
    char buf[512] = {0}; 
    int fd; 
    int ret; 

    /* 打开文件 */ 
    fd = open("/proc/version", O_RDONLY); 
    if (-1 == fd) 
    { 
        perror("open error"); 
        exit(-1); 
    }

    /* 读取文件 */ 
    ret = read(fd, buf, sizeof(buf)); 
    if (-1 == ret) 
    { 
        perror("read error"); exit(-1); 
    }

    /* 打印信息 */ 
    puts(buf);
     
    /* 关闭文件 */ 
    close(fd); 
    exit(0); 
}

 运行结果:

2.线程冲突的概念?以及解决办法?

答:线程冲突,也被称为竞态条件,是指在多线程环境中,当两个多更多的线程同时访问某一共享数据时,最终输出的结果取决于线程的运行时序,这可能导致输出的数据混乱,达到不可预测的效果。也可能导致数据丢失、程序崩溃或者死锁情况。

解决方法:

使用互斥锁(Mutex)互斥锁是一种同步机制,用于防止多个线程同时访问共享资源。比如在C++中,可以使用std::mutex类来创建互斥锁。当一个线程需要访问共享资源时,他必须首先获取互斥锁。如果互斥锁已经被另一个线程获取,那么尝试获取互斥锁的线程将被阻塞,直到拥有互斥锁的线程释放它。示例如下:

#include 
#include 
#include 

std::mutex mtx; // 创建互斥锁

void print_block(int n, char c) {
    // 锁定
    mtx.lock();
    for (int i=0; i

        比如项目上有辐射探测器和温湿度传感器,这是两个线程,而其余线程又需要这两个线程的数据,一般都会对两个传感器的数值使用mutex.lock()和mutex.unlock()进行加锁,防止数据处理冲突。

加条件变量:条件变量是一种同步机制。可以用来解决多线程间的协调问题。条件变量可以让一个线程在某个条件不满足时处于等待状态,直到另一个线程改变了条件并通知该条件变量。

还有信号量、原子操作、读写锁

3.死锁的概念以及解决方法

        死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。产生死锁的原因,主要包括:

  • 系统资源不足;
  • 程序执行的顺序有问题;
  • 资源分配不当等。

        如果系统资源充足,进程的资源请求都能够得到满足,那么死锁出现的可能性就很低;否则,就会因争夺有限的资源而陷入死锁。其次,程序执行的顺序与速度不同,也可能产生死锁。产生死锁的四个必要条件:

1.互斥使用即当资源被一个线程使用 (占有)时,别的线程不能使用3。
2.不可抢占资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放3。
3.请求和保持即当资源请求者在请求其他的资源的同时保持对原有资源的占有3。
4.循环等待即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

        解决死锁问题的方法主要有以下三种: 

1.死锁防止:通过设置一些限制条件,去破坏产生死锁的必要条件。
2.死锁避免:在程序运行时避免发生死锁。
3.死锁检测和恢复:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉

        具体来说,可以通过以下方式来解决死锁: 

1.破坏互斥条件:使资源同时访问而非互斥使用。
2.破坏占有和等待条件:采用静态分配的方式。
3.破坏不剥夺条件:剥夺调度能够防止死锁。
4.破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。

4.面向对象编程(OOP)和面向过程编程(POP)的区别

        面向过程编程:函数是程序的基本单元,我们可以把一个问题分解成多个步骤来解决,每一步或每一个功能都可以使用函数来实现。

        面向对象编程:对象是程序的基本单元,对象是类的实例化,类则是对客观事物抽象信息而成的一种数据类型,其内部包括属性和方法(即数据成员和函数实现)

        POP和OOP除了在语言语法上实现的不同,更大的区别在于两者解决问题的思路不同:

        面向过程编程侧重于解决问题的步骤过程,一般适用于简单功能的实现场合。如要完成一件事情:把大象放到冰箱里,我们可以分为三步
        ● 打开冰箱门。
        ● 把大象放到冰箱里。
        ● 关上冰箱门。
        每一步我们都可以使用一个函数完成特定的功能,然后在主程序中分别调用即可

        面向对象编程则侧重于将问题抽象、封装成一个个类,然后通过继承来实现代码复用,面向对象编程一般用于复杂系统的软件分层和架构设计。

5.vector Rad_R(2000);和vector Rad_R;Rad_R.reserve(2000);一样吗?

`vector Rad_R(2000);` 和 `vector Rad_R; Rad_R.reserve(2000);` 不是完全相同的。

1. `vector Rad_R(2000);`:这行代码创建了一个包含 2000 个整数的向量 `Rad_R`,并在创建时就分配了足够的内存来容纳这些元素。这意味着向量的大小是2000,同时也会为这2000个整数元素分配内存,准备存储数据

2. `vector Rad_R; Rad_R.reserve(2000);`:这行代码首先创建了一个空的整数向量 `Rad_R`,然后使用 `reserve(2000)` 函数来保留向量的容量,以便容纳2000个元素。但请注意,这只是为向量预留了足够的容量,而向量的大小仍然为零。您需要通过 `push_back` 或其他方法向向量添加元素,使其达到指定的大小。

所以,两者之间的主要区别在于内存的分配时间点。第一个示例在创建向量时就为元素分配了内存,而第二个示例只是预留了足够的容量,而没有添加元素

情况一实例代码如下:

#include 
#include 
using namespace std;
vector Rad_R(2000);

int main(char argc,char **argv)
{
    cout << "Rad_R.size = " << Rad_R.size() << endl;
	cout << "Rad_R.capacity = " <

 运行结果如下:

情况二实例代码如下:

#include 
#include 
using namespace std;
vector Rad_R;

int main(int argc,char **argv)
{
    Rad_R.reserve(2000);
    cout << "Rad_R.size = " << Rad_R.size() << endl;
	cout << "Rad_R.capacity = " <

  运行结果如下:

你可能感兴趣的:(嵌入式面试题总结,c语言,开发语言,linux,面试,笔记,学习)