Linux应用开发基础

  1. 由源文件到可执行文件分为四部分
    • 预编译 gcc -E main.c -O main.i
    • 编译 gcc -S main.i -O main.s
    • 汇编 gcc -C main.s -O main.o
    • 链接 gcc main.o -O main
  2. Makefile
  3. GDB调试程序
    • debug和release版本的区别
    • gcc -g 编译debug版本
    • 通常使用gdb调试程序
      • l 显示代码
      • b 行号/函数名 加断点
      • info break 查看断点信息
      • r 运行程序
      • n 单布执行
      • p 打印内容
      • c 继续运行
      • s 进入函数
      • finish 退出函数
      • q 退出程序
      • bt 函数调用栈的关系
  4. 静态库和共享库
    • libxxx.a(静态库)、libxxx.so(共享库)
    • 库文件默认在/lib、/usr/lib,头文件默认放在/usr/include下
    • 静态库:
      • ar crv libxx.a a.o b.o
      • gcc -o main main.c -L路径 -l库名
    • 共享库
      • gcc -shared -fPIC -o libfoo.so add.o max.o
      • gcc -o main main.c -L路径 -l库名(在使用该命令时,默认优先使用共享库)
      • 在运行共享库时默认再标准目录下找共享库,使用ldd命令查看库调用情况
  5. 主函数入口参数
    • argc:参数数量
    • grgv:参数内容
      • 数组指针:int (*p)[] = NULL
      • 指针数组:int *p[n] = {NULL}
    • envp:环境变量,环境变量继承自父进程,在命令行中执行程序时,父进程就是bash命令行,可以使用export添加环境变量
  6. 输出缓冲区
    • 在执行printf时,会将数据发送到数据缓冲区,数据缓冲区在积攒一定数据后,会将数据打印到窗口
    • 使用\n换行符会将数据立刻打印输出
    • 使用fflush(stdout)刷新缓冲区
    • 如果不刷新缓冲区,数据不会被打印,默认在exit函数中进行刷新,若使用_exit(1)可以观察到,无数据输出
  7. fork复制进程
    • 父进程的fork()结果不为0,子进程为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>

int main(int argc, char *argv[], char *envp[])
{
char *s = NULL;
int n = 0;

pid_t id = fork();
assert( id != -1);
if(id == 0)
{
s = "child";
n = 3;
}
else
{
s = "parent";
n = 7;
}
int i = 0;
for (; i < n; i++)
{
printf("s=%s,pid=%d,ppid=%d\n", s, getpid(), getppid());
sleep(1);
}
exit(0);
}
  1. fork写时拷贝技术
    • 物理内存、逻辑内存:程序内存在物理内存中空间是不连续的,在逻辑内存中不是连续的,一般采用页表进行映射,一般情况一页的大小为4K
    • 页表:逻辑内存到物理内存的映射关系
    • 由于两者采用页表进行映射,所以可以使不同进程的逻辑页映射到相同的物理页
      • 在进行进程复制时,若对物理页完全复制,则在物理内存中会出现较多重复内容(有的页数据只读,并不修改),会浪费机器性能,所以采用写时拷贝技术
      • 某页数据需要进行修改,则将该页复制到其他页上,修改逻辑页表,改变映射关系
  2. 逻辑地址与物理地址
    • 程序中看到的地址一般为逻辑地址
    • 在32位中最大内存4G
      • 高位1G: 内核使用
      • 低位3G: 用户使用
    • 相同逻辑地址可能不是相同物理地址
  3. 僵死进程(defunct)
    • 当子进程先于父进程结束,父进程没有获取子进程的退出码,此时子进程变成僵死进程
    • 在lunix中子进程退出后,程序实体会被去除,但PCB会被保留,父进程从子进程的PCB中可以获取子进程的退出码
    • 避免产生僵死进程
      • 父进程调用wait()方法获取子进程的退出码,此时会阻塞
      • 父进程先结束:在lunix中父进程结束后子进程如果没结束被称为孤儿进程,会被分配一个父进程(init进程),init进程会收养其他孤儿进程,在子进程结束后获取子进程退出码
      • 退出码左移8位后的数据是wait返回数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char *argv[], char *envp[])
{
char *s = NULL;
int n = 0;

pid_t id = fork();
assert( id != -1);
if(id == 0)
{
s = "child";
n = 3;
}
else
{
s = "parent";
n = 5;
int val = 0;
printf("waid pid:%d\n", wait(&val));
if(WIFEXITED(val))
{
printf("exitId is:%d\n", WEXITSTATUS(val));
}
}
int i = 0;
for (; i < n; i++)
{
printf("s=%s,pid=%d,ppid=%d\n", s, getpid(), getppid());
sleep(1);
}
exit(2);
}