Linux C语言环境变量(二)

一、系统调用
UNIX/Linux系统绝大部分功能都是通过系统调用实现,比如:open/close…
UNIX/Linux把系统调用都封装成了C函数的形式,但他们并不是标准C的一部分。
标准库中的函数绝大部分时间都工作在用户态,但部分时间也需要切换到内核(进行了系统调用),比如:fread/fwirte/malloc/free。
我们自己所编写的代码也可以直接调用系统接口进入内核态(进行系统调用),比如:brk/sbrk/mmap/munmap
系统调用的功能代码存在于内存中,接口定义在C库中,该接口通过系统中断实现调用,而不是普通函数进行跳转。
注意:从用户态切换到内核态或从内核态返回到用户态都会消耗时间。
time a.out
real 0m0.137s 总执行时间=用户态+内核态+切换消耗
user 0m0.092s 用户态执行时间
sys 0m0.040s 内核态执行时间
strace 程序 可以跟踪系统调用

二、一切皆文件
在UNIX/Linux系统下,几乎所有资源都是以文件形式提供了,所以在UNIX/Linux系统下一切皆文件,操作系统把它的服务、功能、设备抽象成简单的文件,提供一套简单统一的接口,这样程序就可以像访问磁盘上的文件一样访问串口、终端、打印机、网络等功能。
大多数情况下只需要 open/read/write/ioctl/close 就可以实现对各种设备的输入、输出、设置、控制等。
UNIX/Linux下几乎任何对象都可以当作特殊类型的文件,可以以文件的形式访问。
目录文件 里面记录的是一些文件信息,相关条目。
设备文件 在系统的/dev目录下存储了所有的设置文件
stderr
stdin
stdout
普通文件
链接文件
管道文件
socket文件

三、文件相关系统调用
open 打开或创建文件
creat 创建文件
close 关闭文件
read 读文件
write 写文件
lseek 设置文件读写位置
unlink 删除链接
remove 删除文件

四、文件描述符
文件描述符是一个非负整数,表示一个打开的文件,由系统调用open/creat/socket返回值。
为什么使用文件描述符而不像标准库那使用文件指针?
因为记录文件相关信息的结构存储在内核中,为了不暴露内存的地址,因此文件结构指针不能直接给用户操作,内核中记录一张表,其中一列是文件描述符对应一列文件结构指针,文件描述符就相当于获取文件结构指针的下标。
内核中已经有三个已经打开的文件描述符,它们的宏定义在unistd.h:
stdin 0 STDIN_FILENO
stdout 1 STDOUT_FILENO
stderr 2 STDERR_FILENO
0,1,2 都代表的是终端

五、open/creat/close
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
功能:打开文件
pathname:文件的路径
flags:打开的权限
	O_RDONLY, 只读
	O_WRONLY, 只写
	O_RDWR,读写
   	O_NOCTTY, 当打开的是终端设备文件,不要把该文件当作主控终端。
   	O_TRUNC,清空
   	O_APPEND,追加
 返回值:文件描述符

int open(const char *pathname, int flags, mode_t mode);
功能:创建文件
pathname:文件的路径
flags:打开的权限
	O_CREAT,  文件不存在则创建
   	O_EXCL,如果文件存在,则创建失败
mode:设置文件的权限
	S_IRWXU  00700 read,write and execute permission
	S_IRUSR  00400 user has read permission
	S_IWUSR  00200 user has write permission
	S_IXUSR  00100 user has execute permission

    S_IRWXG  00070 read,write and execute permission
	S_IRGRP  00040 group has read permission
    S_IWGRP  00020 group has write permission
	S_IXGRP  00010 group has execute permission
	
	S_IRWXO  00007 others have read, write and
	S_IROTH  00004 others have read permission
	S_IWOTH  00002 others have write permission
	S_IXOTH  00001 others have execute  permis‐sion
	
int close(int fd);
功能:关闭打开的文件

