今天面Intel,要多残有多残

记录问题如下:

  • Linux下动态链接库与静态链接库的区别以及动态链接库的创建
    • 通常情况下,对函数库的链接是放在编译时期(compile time)完成的。所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)。程序在运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下。所以这些函数库被成为静态库(static libaray),通常文件名为“libxxx.a”的形式。
    • 其实,我们也可以把对一些库函数的链接载入推迟到程序运行的时期(runtime)。这就是如雷贯耳的动态链接库(dynamic link library)技术
    • 动态链接库的特点与优势,首先让我们来看一下,把库函数推迟到程序运行时期载入的好处:
      • 可以实现进程之间的资源共享
        • 什么概念呢?就是说,某个程序的在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了。如果有,则让其共享那一个拷贝;只有没有才链接载入。这样的模式虽然会带来一些“动态链接”额外的开销,却大大的节省了系统内存资源。C的标准库就是动态链接库,也就是说系统中所有运行的程序共享着同一个C标准库的代码段。
      • 将一些程序升级变得简单
        • 用户只需要升级动态链接库,而无需重新编译链接其他原有的代码就可以完成整个程序的升级。Windows 就是一个很好的例子
      • 甚至可以真正做到链接载入完全由程序员在程序代码中控制
        • 程序员在编写程序的时候,可以明确的指明什么时候或者什么情况下,链接载入哪个动态链接库函数。你可以有一个相当大的软件,但每次运行的时候,由于不同的操作需求,只有一小部分程序被载入内存。所有的函数本着“有需求才调入”的原则,于是大大节省了系统资源。比如现在的软件通常都能打开若干种不同类型的文件,这些读写操作通常都用动态链接库来实现。在一次运行当中,一般只有一种类型的文件将会被打开。所以直到程序知道文件的类型以后再载入相应的读写函数,而不是一开始就将所有的读写函数都载入,然后才发觉在整个程序中根本没有用到它们。
    • 动态链接库的创建
      • 由于动态链接库函数的共享特性,它们不会被拷贝到可执行文件中。在编译的时候,编译器只会做一些函数名之类的检查。在程序运行的时候,被调用的动态链接库函数被安置在内存的某个地方,所有调用它的程序将指向这个代码段。因此,这些代码必须使用相对地址,而不是绝对地址。在编译的时候,我们需要告诉编译器,这些对象文件是用来做动态链接库的,所以要用地址无关代码(Position Independent Code (PIC))
      • 对gcc编译器,只需添加上 -fPIC 标签,如:
        • gcc -fPIC -c file1.c
          gcc -fPIC -c file2.c
          gcc -shared libxxx.so file1.o file2.o
        • 注意到最后一行,-shared 标签告诉编译器这是要建立动态链接库。这与静态链接库的建立很不一样,后者用的是 ar 命令。也注意到,动态链接库的名字形式为 “libxxx.so” 后缀名为 “.so”
    • 动态链接库的使用
      • 使用动态链接库,首先需要在编译期间让编译器检查一些语法与定义。
      • 这与静态库的实用基本一样,用的是 -Lpath 和 -lxxx 标签。如:
        • gcc file1.o file2.o -Lpath -lxxx -o program.exe
        • 编译器会先在path文件夹下搜索libxxx.so文件,如果没有找到,继续搜索libxxx.a(静态库)
      • 在程序运行期间,也需要告诉系统去哪里找你的动态链接库文件。在UNIX下是通过定义名为 LD_LIBRARY_PATH 的环境变量来实现的。只需将path赋值给此变量即可。csh 命令为:
        • setenv LD_LIBRARY_PATH your/full/path/to/dll
      • 一切安排妥当后,你可以用 ldd 命令检查是否连接正常
        • ldd program.exe
    • 动态链接库*.so的编译与使用
      1. 动态库的编译
        • 下面通过一个例子来介绍如何生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.c、test_b.c、test_c.c,我们将这几个文件编译成一个动态库:libtest.so
          • so_test.h:
            • #include 
              #include 
              
              void test_a();
              void test_b();
              void test_c();
          • test_a.c:
            • #include "so_test.h"
              void test_a()
              {
              printf("this is in test_a...\n");
              }
          • test_b.c:
            • #include "so_test.h"
              void test_b()
              {
              printf("this is in test_b...\n");
              }
          • test_c.c
            • #include "so_test.h"
              void test_c()
              {
              printf("this is in test_c...\n");
              }
        • 将这几个文件编译成一个动态库:libtest.so
          • gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
      2. 动态库的链接
        • 在1、中,我们已经成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程序来调用这个库里的函数。程序的源文件为:test.c
          • #include "so_test.h"
            int main()
            {
            test_a();
            test_b();
            test_c();
            return 0;
            }
        • 将test.c与动态库libtest.so链接生成执行文件test:
          • gcc test.c -L. -ltest -o test
        • 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了
          • ldd test
        • 执行test,可以看到它是如何调用动态库中的函数的
      3. 编译参数解析
        • -shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
        • -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
        • -L.:表示要连接的库在当前目录中
        • -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
        • LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
        • 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了
    • 调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了
  • gdb 怎么定位程序崩溃位置
    • 通过生成core file文件加上gdb来定位
    • Linux 下如何生成core file
      • 我们可以使用ulimit这条命令对core file文件的大小进行设定
      • 一般默认情况下,core file的大小被设置为了0,这样系统就不dump出core file了
      • 这时用如下命令进行设置:
        • ulimit -c unlimited
        • 这样便把core file的大小设置为了无限大,同时也可以使用数字来替代unlimited,对core file的上限值做更精确的设定
    • 生成的core file在哪里?
      • core file生成的地方是在/proc/sys/kernel/core_pattern文件定义的
      • 改动到生成到自己定义的目录的方法是:
        • echo "pattern" > /proc/sys/kernel/core_pattern
      • "pattern"类似我们C语言打印字符串的格式,相关标识如下:
        • %%: 相当于%
          %p: 相当于
          %u: 相当于
          %g: 相当于
          %s: 相当于导致dump的信号的数字
          %t: 相当于dump的时间
          %h: 相当于hostname
          %e: 相当于执行文件的名称
      • 这时用如下命令设置生成的core file到系统/tmp目录下,并记录pid以及执行文件名
        • echo "/tmp/core-%e-%p" > /proc/sys/kernel/core_pattern
    • 测试如下代码
      • #include   
           
        intfunc(int*p)  
        {  
                *p = 0;  
        }  
           
        intmain()  
        {  
                func(NULL);  
                return0;  
        }
      • 生成可执行文件并运行
        • gcc -o main a.c
          root@ubuntu:~# ./main
          Segmentation fault (core dumped) 
          <-----这里出现段错误并生成core文件了。
          在/tmp目录下发现文件core-main-10815
    • 如何查看进程挂在哪里了?
      • 我们可以用
        gdb main /tmp/core-main-10815 
        查看信息,发现能定位到函数了
        Program terminated with signal 11, Segmentation fault.
        #0  0x080483ba in func ()
    • 如何定位到行?
      • 在编译的时候开启-g调试开关就可以了
        gcc -o main -g a.c
        gdb main /tmp/core-main-10815 
        最终看到的结果如下,好棒。
        Program terminated with signal 11, Segmentation fault.
        #0  0x080483ba in func (p=0x0) at a.c:5
        5          *p = 0;
    • 总结一下,需要定位进程挂在哪一行我们只需要4个操作
      • ulimit -c unlimited
        echo "/tmp/core-%e-%p" > /proc/sys/kernel/core_pattern
        gcc -o main -g a.c
        gdb main /tmp/core-main-10815 
        就可以啦。
    • 补充说明,gdb的常用命令:
      • (gdb) backtrace /* 查看当前线程函数栈回溯 */
        以上面的例子为例
        Program terminated with signal 11, Segmentation fault.
        #0  0x080483ba in func (p=0x0) at main.c:5
        5*p = 0;
        (gdb) backtrace
        #0  0x080483ba in func (p=0x0) at main.c:5
        #1  0x080483d4 in main () at main.c:10
        如果是多线程环境下(gdb) thread apply all backtrace /* 显示所有线程栈回溯 */
      • (gdb) print [var] /* 查看变量值 */
        (gdb) print p
        $1 = (int *) 0x0
        (gdb) print &p
        $2 = (int **) 0xbf96d4d4
      • (gdb) x/FMT [Address] /* 根据格式查看地址指向的值 */
        其中
        FMT is a repeat count followed by a format letter and a size letter.
        Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
          t(binary), f(float), a(address), i(instruction), c(char) and s(string).
        Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
        The specified number of objects of the specified size are printed
        according to the format.
         
        (gdb) x/d 0xbf96d4d4
        0xbf96d4d4:0
        (gdb) x/c 0xbf96d4d4
        0xbf96d4d4:0 '\000'
    • 另外linuxi下能导致产生core file文件的信号有以下10种
      • SIGQUIT:终端退出符
        SIGILL:非法硬件指令
        SIGTRAP:平台相关的硬件错误,现在多用在实现调试时的断点
        SIGBUS:与平台相关的硬件错误,一般是内存错误
        SIGABRT:调用abort函数时产生此信号,进程异常终止
        SIGFPE:算术异常
        SIGSEGV:segment violation,无效内存引用
        SIGXCPU:超过了cpu使用资源限制(setrlimit)
        SIGXFSZ:超过了文件长度限制(setrlimit)
        SIGSYS:无效的系统调用
  • Java Native 接口的代码具体怎么写,Java的代码怎么写,C的代码要怎么写
    • 参见博客
  • Java多线程同步问题
    • 这方面应该是我想但了解的地方,可是也回到的不好,真想抽自己两下
    • Java信号量机制Java Semaphore
    • 当时问了一个读写者问题,问:如果由于某种原因数据队列一直是空的,那么读进程就会不断尝试访问数据队列,但是总是在做无用功,如何避免这个情况的发生
      • 我只想说当时犯什么傻,多好的用观察者模式的场景,怎么就想不起来
  • 问了一个对链式数据结构优化的问题,这个回答的还不错
    • 数组和链表的区别优劣
    • 如何优化链式的数据结构避免它的劣势
      • 内建一些高效的搜索数据结构,如:红黑树
    • 内建这些数据结构对存储的数据有什么要求
      • 有节点的比较就一定会产生key的概念,那么会要求key定长、存入数据单元的大小预先知道
  • 问了HANA是分布式的吗?(HANA是支持分布式的),那么是通过什么策略来解决  分布式内存数据一致性问题
    • 这个我也很想扇自己,当初研究的Memcached一致性hash环算法干什么吃去了

你可能感兴趣的:(笔试面试)