0%

Linux系统编程_基础函数

1.基本概念

这里我们只对一些概念做简单介绍,让第一次接触的读者有一个大概的映像。后续会进行详细的讲述 ##### 1.1 操作系统的核心-内核 操作系统是指完整的软件包,它包括了用来管理和分配计算机资源的核心层软件,以及附带所有标准软件工具,诸如命令行解释器、图形用户界面、文件操作工具和文件编辑器等。其中最为重要的是其核心层软件——内核:

内核:内核执行的主要任务是进程调度、内存管理、提供系统文件、创建和终止进程、对设备的访问、联网、提供系统调用应用编程接口(API)

  • 进程调度:计算机均配备一个或多个CPU以执行程序指令,Linux与其他系统如Windows一样属于抢占式多任务操作系统,即多个任务(进程)可同时驻留在CPU中,每个进程都可以获得对CPU的使用权,那么内核就得规定什么时候哪个进程占有CPU进行处理。
  • 内存管理:linux当中采用了虚拟内存管理机制,这种机制有两个优点:是使得进程与进程之间,进程与内核之间彼此隔离,一个进程无法读取或修改内核或其他进程的内存内容;是只需将进程的一部分保存在内存中,降低了每个进程对内存的需求量,使得RAM能够加载更多的进程。
  • 系统调用:内核提供了系统调用应用编程接口,这样进程可以利用内核入口点(系统调用)请求内核去执行各种任务,如epoll

注意:必须得理解并行并发,在多核CPU中可以做到并行

  • 并行:真正的同一时刻
  • 并发:同一时间周期(我们感觉是同时执行,但是计算机执行周期是毫秒单位级别的,我们感觉不到)
1.2 内核态和用户态
  • 用户态:当在用户态运行时,CPU只能访问进程中被标记为用户空间的内存,试图访问内核空间会引发硬件异常。
  • 内核态:当处于内核态时,CPU既能访问用户内存空间,也能访问内核空间内存。一个用户态要切换到内核态,唯一方法是通过中断、故障、或者陷入系统调用这样的异常。处理后返回时,又变为用户模式。
1.3 shell

shell时一种具有特殊用途的的程序,主要用于读取用户输入的命令,并执行相应的程序以响应命令

1.4 用户和组
  • 用户:系统的每一个用户都有唯一的登录名和相应的整型用户UID

  • :为了管理方便,控制对文件和其它资源的访问,将多个用户分组。每个组都有相应的组ID(GID)。

每个文件都有与之相应的用户ID和组ID,分别定义文件的属主和属组。系统根据所有权来判定用户对文件的访问权。

1.5 单根目录层级、目录、链接及文件

Linux内核维护着一套单根目录结构,以放置系统的所有文件,这与windows形成鲜明对比(windows分硬盘区映射)。Linux的目录层级的根未/,其余的所有目录均为其子孙:

1
2
3
4
5
root@trluper-virtual-machine:/home/trluper# cd /
root@trluper-virtual-machine:/# ls
bin dev lib libx32 mnt root snap sys var
boot etc lib32 lost+found opt run srv tmp
cdrom home lib64 media proc sbin swapfile usr
  • /:根目录,一般根目录下只存放目录,Linux下有且只有一个根目录。所有的东西都是从这里开始。当你在终端里输入/home,你其实是在告诉电脑,先从/(根目录)开始,再进入到home目录。
  • /bin: 这一目录中存放了供超级用户和一般用户都可以使用的命令,常用的命令ls、tar、mv、cat
  • /usr/bin: 安装的外部的命令,usr表示的是unix software source,不是user。
  • /boot:放置linux系统启动时用到的一些文件,如Linux的内核文件/boot/vmlinuz,系统引导管理器/boot/grub
  • /dev:存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,
  • /etc系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有 /etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d。
  • /home系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,~表示当前用户的家目录,~edu表示用户edu的家目录。
  • /lib内核级别,系统使用的函数库的目录,包含许多被/bin//sbin/中的程序使用的库文件。
  • /usr/lib: 系统级别,目标库文件,包括动态连接库加上一些通常不是直接调用的可执行文件的存放位置。这个目录功能类似/lib目录
  • /usr/local/lib用户级别,包含许多被/bin//sbin/中的程序使用的库文件。
  • /usr/includeC程序语言编译使用的头文件。linux下开发和编译应用程序所需要的头文件一般都存放在这里,通过头文件来使用某些库函数
  • /lost+fount:系统异常产生错误时,会将一些遗失的片段放置于此目录下。
  • /mnt、/media:光盘默认挂载点,通常光盘挂载/mnt/cdrom下,也不一定,可以选择任意位置进行挂载。
  • /opt:给主机额外安装软件所摆放的目录。
  • /proc:此目录的数据都在内存中,如系统核心,外部设备,网络状态,由于数据都存放于内存中,所以不占用磁盘空间,比较重要的目录有 /proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/* 等。
  • /root:系统管理员root的家目录。
  • /sbin、/usr/sbin、/usr/local/sbin放置系统管理员使用的可执行命令,如fdisk、shutdown、mount等。/bin不同的是,这几个目录是给系统管理员root使用的命令,一般用户只能"查看"而不能设置和使用。
  • /tmp一般用户或正在执行的程序临时存放文件的目录,任何人都可以访问,重要数据不可放置在此目录下。
  • /srv:服务启动之后需要访问的数据目录,如www服务需要访问的网页数据存放在/srv/www内。
  • /usr应用程序存放目录,/usr/bin存放应用程序,/usr/share存放共享数据,/usr/lib存放不能直接运行的,却是许多程序运行所必需的一些函数库文件。/usr/local:存放软件升级包。/usr/share/doc:系统说明文件存放目录。/usr/share/man:程序说明文件存放目录。
  • /var:放置系统执行过程中经常变化的文件,如随时更改的日志文件 /var/log,/var/log/message所有的登录文件存放目录,/var/spool/mail邮件存放的目录,/var/run程序或服务启动后,其PID存放在该目录

Linux世界里,一切皆文件。在Linux中,文件可分为以下几种:普通文件(普通文件、可执行文件、压缩文件)、目录文件、设备文件、链接文件和套接字

文件符号标识:

  • 普通文件:-
  • 目录:d
  • 字符设备:c
  • 管道:p
  • 符号链接:l
  • 套接字:s
  • 符号连结:l
  • 一般文件:f
1.6 文件IO

unix系统I/0模型具有通用性,即对任何文件采用open()、read()、write()、close()等这些系统调用的程序能够处理任何类型的文件。

stdio函数库的Ifopen()、fclose()、scanf()、print()、fputs()、fgets()等,这些函数位于系统调用open()、read()、write()、close()之上

1.7 进程

进程是正在执行的程序的实例,当我们运行一个程序时就创建了一个进程。在执行过程中,内核会将程序代码载入虚拟内存,未程序变量分配空间(Linux中分配4G的虚拟内存映射)

  • 因此在一个系统可同时运行多个进程,而每个进程都好像独占地使用硬件。

  • 对于单核CPU来说进程是并发运行,则是说一个进程的指令和另一个进程的指令是交错执行(有重叠)的。传统的系统在一个时刻只能执行一个程序,但多核处理器能同时执行多个程序,即并行。

  • 并发的实现是通过进程间切换来实现的(上下文切换),上下文切换,即保存当前进程的上下文、恢复新进程的上下文,然后将控制权传递到新进程。

  • 一个进程实际上可以由多个称为线程的执行单元组成。每个线程都运行在进程的上下文中,共享同样的代码、全局数据和堆。但它们有自己的栈,用来装载本地变量和函数调用链接信息。

特殊进程介绍: - init进程:init进程是Linux当中所有进程之父,当我们启动Linux系统时,它就已经在运行,后续的所有进程不是有init亲自创建fork(),就是尤其子孙创建的。init进程的ID号为1,以超级用户root权限运行,谁都无法杀死init进程,其任务是创建并监控系统运行过程中的一系列进程。

  • 守护进程:守护进程也称为精灵进程,守护进程在后台运行,没有控制终端供其读取或写入数据的能力,只有在系统主动关闭是他才会消失,否则一直健在。有着它特殊的作用。
1.8 静态库和共享库
  • 静态链接:静态库时一种目标库,如果一个程序引用了静态库的函数,那么链接器在解析了引用之后,会从库中抽取所需目标模块,将其复制到最重可执行目标文件中,这就是静态链接。静态链接需要更多内存和静态库版本依赖严重

  • 动态链接:共享库是一目标模块,在运行或加载时,可以加载到任意内存地址,并和一个内存中的程序链接起来,实施运行时链接。这个过程称为动态链接

1.9 内存映射

调用系统函数mmap()的进程,会在指定进程的虚拟地址空间中创建一个新的内存映射,映射有两种:

  • 文件映射:将文件的部分区域映射入调用进程的虚拟内存。映射一旦完成,对文件映射内容的访问则转化为对相应内存区域的字节操作(即将数据放入内存缓存中,避免磁盘反复I/O影响速率,这样就能加快执行效率)。注意映射不是一次性映射,而是按需自动从文件加载

  • 匿名映射:没有文件与之对应映射时,映射页面的内容会被初始化为0

1.10 进程间的通信和同步

Linux上有许多进程在运行,有些时独立的,但有一些是相互合作的,Linux提供了丰富的进程间通信(IPC)机制:

  • 信号(signal),用来表示事件的发生
  • 管道(shell当中的|操作)和FIFO,用于进程间的数据传递。
  • 套接字,提供了一台主机或是联网的不同主机所运行进程之间的数据传递(服务端----客户端)
  • 消息队列,用于进程间交换信息
  • 信号量,用来同步进程动作
  • 共享内存,运行两个及以上的进程共享一块内存,当某一进程改变了共享内存的内容,其他进程会知道。
1.11 信号

尽管信号被认为是IPC的一种,但是它运用于其他更为广泛,往往会将信号作为‘软件中断’,进程收到信号,就意味着某一事件或异常发生(如定时任务,ctrl+c中断)

信号的类型有很多,以SIGXXX标识,

接下来就正式进入Linux系统编程的学习

2. 系统调用

系统调用是操作系统提供给用户程序调用的一组“特殊”接口。用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务

相应的操作系统也有不同的运行级别,用户态和内核态

  • 运行在内核态的进程可以毫无限制的访问各种资源,
  • ** 而在用户态下的用户进程的各种操作都有着限制,比如不能随意的访问内存、不能开闭中断以及切换运行的特权级别。**

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

2.1 库函数和系统调用区别

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

  • 系统调用:系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。当运行内核代码时,CPU工作在内核态,在系统调用发生前需要保存用户态的栈和内存环境,然后转入内核态工作。系统调用结束后,又要切换回用户态。这种环境的切换会消耗掉许多时间 。

2.2 文件描述符

对文件进行相应的操作open()、close()、write() 、read()等)。打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件

程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2记录在表中。程序运行起来后这三个文件描述符是默认打开的。

1
2
3
#define STDIN_FILENO 0  //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符

Linux 中一个进程默认最多只能打开的文件是1024个。

2.3 相关文件函数(系统调用版)
2.3.1 open函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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);

int creat(const char *pathname, mode_t mode);

int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

功能:
打开文件,如果文件不存在则可以选择创建,并返回一个文件描述符
参数:
pathname:文件路径及文件名
flags:打开文件的行为标志,必选项有O_RDONLY,O_WRONLY,O_RDWR
mode:该参数只在文件不存时创建文件时有效,指新建文件时指定的文件权限

flages详细说明:

mode说明:

2.3.2 close函数
1
2
3
4
5
6
7
8
9
10
#include<unistd.h>
int close(int fd);

功能:
关闭已打开的文件
参数:
fd:文件描述符,open()返回值
返回值:
成功:0
失败:-1,并设置errno

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

2.3.3 write函数
1
2
3
4
5
6
7
8
9
10
11
12
#include<unistd.h>
sszie_t write(int fd,const void* buf,size_t count);

功能:
把指定数目的数据写到文件fd
参数:
fd:文件描述符
buf:数据首地址
count:写入数据长度
返回值:
成功:实写入数据的字节数
失败:-1
2.3.4 read函数
1
2
3
4
5
6
7
8
9
10
11
12
#include<unistd.h>
ssize_t read(int fd,void* buf,size_t count);

功能:
把指定数目的数据读到内存(缓冲区)
参数:
fd:文件描述符
buf:内存首地址
count:读取的字节数
返回值:
成功:实际读取到的字节数
失败:-1
2.3.5 lseek()函数:文件偏移量

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

示例:

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
38
39
40
41
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#define SIZE 100
int main(void)
{
int fd =-1;
int ret =-1;
const char *str="trluper"
char buf[SIZE];
fd = open("txt",O_WRONLY|O_CREAT,0644);
//写入
if(-1==fd)
{
perror("open");
return 1
}
printf("fd=%d\n",fd);
ret= write(fd,str,strlen(str));
if(-1 == ret)
{
perror("write");
return 1;
}
printf("write len:%d\n",ret);
//设置偏移量
ret=lseek(fd,7,SEEK_SET);
if(-1 == ret)
{
perror("lseek");
return 1;
}
write(fd," github",6);
//将文件位置指针指向文件开头,读取
lseek(fd,0,SEEK_SET);
memset(buf,0,SIZE);
ret= read(fd,buf,SIZE)
printf("read ret:%d buf:%s\n",ret,buf);
close(fd);

}

2.3.6 perror
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <errno.h>
//输出函数调用失败的错误消息 会在你输入的字符串后面拼接错误信息
void perror(const char *s);

const char * const sys_errlist[];
int sys_nerr;
//error是一个全局变量 通过他获得错误码
int errno;

错误码

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
#define	EPERM		 1	/* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */

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

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

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