六、read/write
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存
fd:文件描述符,open函数的返回值
buf:数据的存储位置
count:读取的字节数
返回值:成功读取到的字节数

ssize_t write(int fd,const void *buf, size_t count);
功能:把数据写入到文件
fd:文件描述符,open函数的返回值
buf:要写入的数据内存首地址
count:要写入的字节数
返回值:成功写入的字节数
注意:如果把结构体以文本形式写入到文件,需要先把结构体转换成字符串。

七、lseek
off_t lseek(int fd, off_t offset, int whence);
功能:设置文件位置指针
返回值:文件位置指针所在的位置 功能类似ftell

练习1:实现一个Linux系统下的计算文件大小的函数,使用系统调用完成。
练习2:实现带覆盖检查的cp命令。

八、dup/dup2
int dup(int oldfd);
功能:复制文件描述符,操作系统会从末的文件描述符中选择一个返回。
oldfd:被复制的文件描述符

int dup2(int oldfd, int newfd);
功能:复制指定的文件描述符,如果newfd已经被使用,则先关闭,再复制。

九、标准IO与系统IO比较
练习3:分别使用标准IO与系统IO随机写入1000000个整数到文件,比较哪种更快,为什么?
因为标准IO使用了缓冲技术,当数据写入时并没有立即把数据交给内核,而是先存放在缓冲区中,当缓冲区满时,会一次性把缓冲中的数据交给内核写到文件中,这样就减少内核态与用户态的切换次数。
而系统IO每写一次数据就要进入一次内核态,这样就浪费了大量时间进行内核态与用户态的切换,因此用时更长。
如果为系统IO,设置更大的缓冲区,它会比标准IO更快。

real	0m0.113s 标准IO 
user	0m0.100s
sys		0m0.008s

real	0m2.845s 系统IO
user	0m0.268s
sys		0m2.572s

real	0m0.064s 设置过缓冲区的系统IO
user	0m0.060s
sys		0m0.000s
一、sync/fsync/fdatasync
1、硬盘上一般都有一些缓冲区以此来提高数据的写入效率,操作系统写入数据其实只是写入缓冲区,直到缓冲区满,才排队写入硬盘中。
2、这种操作降低的写入的次数,但是提高了数据写入的延时,导致缓冲区中的数据与磁盘中的内容不同步。
void sync(void);
功能:把所有缓冲区中的数据全部同步到磁盘
注意:只是发面将数据同步到磁盘的命令,并不等待执行完比才返回,而是命令发布后立即返回。

int fsync(int fd);
功能:指定fd文件的缓冲区数据同步到磁盘,只针对一个文件,数据同步到磁盘后才返回。

int fdatasync(int fd);
功能:指定fd文件的缓冲区数据同步到磁盘,但仅是文件的数据并不同步文件属性。

二、fcntl
int fcntl(int fd, int cmd, … /* arg */ )
cmd:操作指令,不同的操作指令决定,后续参数的个数和类型
注意:这是个变长参数的函数

int fcntl(int fd, int cmd, long newfd)
cmd:F_DUPFD
功能:复制文件描述符,与fd操作同一个文件
返回值:如果newfd没有使用则返回newfd,如果newfd已经被占用,则返回一个不小于newfd的文件描述符。
练习1:利用fcntl,实现dup和dup2的功能。

int fcntl(int fd, int cmd,void/long)
功能:设置或获取文件描述符标志
cmd:
	F_GETFD void
	返回值:0新进程保持打开状态,1新进程中关闭该文件描述符。
	F_SETFD long
	目前只能设置FD_CLOEXEC标志位。
	返回值:成功返回0,失败返回-1



int fcntl(int fd, int cmd,void/long)
功能:获取文件状态标志(此文件打开的权限以及打开的方式)。
cmd:
	F_GETFL void
		O_CREAT,O_EXCL,O_NOCTTY,O_TRUNC 不能获取到
		返回值:带有文件状态标志的int类型变量,需要与各标志相与得到。
	F_SETFL long
		仅能设置的有
		O_APPEND,O_ASYNC,O_DIRECT,O_NOATIME,O_NONBLOCK
		返回值:成功返回0,失败返回-1
		
