一、GCC和库
1.gcc指令
1.在 gcc 命令加上-E 参数,可以得到预处理文件。展开头文件
-o表示新文件名称
gcc -E hello.c -o hello.i
2.编译,变为汇编文件
gcc -S hello.i
3.汇编,得到机器码
gcc -c hello.s
4.链接
gcc hello.o -o hello
2.静态库
1.创建静态库
//先编译成可重定位目标文件
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
//利用ar工具创建静态库:ar rcs lib库名.a 所有可重定位目标文件
ar rcs libmath.a add.o sub.o
2.构建可执行文件
gcc -c main.c -o main.o
小l写库名,L写所在路径,库名不用写lib
gcc -static main.o -o main -l math -L ./
3.动态库
1.创建动态库
//先编译成可重定位目标文件(生成与位置无关的代码 -fPIC)
gcc -c add.c -o add.o -fPIC
gcc -c sub.c -o sub.o -fPIC
//使用 gcc -shared 制作动态库
gcc -shared -o libmath.so add.o sub.o
2.构造可执行文件
系统默认动态链接
gcc main.c -o main -l math -L ./
3.查看连接了哪些动态库
ldd main
4.配置动态库环境变量
- 通过环境变量,终端重启后失效:
export LD_LIBRARY_PATH=/home/lsr/桌面
- 写入终端配置文件.bashrc,永久生效
vim ~/.bashrc
写入export LD_LIBRARY_PATH=/home/lsr/桌面
. .bashrc 或 source .bashrc 或 重启终端 --> 让修改后的.bashrc生效。
动态库与静态库区别是重点
4.makefile脚本语言
makefile写在文件夹名为makefile的文件中,也可以指定文件名
make -f lib.mk
用make指令运行,-n查看make将要执行的命令
make
make -n
make clean 执行文件中的伪命令
定义变量,makefile变量为字符替换,$()使用变量
cc=gcc;
$(cc) main.c -o main
常用函数,获取当前目下符合条件的文件(字符串形式)
src=$(wildcard *.c)
替换字符串,将.c文件替换为.o
obj= ( p a t s u b s t (patsubst %.c,%.o, (patsubst(src))
三个系统变量
$@ 目标
$^ 所有依赖条件
$< 第一个依赖条件,在模式规则中依次取出条件
当文件与命令同名造成干扰,声明这是一个伪目标
.PHONY:clean
最终目标默认为第一个命令,也可以指定目标
ALL main
编译文件常规版
1 src=$(wildcard *.c)
2 obj=$(patsubst %.c,%.o,$(src))
3 ALL:main:add.o sub.o main.o
4 gcc main.o add.o sub.o -o main
5 main.o:main.c
6 gcc -c main.c -o main.o
7 add.o:add.c
8 gcc -c add.c -o add.o
9 sub.o:sub.c
10 gcc -c sub.c -o sub.o
11 clean:
12 rm -rf *.o
采用模式替换版
1 ALL:main
2 src=$(wildcard *.c)
3 obj=$(patsubst %.c,%.o,$(src))
4 main:$(obj)
5 gcc $^ -o $@
6 %.o:%.c
7 gcc -c $< -o $@
8 .PHONY:clean main
9 clean:
10 rm -rf *.o
5. io函数
#include<fcntl.h>
#include<sys/stat.h> 储存宏定义
三个文件描述符,0标准输入,1标准输出,2错误输出
1.open
三种打开方式
- O_RDONLY 只读方式
- O_WRONLY 只写方式
- O_RDWR 读写方式
可选模式组合:
1) O_APPEND: 把写入数据追加在文件的末尾。
2) O_TRUNC: 打开文件时把文件长度设置为零,丢弃已有的内容。
3) O_CREAT: 创建文件
4) O_EXCL: 与O_CREAT一起使用,不存在文件才创建
使用实例,返回文件描述符,失败返回-1
可选模式用 | 连接
0644中的0代表8进制
int fd=open("./test.tXt",O_RDWR | O_TRUNC);
int fd=open("./test.tXt",O_RDWR | O_CREAT,0664);
2.read
读取前n个字节文件到buf中
返回读到的字节数,失败返回-1
char buf[1024];
int rr=read(fd,buf,10);
接收系统输入,类似于scanf
int d=read(0,buf,10);
printf("%s\n",buf);
3.write
写入buf的前n个字节,返回写入的字节数
int wr = write(fd,buf,10);
4.close
关闭文件,成功返回0,失败返回-1
int c=close(fd);
5.打印错误信息
#include<errno.h>
返回根据错误号映射的字符串
#include <string.h>
printf(“open error:%s\n”,strerror(errno));
#include <stdio.h>
perror(“open error”);
6.lseek光标偏移
#include<sys/types.h>
#include <unistd.h>
1)SEEK_SET: 是一个绝对位置(从文件的头位置开始计算)。
2)SEEK_CUR: 是相对于当前位置的一个相对位置(从文件的当前位置开始计算)。
3)SEEK_END: 是相对于文件尾的一个相对位置(从文件的末尾位置开始计算)。
移动光标到相对位置,返回值为到文件开头的距离
int l=lseek(fd,-5,SEEK_CUR);
扩展文件大小
移动光标到文件末尾后500个字节,然后写入“\0“
注意文件要有写权限
lseek(fd, 500, SEEK_END);
write(fd, "\0", 1);
直接调用函数扩展,底层还是调用lseek,文件也要有写权限
ftruncate(fd,1000); 扩展文件大小到1000,文件要有写权限
7.dup建立新文件标识符
#include <unistd.h>
给文件增加一个文件标识符,原有标识符并不会消失,也指向当前文件
返回一个系统自动生成的最小可用文件标识符,指向fd文件
int newfd=dup(fd);
指定fd的新标识符为10
dup2(fd,10);
8.main函数参数
argc是命令行参数的数量,argv是具体的参数。
./main,也占用一个参数
./main aaa bbb ccc
argc=4,
argv内含有:./main,aaa,bbb,ccc
9.stat显示文件信息
把文件信息存到结构体中
stat访问软连接是访问它所连接的文件,lstat访问的是软连接本身
struct stat sb;
stat("./main.c",&sb);
prtintf("%d",sb.st_size);
6. 进程
1. fork 创建进程
创建进程,但运行的还是当前程序,从创建行开始运行
返回子进程的进程号
子进程返回0,父进程返回子进程号,失败返回-1
int pid=fork();
获取当前进程号
getpid();
获取当前父进程号
getppid();
使程序休眠(单位秒)
sleep(1);
通过pid是否为0,判断子进程,为0就是子进程
int main(int argc, char* argv[])
{
int pid=fork();
if(pid==0){
printf("chiled%d,%d",getpid(),getppid());
}
else{
printf("parennt%d,%d",getpid(),getppid());
}
}
循环创建多个进程,不加break会创建出2^n个进程
for(int i=0;i<3;i++){
int pid=fork();
if(pid==0){
break;
}
}
2. exit 结束进程
结束当前进程,并驯如结束信息
exit(111)
在终端查看结束信息
echo $?
3. execl 执行其他程序
执行mian,进程号不变,如果execl运行成功,当前程序execl行下面的代码都不会被运行
第一个参数为路径,后几个参数为朱函数参数,所以第二个参数要写main(对应的第一个主函数参数)
execl("./main",“main”);
利用写绝对路径的方式执行系统指令,系统指令都在/bin下
execl("/bin/ls",“ls”,"-l","-h");
execlp执行系统命令
execlp(“ls”,“ls”,"-l","-h");
4. 打印进程情况
打印所有进程信息
ps -aux
搜索进程
ps -aux | grep 进程名
5. wait函数
僵尸进程:子进程死后没有被父进程回收,子进程残留资源(PCB)存放于内核中,会占用内存资源
孤儿进程:父进程先于子进程结束,子进程的父进程变为init进程(1)
1.wait函数
返回值为释放的进程号,没有子进程返回-1,一个wait函数只能释放一个进程,如有多个子进程需要循环释放
会阻塞进程,直到子进程结束
status为传出参数,程序正常结束会记录返回值(和exit()括号中的东西),不正常结束时会记录被哪个信号终止
#include <sys/types.h>
#include <sys/wait.h>
int status;
int wr = wait(&status);
2.waitpid
第一个参数
大于0 回收指定 ID 的子进程
0 回收和当前调用 waitpid 一个组的所有子进程
-1 回收任意子进程(相当于 wait)
小于 -1 回收指定进程组内的任意子进程
阻塞回收指定进程
int wr = waitpid(1023,&status,0);
WNOHANG不阻塞回收指定进程,如果子进程还没有结束返回0
int wr = waitpid(1023,&status,WNOHANG);
3.查看进程死亡信息
查看所有信号,linux中所有进程都是被信号终止的
kill -l
查看退出信息
WIFEXITED(status)//为真→ 进程正常结束
WEXITSTATUS(status)//如上宏为真,使用此宏 → 获取进程返回值 (exit的参数)
if(WIFEXITED(status)){ //如果正常退出
printf("%d",WEXITSTATUS(status)); //打印返回值
}
else if(WIFSIGNALED(status)){ //被信号终止
printf("%d",WTERMSIG(status)); //打印相应信号
}
6. 管道(pipe)
1.匿名管道
只能用于有亲缘关系的进程,共用一个管道
注意用写端就关闭读端,用读端就关闭写端
建立一个int长度为2的数组,p[1]为写端,p[0]为读端
从写段写的东西,会从读端读到
① 读管道: 1. 管道中有数据,read 返回实际读到的字节数。
2. 管道中无数据:
(1)管道写端被全部关闭,read 返回 0 (像读到文件结尾)
(2)写端没有全部被关闭,read 阻塞等待
② 写管道: 1. 管道读端全部被关闭, 进程异常终止
2. 管道读端没有全部关闭:
(1)管道已满,write 阻塞。
(2)管道未满,write 将数据写入,并返回实际写入的字节数。
实例代码
int pipefd[2];
int pr = pipe(pipefd);
int pid = fork();
if(pid==0){
close(pipefd[0]);
write(pipefd[1],"Hello World!",12);
}
if(pid > 0){
close(pipefd[1]);
read(pipefd[0],buf,12);
}
7. 有名管道(fifo)
#include <sys/stat.h>
创建管道
mkfifo fifo
函数创建管道
返回值:成功返回0,出错返回-1
int mr = mkfifo("./fifo",0644);
有名管道创建成功后可以像读写文件一样使用,写入管道中内容
int fd = open("./myfifo",O_RDWR);
int i = 1;
while(1){
dprintf(fd,"%dth",i++); //写内容到对应文件
sleep(1);
}
读出管道中内容
int buf[10];
int fd = open("./myfifo",O_RDONLY);
while(1){
int rd = read(fd,buf,3);
printf("%s\n",buf);
}
8. mmap
函数原型
<sys/mman.h>
创建映射:void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
释放映射:int munmap(void *addr, size_t length);
参数:
参数addr:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
参数length映射区的长度。
参数prot:映射区域的保护方式。可以为以下几种方式的组合:
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
MAP_ANON建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
返回值:
若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。
文件权限要大于mmap权限
偏移量要为0或4096的整数倍
mmap实现在内存进行文件操作
11 #include<sys/mman.h>
12 #include<fcntl.h>
13 int main(int argc, char* argv[])
14 {
15 int fd = open("mmap.txt", O_RDWR | O_CREAT, 0644);
16 ftruncate(fd,1000); 文件要有写权限
17 void* ptr = mmap(NULL, 200, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
18 if(ptr == MAP_FAILED)
19 {
20 perror("mmap error:");
21 exit(1);
22 }
23 while(1)
24 {
25 sprintf(ptr, "-------hahaha--------");
26 printf("%s\n", ptr);
27 sleep(1);
28 }
29 munmap(ptr, 200);
30 close(fd);
31 return 0;
32 }
进程间通信,父子进程公用一个缓存区,进行读写即可完成通信
无血缘关系的进程,用同一个文件做映射(参数要相同),得到也是一块相同的内存区(但系统检测到文件已经建立映射时,会直接返回地址,并不会创建新的映射)
血缘关系文件可以用匿名映射,无血缘关系的不能
11 #include<sys/mman.h>
12 #include<fcntl.h>
13 int main(int argc, char* argv[])
14 {
15 int fd = open("mmap.txt", O_RDWR);
16 void* ptr = mmap(NULL, 200, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
17 int pid = fork();
18 if(fork == 0)
19 {
20 sprintf(ptr, "++++happy++++");
21 }
22 else
23 {
24 sleep(1);
25 printf("%s\n",ptr);
26 }
27 close(fd);
28 munmap(ptr, 200);
29 return 0;
30 }
匿名映射
用MAP_ANON,有些系统可能不支持
void* ptr = mmap(NULL, 200, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
用/dev/zero文件
int fd = open("/dev/zero", O_RDWR);
void* ptr = mmap(NULL, 200, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
9. 创建线程
gcc thread1.c -o thread1 -lpthread
编译含有线程的文件时,要在末尾添加-lpthread指令
#include<pthread.h>
void* fun(void* arg){ //线程执行的函数
int a = (int)arg;
while(1)
{
printf("child thread\n,%d",a);
sleep(1);
}
}
int main(int argc, char* argv[])
{
int a = 10;
pthread_t thread;
int creat = pthread_create(&thread, NULL, fun, (void*)a);
if(creat < 0)
{
printf("error: %s", strerror(creat));
exit(1);
}
while(1)
{
printf("main thread\n");
sleep(1);
}
return 0;
}