gcc编译四个过程
预处理
头文件包含、宏替换、条件编译、删除注释
gcc -E
该步骤生成的文件后缀 .i
编译
语法检查、将预处理的文件编译成汇编文件
gcc -S
该步骤生成的文件后缀 .s
汇编
将汇编文件处理成二进制文件
gcc -c
该步骤生成的文件后缀 .o
链接
整合二进制文件、相关库函数、启动代码生成可执行文件,main函数是由启动代码调用的,程序是从启动代码开始运行的。
静态链接,指调用ld/collect2链接程序,将所有的.o中的机器指令整合到一起,然后保存到可执行文件中。
动态链接,在编译的时候只留下调用接口(函数第一条指令的地址),当程序真正运行的时候,才去链接执行,动态链接这件事不是在编译时发生的,是在程序动态运行时发生。
比如程序中调用printf函数,这个函数基本都是动态库提供的,程序编译后代码里面是没有printf函数代码的,只有printf这个接口,当程序运行起来后,再去动态链接printf所在的动态库,那么程序就能调用printf函数。
Linux默认的动态库搜索路径/usr/lib
gcc
该步骤生成的文件后缀 .out
参考自
动态空间的申请与释放
//动态从堆区空间申请
int *p = (int *)malloc(n*sizeof(int));
if(p == NULL)
{
perror("malloc");
exit(-1);
}
// 空间的释放
if(p != NULL)
{
// 释放指向的内存空间
free(p);
// 取消对以释放空间的指向
p = NULL;
}
进程相关
进程创建
进程是系统资源分配的最小单位,是正在运行的,且占有内存空间。
Linux环境中创建进程可通过调用fork() / vfork()函数,在一个已经存在的进程中创建一个新的子进程,它拷贝了父进程的地址空间,包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等,子进程仅独有其进程号、计时器等,因此使用fork()创建的进程的代价是较大的。
父子进程的地址空间相同,但相互独立。
子进程从fork()语句后开始执行。
但在进程中使用exec函数族时,exec中调用执行的程序会替换当前进程。
#include
#include
...
pid_t pid = fork();
if(pid < 0) // 创建失败
{
perror("fork");
_exit(-1);
}
if(pid == 0) // 子进程
{
...
}
else if (pid > 0) // 父进程,返回的pid是子进程的id
{
...
}
进程通信——管道
管道是内核提供的一段内存(队列),这段内存抽象成文件,通过访问文件描述符的形式,来读写这块内存中的数据。
单向通信,半双工
面向字节流
内置同步互斥机制。互斥:当多个进程一起读时会,读到完整数据或者读不到数据。同步:管道为空,读阻塞;管道满了,写阻塞。
所有引用这个管道的进程都销毁,管道才释放。真正释放的只是管道在内核中对应的这段内存,这个内存才是管道的本质
无名管道
仅限于具有“血缘关系”的进程间(如父子进程)的通信,因为这类进程在创建时拷贝了父进程的地址空间,其中包含了以打开的文件描述符,故可以打开以在父进程中创建的无名管道,进行相互通信。
// 创建数组存储管道的文件描述符,fd[0]用于读, fd[1]用于写
int fd[2];
// 创建并打开管道
pipe(fd);
pid_t pid = fork();
if(pid == 0)
{
// 可在子进程中读取管道信息
char buf[128] = "";
read(fd[0], buf, sizeof(buf));
}
if(pid > 0)
{
// 可在父进程中向管道写信息
write(fd[1], "hello pipe", strlen("hello pipe"));
}
命名管道
相互通信的进程可通过管道的名字(即文件名),打开管道进行读/写
一般在读端与写端都分别创建同一命名管道(当要创建文件存在时,文件不会重复再创建),因为无法确定实际运行中,哪端的代码先运行。若仅在一端创建管道文件,而是另一端先运行到open管道文件的代码时,会因找不到该文件而报错。通过读写两端分别创建同一命名管道文件,可以确保无论哪端先执行到open代码,都能正常打开管道。
读端进程
// 创建一个有名管道,并赋予相关权限
mkfifo("fifo_demo", 0777);
// 以只读的方式打开创建的有名管道
int fd = open("fifo_demo", O_RDONLY);
char buf[128] = "";
read(fd, buf, sizeof(buf));
printf("读取到的数据为:%s\n", buf);
// 关闭文件描述符fd
close(fd);
写端进程
// 创建一个有名管道
mkfifo("fifo_demo", 0777);
// 以只写的方式打开创建的有名管道
int fd = open("fifo_demo", O_WRONLY);
// 发送消息
write(fd, "hello fifo", strlen("hello fifo"));
// 关闭文件描述符fd
close(fd);
文件描述符符重定向
if(pid == 0) // 子进程
{
// 由于grep仅从输入设备0中读取信息
// 所以需要将文件描述符1重定向到fd[0],使grep从无名管道中读取结果
dup2(fd[0], 0);
// 执行grep命令
execlp("grep", "grep", "ps", NULL);
}
else if (pid > 0) // 父进程
{
// 由于ps仅向输出设备1中写结果
// 所以需要将文件描述符1重定向到fd[1],使ps向无名管道中输出结果
dup2(fd[1], 1);
// 执行ps命令
execlp("ps", "ps", "-elf", NULL);
}