int fcntl(int fd, int cmd,struct* flock);
功能:为文件加锁,能锁整个文件,或锁一部分内容。
	一旦进程结束,锁会自动解锁。
cmd:
	F_GETLK	获取锁的信息
	F_SETLK 设置文件锁
	F_SETLKW 测试锁
	注意:加锁并不能让其它进程打不开文件或不能操作,而是使用者都要遵守锁的约定,确保文件不混乱(劝谏锁)。
struct flock 
{
	short l_type;// 锁的类型 F_RDLCK,F_WRLCK, F_UNLCK
	short l_whence;// SEEK_SET, SEEK_CUR, SEEK_END
    off_t l_start;// 锁的偏移值
    off_t l_len;// 锁的长度,为0表示锁到文件尾
    pid_t l_pid;// 加锁的进程号
};
读锁 与 读锁 成功
读锁 与 写锁 失败
写锁 与 写锁 失败

三、stat/fstat/lstat
#include <sys/stat.h>
功能:用来获取文件属性,返回值:成功返回0,失败返回-1
int stat(const char *path, struct stat *buf);
path:需要文件路径
int fstat(int fd, struct stat *buf);
fd:需要打开后的文件描述符
int lstat(const char *path, struct stat *buf);
stat/fstat会跟踪链接目标,而lstat不跟踪链拉目标。

struct stat {
	dev_t st_dev; // 设备ID
	ino_t st_ino; // 节点号
    mode_t st_mode; // 文件类型和权限
    nlink_t st_nlink; // 硬链拉数
    uid_t st_uid; // 用户ID
    gid_t st_gid; // 组ID
    dev_t st_rdev; // 特殊设备ID
    off_t st_size; // 文件的总字节数
    blksize_t st_blksize; // IO块数
    blkcnt_t st_blocks; // 占用块(512字节)数
    time_t st_atime; // 最后访问时间
    time_t st_mtime; // 最后修改时间
    time_t st_ctime; // 最后文件属性修改时间

S_ISREG(m)  测试是否是标准文件
S_ISDIR(m)  目录
S_ISCHR(m)  字符设备文件
S_ISBLK(m)  块设备文件
S_ISFIFO(m) 管道文件
S_ISLNK(m)  软链接文件
S_ISSOCK(m) socket文件


S_IFMT     0170000   获取文件类型出错
S_IFSOCK   0140000   socket文件
S_IFLNK    0120000   软链接文件
S_IFREG    0100000   标准文件
S_IFBLK    0060000   块设备文件
S_IFDIR    0040000   目录
S_IFCHR    0020000   字符设备文件
S_IFIFO    0010000   管道文件
S_ISUID    0004000   set UID bit
S_ISGID    0002000   set-group-ID bit (see below)
S_ISVTX    0001000   sticky bit (see below)

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     其它用户的执行
练习2:使用stat函数,获取文件的属性,显示出文件的类型和权限,参考ls -l的第一列内容。

struct tm *localtime(const time_t *timep);
功能:使用一个记录秒数据的变量,获取当前时间
struct tm {
	int tm_sec;         /* seconds */
	int tm_min;         /* minutes */
	int tm_hour;        /* hours */
	int tm_mday;        /* day of the month */
	int tm_mon;         /* month */
	int tm_year;        /* year */
	int tm_wday;        /* day of the week */
	int tm_yday;        /* day in the year */
	int tm_isdst;       /* daylight saving time */

练习3:获取文件的最后访问时间,最后修改时间,最后文件属性修改时间。

四、access
int access(const char *pathname, int mode);
功能:测试当前用户对文件的访问权限,或者文件是否存在
pathname:文件路径
mode:
F_OK 是否存在
R_OK 是否有读权限
W_OK 是否写权限
X_OK 是否有执行权限
返回值:0表示有,-1表示没有

五、umask
mode_t umask(mode_t mask);
功能:设置并获取权限屏蔽码,功能与umask命令一样,一旦设置成功新创建文件就不会具有mask中的权限。
返回值:旧的权限屏幕码
注意:该权限屏蔽码只对当前进程有效,进程结束后就会变成默认的。

六、chmod/fchmod
功能:修改文件的权限,返回值:成功返回0,失败返回-1
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
注意:它们不受权限屏蔽码的干扰

七、truncate/ftruncate
功能:修改文件的大小,如果文件的,成功返回0,失败返回-1
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

八、link/unlink/remove/rename
返回值:成功返回0,失败返回-1
int link(const char *oldpath, const char *new‐path);
功能:创建硬链拉文件,硬链接指向的是文件的内存,因此当链接目标被删除后,依然可以访问文件的内容。

int unlink(const char *pathname);
功能:删除硬链拉文件
注意:普通文件就是硬链接数量为1的文件,当把一个文件的硬链接数删除到0个时,这个文件就被删除了。

int remove(const char *pathname);
功能:删除文件,该函数是标准库中的删除文件函数,底层调用了unlink系统调用。

int rename(const  char *oldpath, const char *new‐path);
功能:文件重命名

九、symlink/readlink
int symlink(const char *oldpath, const char *new‐path);
功能:创建软件链拉(目录文件只能创建软链接)
oldpath:链接目标
new‐path:链接文件
返回值:成功返回0,失败返回-1

ssize_t  readlink(const  char  *path,  char  *buf,size_t bufsiz);
功能:读取软链接文件的内容而非链接目标(open打开软链接文件是打开的是目标文件)
path:链接文件的路径
buf:读取数据的存储位置
bufsiz:读取多少个字节
返回值:成功读取到的字节数

十、mkdir/rmdir
返回值:成功返回0,失败返回-1
int mkdir(const char *pathname, mode_t mode);
功能:创建目录,目录一定要有执行权限,否则无法进入
int rmdir(const char *pathname);
功能:删除空目录(只能删除空目录)

十一、chdir/fchdir/getcwd
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作目录,工作目录是指当不加路径信息时,创建/打开时从那个目录下查找,工作目录默认是程序所在的目录。

int chdir(const char *path);
功能:修改进程的工作目录
返回值:成功返回0,失败返回-1

int fchdir(int fd);
功能:修改进程的工作目录
fd:被open打开的目录文件的fd
返回值:成功返回0,失败返回-1

十二、opendir/fdopendir/closedir/readdir/rewinddir/telldir/seekdir
#include <dirent.h>
DIR *opendir(const char *name);
功能:打开一个目录流
返回值:目录流(链表)

DIR *fdopendir(int fd);
功能:使用文件描述获取目录流,fd必须是目录文件的。

struct dirent *readdir(DIR *dirp);
功能:从目录流中读取一个文件结点信息
struct dirent {
	ino_t          d_ino; // i节点号
	off_t          d_off; // 下一个文件结点信息的偏移量
	unsigned short d_reclen; // 当文件结点信息的长度
	unsigned char  d_type; // 文件类型     
	char           d_name[256]; // 文件的名字

DT_BLK      This is a block device.
DT_CHR      This is a character device.
DT_DIR      This is a directory.
DT_FIFO     This is a named pipe (FIFO).
DT_LNK      This is a symbolic link.
DT_REG      This is a regular file.
DT_SOCK     This is a UNIX domain socket.
DT_UNKNOWN  The file type is unknown.

void rewinddir(DIR *dirp);
功能:把目录流的位置指针调整到开头

long telldir(DIR *dirp);
功能:获取当前目录流的位置指针在第几个文件结点

void seekdir(DIR *dirp, long offset);
功能:调整当前目录流的位置指针
offset:根据开头位置指针,进行偏移
一、信号的基本概念
1、中断:中止(注意不是终止)当前正在执行的任务,转而执行其它任务(可能返回也可能不返回),中断分为硬件中断(硬件设备产生的中断)和软件中断(其它程序产生的中断)。
2、信号:是一种软件中断,提供了一种异步执行任务的机制。
3、常见的信号
	SIGINT(2)	Ctrl+c 产生的信号
	SIGQUIT(3)	Ctrl+\ 产生的信号
	SIGABRT(6)  调用abort函数,产生此信号
	SIGFPE(8)	浮点数例外,除以0、浮点溢出等
	SIGKILL(9) 	不能被捕获或忽略,常用于杀死进程
	SIGSEGV(11) 段错误信号,非法访问内存产生的信号
	SIGTSTP(20) Ctrl+z 生产的信号
	SIGCHLD(17) 子进程状态改变信号
	注意:在终端中执行 kill -l 可以显示出所有信号
4、不可靠信号
	建立在早期机制上的信号被称为不可靠信号,SIGHUP(1)~SIGSYS(31)。
	不支持排队可能会丢失,同一个信号产生多次,进程可能只接收到一次。
5、可靠信号
	采用新的机制产生的信号,SIGRTMIN(34)~SIGRTMAX(64).
	支持排队,不会丢失。
6、信号的来源
	硬件产生:除0,非法内存访问。
	这些异常是硬件(驱动)检查到,并通知内核,然后内核再向引发这些异常的进程发送相应信号。
	软件产生:通过kill/raise/alarm/setitmer/sigqueue函数产生。
7、信号的处理
	1、忽略
	2、终止进程
	3、终止进程并产生core文件
	4、捕获信号并处理。

二、信号的捕获
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理注册函数
signum:信号的编号,1~31 也可以是宏
handler:
SIG_IGN 忽略该信号
SIG_DEL 默认处理
函数指针
注意:在某些UNIX系统上,signal注册的函数只执行一次,执行完后就恢复成默认处理方式,如果长期使用该函数处理信号,可以在函数结束前再注册一次。
SIGKILL/SIGSTOP 既不能被捕获,也不能被处理
SIGSTOP信号会让进程暂停,当再次收到SIGCONT信号时会继续执行。
普通用户只能给自己的进程发送信号,而root可以给任何进程发送信号。

练习1:实现一个"死不掉的进程",当收到信号后,给出信号产生的原因。

三、发送信号
1、键盘
Ctrl+c SIGINT
Ctrl+\ SIGQUIT
Ctrl+z SIGTSTP
2、错误
除零 SIGFPE
非法访问内存 SIGSEGV
3、命令
kill -signum pid
ps -aux 查看所有进行
4、函数
int kill(pid_t pid, int sig);
功能:向指定的进程发送信号
pid:进程id
pid > 0 向进程号为pid的进程发送信号
pid = 0 向同组一进程组的进程发送信号
pid = -1 向所有(有权力发送信号的)进程发送信号
pid < -1 向进程号为abs(pid)的进程组发送信号
sig:信号的编号
sig值为0时,kill不会发送信号,但会进行错误检查(检查进程号或进程组id号是否存在)。

	int raise(int sig);
	功能:发向当前进程发送信号

四、休眠
int pause(void);
功能:一旦执行进程就会进入无限的休眠(暂停),直到遇到信号。
先执行信号处理函数才会从休眠中醒来。

unsigned int sleep(unsigned int seconds);
功能:休眠指定的秒数,当有信号来临时会提前醒来,提前醒来会返回剩余的秒数,或者睡够了,返回0。

五、闹钟
unsigned int alarm(unsigned int seconds);
功能:告诉内核在seconds秒之后,向当前进程发送SIGALRM信号。
返回值:如果之前设定的时间还没有到,则会重新设置(覆盖),并返回之前设置的剩余秒数。

六、信号集与信号屏蔽
信号集:信号的集合,由128位二进制组成,每一位代表一个信号。
int sigemptyset(sigset_t *set);
功能:清空信号集,把所有位设置为0。

int sigfillset(sigset_t *set);
功能:填满信号集,把所有位设置为1。

int sigaddset(sigset_t *set, int signum);
功能:往信号集中添加一个信号。

int sigdelset(sigset_t *set, int signum);
功能:从信号集中删除一个信号。

int sigismember(const sigset_t *set, int signum);
功能:判断信号集中是否有signum信号

信号屏蔽:当做一些特殊操作时会希望,有些信号来而有些信号不要来,而与设置信号忽略不同的,信号屏蔽只是暂时不来,而可以获取这段时间发生那些信号。

每个进程都有一个信号掩码(信号集),其中包括了需要屏蔽的信号,可以通过sigprocmask函数,检查、修改进程的信号掩码。

int sigprocmask(int  how,  const  sigset_t  *set,
   sigset_t *oldset);
功能:检查、修改进程的信号掩码  
how:
	SIG_BLOCK
    	设置当前信号掩码与set的并集为新的信号掩码,添加。
	SIG_UNBLOCK
        新的信号掩码是当前掩码与set补集的交集,删除。
	SIG_SETMASK
		把set当作新的信号掩码,重新设置。
set:可以为空,则获取信号掩码。
oldset:旧的信号屏幕掩码

int sigpending(sigset_t *set);
功能:获取信号屏蔽期间发生的信号,当信号屏蔽解除后就没了。

注意:在信号屏蔽期间发生的信号,无论多少次(不可靠信号),只能捕获一次。

练习:学生信息管理系统,在保存数据在加载数据时屏蔽Ctrl+c和Ctrl+\,等数据加载、保存完成后再处理该信号。

七、带附加信息的信号捕获
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
功能:向内核注册信号处理函数
signum:信号编码
act:信号处理方式
oldact:获取到此信号旧的处理方式,可以为NULL。
struct sigaction {
void (*sa_handler)(int); //简单的信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);
// 可以带附加信息的信号处理函数指针
sigset_t sa_mask; // 当执行信号处理函数时需要屏蔽的信号
int sa_flags;
SA_RESETHAND 表示信号只处理一次,然后就恢复默认处理方式。
SA_RESTART 系统调用如果被signum信号中断,自行重启
SA_NOCLDSTOP 当子进程暂时时,不用通知父进程。
SA_NODEFER 当执行信号处理函数时,不屏蔽正常的处理的信号。
SA_SIGINFO 使用第二个函数指针处理信号