2.5 stat()函数

stat函数在之后的编程中会经常用到,可以了解一下 struct stat结构体说明:buf传出,获取指定文件信息 使用实例1:判断文件类型(先明白如何判断,用man 2 stat查看st_mode的使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int show_file_type(struct stat* s)
{
switch(s->st_mode&S_IFMT)
{
case S_IFREG:
print("该文件是普通文件\n");
break;
case S_IFDIR:
print("该文件是目录\n");
break;
case S_IFCHR:
print("该文件是字符设备\n");
break;
case S_IFBLK:
print("该文件是块设备\n");
break;
case S_IFSOCK:
print("该文件是套接字\n");
break;
case S_IFFIFO:
print("该文件是管道\n");
break;
}
}

文件类型和权限解释:

2.6 文件描述符的复制

dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件。

2.6.1 dup()函数

2.6.2 dup2()函数

dup2函数:应用于重定向

2.7 fcnlt函数:改变打开文件的性质

fcntl函数有5种功能:

    1. 复制一个现有的描述符(cmd=F_DUPFD)
    1. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
    1. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
    1. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
    1. 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<unistd.h>
#include<fcnlt.h>

int main()
{
//等价于dup()
int new_fd=fcnlt(fd,F_DUPFD,0);
//获取文件状态标记
int flag=fcnlt(fd,F_GETFD,0)
switch(flag&O_ACCMODE)
{
case O_RDONLY:
printf("read only\n");
break;
}
if(flag&O_APPEND){
printf("append\n");
}
flag|=O_APPEND; //追加flag
fcnlt(new_fd,F_SETFT,flag); //设置状态标记
}
2.8 目录的相关函数
2.8.1 getcwd函数

getcwd函数:获取当前进程的工作目录

2.8.2 chdir函数

chdir函数:修改当前进程的路径

2.8.3 opendir函数

opendir函数:打开一个目录,相当于cd命令

2.8.4 closedir函数

2.8.5 readdir函数

readdir函数:读取目录

相关结构体说明:

d_type文件类型说明: