Linux 3 系统编程

Linux 系统编程 三day

1.Makefile 介绍

多个点c文件难以用gcc编译 故此出现了项目管理器

自动化编译

 sudo apt install make 

 

2.Makefile 语法规则
(1).一条规则
     目标:依赖文件列表
    命令列表
  test:test1 test2
    echo "test"
 test1:
    echo "test1"
 test2:
    echo "test2"
(2).make 命令格式

make是一个命令工具,它解释Makefile 中的指令(应该说是规则)。

make [ -f file ] [ options ] [ targets ]

 1.[ -f file ]:
 
    make默认在工作目录中寻找名为GNUmakefile、makefile、Makefile的文件作为makefile输入文件
    -f 可以指定以上名字以外的文件作为makefile输入文件
 
 l
 
 2.[ options ]
 
    -v: 显示make工具的版本信息
    -w: 在处理makefile之前和之后显示工作路径
    -C dir:读取makefile之前改变工作路径至dir目录
    -n:只打印要执行的命令但不执行
    -s:执行但不显示执行的命令
 
 3.[ targets ]:
 
    若使用make命令时没有指定目标,则make工具默认会实现makefile文件内的第一个目标,然后退出
    指定了make工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)。

 

插入 make filesh例子
 最简单的Makefile
 test:test.c add.c sub.c mul.c div.c
        gcc test.c add.c sub.c mul.c div.c -o test
 第二个版本Makefile
 test.o:test.c
    gcc -c test.c
 add.o:add.c
    gcc -c add.c
 sub.o:sub.c
    gcc -c sub.c
 mul.o:mul.c
    gcc -c mul.c
 div.o:div.c
    gcc -c div.c
     
 该方式可以避免重复编译
3.变量
 //1.自定义变量
 k=test.o add.o sub.o mul.o div.o
 
 test: $(ak)
         gcc $(ak)  -o test
 test.o::test.c
         gcc -c test.c
 add.o::add.c
         gcc -c add.c
 sub.o::sub.c
         gcc -c sub.c
 mul.o::mul.c
         gcc -c mul.c
 div.o::div.c
         gcc -c div.c
 ~                
 ///2.系统定义变量
     除了使用用户自定义变量,makefile中也提供了一些变量(变量名大写)供用户直接使用,我们可以直接对其进行赋值。
     CC = gcc #arm-linux-gcc
     CPPFLAGS : C预处理的选项 如:-I
     CFLAGS: C编译器的选项 -Wall -g -c
     LDFLAGS : 链接器选项 -L -
 //3.自动变量
     $@: 表示规则中的目标
     $<: 表示规则中的第一个条件
     $^: 表示规则中的所有条件, 组成一个列表, 以空格隔开,如果这个列表中有重复的项则消除重复项。
   注意:自动变量只能在规则的命令中中使用      
  ak=test.o add.o sub.o mul.o div.o
 cc=gcc
 te=test
 $(te) : $(ak)
         $(cc)  $(ak) -o   $(te)
 test.o::test.c
         $(cc) -c $< -o $@
 add.o::add.c
         $(cc)  -c $< -o $@
 sub.o::sub.c
         $(cc)  -c $< -o $@
 mul.o::mul.c
         $(cc)  -c $< -o $@
 div.o::div.c
         $(cc)  -c $< -o $@
 ///4.模式规则
         %.o:%.c
     $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
       objs=test.o add.o sub.o mul.o div.o
 te=test
 $(te) : $(OBjs)
         gcc $(objs) -o $(te)
 %.o:%.c//将目标文件都依次执行
         gcc -c %< -o $@
       
         

 

4.函数
 makefile中的函数有很多,在这里给大家介绍两个最常用的。
 
         wildcard 查找指定目录下的指定类型的文件
 
     src = $(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src
 
         patsubst 匹配替换
 
     obj = $(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o
 
 在makefile中所有的函数都是有返回值的。
     SRC=$(wildcard *.c)
 
 OBJS=$(patsubst %.c, %.o, $(SRC))
 TARGET=test
 $(TARGET):$(OBJS)
   cc $(OBJS) -o $(TARGET)
 %.o:%.c
     gcc -c $< -o $@

 

5.系统调用

系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。

系统调用->内核 提供给操作系统提供的接口

系统调用

我们调用系统调用的内核路径都是事先规范好的,这样是为了系统内核的安全

操作系统一般是通过软件中断从用户态切换到内核态。

 

系统调用和库函数区别

Linux 下对文件操作有两种方式:系统调用(system call)库函数调用(Library functions)

1.不需要系统调用 strcpy

2.需要调用 系统调用

例如print

1527650321383

7.IO函数的执行流程

寄存器->缓冲区->内存->硬盘

库函数带缓冲区 但是系统调用不带缓冲区

1527650554264

库函数访问文件的时候根据需要,设置不同类型的缓冲区,从而减少了直接调用 IO 系统调用的次数,提高了访问效率。

这个过程类似于快递员给某个区域(内核空间)送快递一样,快递员有两种方式送:

1)来一件快递就马上送到目的地,来一件送一件,这样导致来回走比较频繁(系统调用)

2)等快递攒着差不多后(缓冲区),才一次性送到目的地(库函数调用)

8.错误处理函数

记录系统的最后一次错误代码

errno.h定义->errno错误码

 //打开不存在文件
 #include <stdio.h>  //fopen
 #include <errno.h>  //errno
 #include <string.h> //strerror(errno)
 int main()
 {
     FILE *fp = fopen("xxxx", "r");
     if (NULL == fp)
 
    {
         printf("%d\n", errno);  //打印错误码
         printf("%s\n", strerror(errno)); //把errno的数字转换成相应的文字
         perror("fopen err");    //打印错误原因的字符串 //自动承接
         
    }
     return 0;
 }
 