	void (*sa_restorer)(void); // 保留,暂不使用

int sigqueue(pid_t pid, int sig, const union sig‐
   val value);
功能:信号发送函数,与kill不同的是可以附加一些额外数据
pid:目标进程号
sig:要发送信号
value:联合,成员可以是整数或指针

八、计时器
1、系统为每个进程维护三个计时器
ITIMER_REAL 真实计时器,程序运行的实际所用时间
ITIMER_VIRTUAL 虚拟计时器,程序运行在用户态所消耗的时间
ITIMER_PROF 实用计时器,程序在用户态在内核态所消耗的时间。
实际时间(真实计算器) = 用户时间(虚拟)+内核时间+睡眠时间

int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:给当前进程设置定时器,与alarm的区别是更精确,可以选择以那个时间段计算(选择使用那个计时器)。
which:选择使用哪些计时器
curr_value:
	struct timeval it_value; 第一次触发时钟信号所需要的时间
	struct timeval it_interval; 每一次触发时钟信号所需要的时间
	
int getitimer(int which, struct itimerval *curr_value);
功能:获取当前进程的定时器
which:选择使用哪些计时器
curr_value:
	struct timeval it_value; 第一次触发时钟信号所需要的时间
	struct timeval it_interval; 每一次触发时钟信号所需要的时间
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值