errno 错误码查看 c

strerror(errno)->错误码打印

perror 输出errno自动输出 提示字符串:错误原因

错误码文件查看-

     /usr/include/asm-generic/errno-base.h
 
    /usr/include/asm-generic/errno.h

 

9.虚拟地址空间

1527650975663

 在进程里平时所说的指针变量,保存的就是虚拟地址。当应用程序使用虚拟地址访问内存时,处理器(CPU)会将其转化成物理地址(MMU)。
 MMU:将虚拟的地址转化为物理地址。
 这样做的好处在于:
    进程隔离,更好的保护系统安全运行
    屏蔽物理差异带来的麻烦,方便操作系统和编译器安排进程地址

 

10.文件描述符和相关配置

打开文件返回的就是文件描述符

1527651181126

如图 0-2 都被占用 故此文件描述符从 3开始

每个进程有文件描述符表

最大打开的文件个数

     cat /proc/sys/fs/file-max
 

设置最大个数

     ulimit -n 4096//设置个数
    ulimit -a//当前默认个数    

11.常用文件IO函数

16.1 open函数

 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 
 int open(const char *pathname, int flags);
 int open(const char *pathname, int flags, mode_t mode);
 功能:
     打开文件,如果文件不存在则可以选择创建。
 参数:
     pathname:文件的路径及文件名
     flags:打开文件的行为标志,必选项 O_RDONLY, O_WRONLY, O_RDWR
     mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
 返回值:
     成功:成功返回打开的文件描述符
     失败:-1

 

flags详细说明

必选项:

取值含义
O_RDONLY 以只读的方式打开
O_WRONLY 以只写的方式打开
O_RDWR 以可读、可写的方式打开

可选项,和必选项按位或起来

取值含义
O_CREAT 文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL 如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC 如果文件存在,则清空文件内容
O_APPEND 写文件时,数据添加到文件末尾
O_NONBLOCK 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O

mode补充说明

  1. 文件最终权限:mode & ~umask

  2. shell进程的umask掩码可以用umask命令查看

Ø umask:查看掩码(补码)

Ø umask mode:设置掩码,mode为八进制数

Ø umask -S:查看各组用户的默认操作权限

取值八进制含义
S_IRWXU 00700 文件所有者的读、写、可执行权限
S_IRUSR 00400 文件所有者的读权限
S_IWUSR 00200 文件所有者的写权限
S_IXUSR 00100 文件所有者的可执行权限
S_IRWXG 00070 文件所有者同组用户的读、写、可执行权限
S_IRGRP 00040 文件所有者同组用户的读权限
S_IWGRP 00020 文件所有者同组用户的写权限
S_IXGRP 00010 文件所有者同组用户的可执行权限
S_IRWXO 00007 其他组用户的读、写、可执行权限
S_IROTH 00004 其他组用户的读权限
S_IWOTH 00002 其他组用户的写权限
S_IXOTH 00001 其他组用户的可执行权限

16.2 close函数

 
 #include <unistd.h>
 
 int close(int fd);
 功能:
     关闭已打开的文件
 参数:
     fd : 文件描述符,open()的返回值
 返回值:
     成功:0
     失败: -1, 并设置errno

需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。

但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。

 

16.3 write函数

 
 #include <unistd.h>
 ssize_t write(int fd, const void *buf, size_t count);
 功能:
     把指定数目的数据写到文件(fd)
 参数:
     fd :  文件描述符
     buf : 数据首地址
     count : 写入数据的长度(字节)
 返回值:
     成功:实际写入数据的字节个数
     失败: - 1

 

16.4 read函数

 
 #include <unistd.h>
 
 ssize_t read(int fd, void *buf, size_t count);
 功能:
     把指定数目的数据读到内存(缓冲区)
 参数:
     fd : 文件描述符
     buf : 内存首地址
     count : 读取的字节个数
 返回值:
     成功:实际读取到的字节个数
     失败: - 1

 

阻塞和非阻塞的概念

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。

从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。

同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

【注意】阻塞与非阻塞是对于文件而言的,而不是指read、write等的属性。

以非阻塞方式打开文件程序示例:

 
 #include <unistd.h> //read
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <errno.h> //EAGAIN
 
 int main()
 {
     // /dev/tty --> 当前终端设备
     // 以不阻塞方式(O_NONBLOCK)打开终端设备
     int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
 
     char buf[10];
     int n;
     n = read(fd, buf, sizeof(buf));
     if (n < 0)
    {
         // 如果为非阻塞,但是没有数据可读,此时全局变量 errno 被设置为 EAGAIN
         if (errno != EAGAIN)
        {
             perror("read /dev/tty");
             return -1;
        }
         printf("没有数据\n");
    }
 
     return 0;
 }

 

16.5 lseek函数

 
 #include <sys/types.h>
 #include <unistd.h>
 
 off_t lseek(int fd, off_t offset, int whence);
 功能:
     改变文件的偏移量
 参数:
     fd:文件描述符
     offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
 
     whence:其取值如下:
         SEEK_SET:从文件开头移动offset个字节
         SEEK_CUR:从当前位置移动offset个字节
         SEEK_END:从文件末尾移动offset个字节
 返回值:
     若lseek成功执行, 则返回新的偏移量
     如果失败, 返回-1

 

所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。

读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。



posted @ 2023-06-14 10:48  大橘|博客  阅读(11)  评论(0)    收藏  举报