第1章——《Unix基础知识》
2018-06-21 本文已影响0人
alex_man
实验环境介绍
-
gcc:4.8.5
-
glibc:glibc-2.17-222.el7.x86_64
-
os:Centos7.4
-
kernel:3.10.0-693.21.1.el7.x86_64
引言
- 参考《Unix环境高级编程》写的笔记,算是个人对Unix/Linux类环境上编程接口的一个总结,入门级的小节会直接略过~~~嘿嘿
以下会以知识点 + 代码实验的方式来进行讲解、总结
Unix体系结构
-
如图所示(自己理解一下):
Unix体系结构
登录
- 登录名:略过
- shell:shell是一个命令行解释器,读取用户的输入,然后执行命令。
-
我的实验环境使用的是bash,如下图:
获取shell类型 -
常见的各系统使用的shell类型如下图:
常见的各系统使用的shell类型
-
注意:虽然《Unix环境高级编程》中使用了多种shell来做实验,但是我的笔记、实验中统一在bash环境下进行
文件和目录
- 省略
输入和输出
- 文件描述符:一个非负整数,内核用来表示某个进程正在访问的文件。当内核打开(创建)一个文件时,都会返回一个文件描述符。通过这个文件描述符进行文件的读写
- 标准输入、标准输出和标准错误:每当运行一个程序(进程)时候,所有的shell都为其打开3个描述符,即标准输入、标准输出、标准错误。(至于标准输出、标准输入、标准错误具体指哪个文件,我们今后讨论),下面的例子就是两个进程分别把数据写到标准输出(当前shell,因为unix的设计就是一切皆文件)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFFSIZE(buf) (strlen(buf) + 1)
int
main(int argc, char *argv[])
{
int n;
char buf[] = "parent write to stdout\n";
char buf2[] = "child write to stdout\n";
pid_t pid = fork();
if (pid < 0)
printf("fork error\n");
else if (pid) {
/* 父进程 */
n = write(STDOUT_FILENO, buf, BUFFSIZE(buf));
printf("parent write %d bytes\n", n);
if (n < 0)
printf("parent write error\n");
} else {
/* 子进程 */
n = write(STDOUT_FILENO, buf2, BUFFSIZE(buf2));
printf("child write %d bytes\n", n);
if (n < 0)
printf("child write error\n");
}
exit(EXIT_SUCCESS);
}
result:
parent write to stdout
child write to stdout
parent write 24 bytes
child write 23 bytes
- 不带缓冲的I/O:函数open、read、write、lseek以及close提供不带缓冲的I/O,这些函数都使用文件描述符。
第3章再来详细说明不带缓冲的I/O函数
- 标准I/O:标准I/O为那些不带缓冲的I/O函数提供了一个带缓冲的接口,这样无需担心如何选取最佳的缓冲区大小。标准I/O函数库提供了使我们能够控制该库所使用的缓冲风格的函数(第五章再来详细讨论缓冲)
程序和进程
- 程序:程序是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数(7个exec函数之一),将程序读入内存,并执行。第8章将说明这些exec函数
- 进程和进程ID:程序执行的实例被称为进程。获取当前程序的进程id,代码如下:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
pid_t pid = getpid();
printf("current pid is: %d\n", (long)pid);
exit(EXIT_SUCCESS);
}
result:
current pid is: 33798
- 进程控制:有3个用于进程控制的主要函数:fork、exec和waitpid(exec函数有7中变体,但是统称为exec函数),在第8章讨论。
- 线程和线程ID:通常一个进程只有一个控制线程(thread),一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一存储区,所以多线程环境下需要采取同步措施。线程也用ID标识。但是线程ID只在所属的进程内起作用。一个进程中的线程ID在另一个进程中没有意义。在第12章中,在进一步讨论
出错处理
- 当unix/Linux的系统函数出错时,通常会返回一个负值,然后整型变量errno通常被设置为具有特定信息的值。posix和ISO C将errno定义成一个符号,将它扩展成为一个可修改的整型左值
// 这个是以前的定义
extern int errno;
// Linux支持多线程存取errno的定义
# ifndef __ASSEMBLER__
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));
# if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value. */
# define errno (*__errno_location ())
# endif
# endif /* !__ASSEMBLER__ */
- 使用man 3 errno,查看下实验环境的errnor(部分)说明:
E2BIG Argument list too long (POSIX.1)
EACCES Permission denied (POSIX.1)
EADDRINUSE Address already in use (POSIX.1)
EADDRNOTAVAIL Address not available (POSIX.1)
EAFNOSUPPORT Address family not supported (POSIX.1)
EAGAIN Resource temporarily unavailable (may be the same value as EWOULDBLOCK) (POSIX.1)
EALREADY Connection already in progress (POSIX.1)
EBADE Invalid exchange
EBADF Bad file descriptor (POSIX.1)
EBADFD File descriptor in bad state
EBADMSG Bad message (POSIX.1)
EBADR Invalid request descriptor
EBADRQC Invalid request code
EBADSLT Invalid slot
EBUSY Device or resource busy (POSIX.1)
ECANCELED Operation canceled (POSIX.1)
ECHILD No child processes (POSIX.1)
ECHRNG Channel number out of range
ECOMM Communication error on send
ECONNABORTED Connection aborted (POSIX.1)
ECONNREFUSED Connection refused (POSIX.1)
ECONNRESET Connection reset (POSIX.1)
EDEADLK Resource deadlock avoided (POSIX.1)
EDEADLOCK Synonym for EDEADLK
EDESTADDRREQ Destination address required (POSIX.1)
-
errno的规则注意:
- 如果没有出错,errno的值是不会被例程清楚的。因此只有当函数返回值指明出错的时候,才检查其值。
- 任何函数都不会把errno的值设置为0,而且在<errno.h>中定义的所有常量都不为0
-
如何规范检查errno
// 错误的示范
if (somecall() == -1) {
printf("somecall() failed\n");
if (errno == ...) { ... }
}
// 规范的使用,因为errno可能会被printf所改变,所以需要在somecall()之后将errno保存下来进行检查
if (somecall() == -1) {
int errsv = errno;
printf("somecall() failed\n");
if (errsv == ...) { ... }
}
- 打印errno的出错信息
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
// 测试strerror函数
errno = EACCES;
fprintf(stderr, "EACCES: %s\n", strerror(errno));
// 测试perror函数
errno = ENOENT;
perror(argv[0]);
exit(EXIT_SUCCESS);
}
result:
EACCES: Permission denied
./1_8: No such file or directory
- 出错恢复:errno.h中定义的各种出错分为两类:致命性和非致命性的。
- 致命性的:无法恢复执行动作。最多能做的是用户屏幕上打印出一条出错消息或者将一条出错消息写入日志文件,然后退出。
- 非致命性的:可以较妥善地处理,比如资源短缺,当系统活动较少时,这种出错可能不会发生。
- 与资源相关的非致命性出错包括:EAGAIN、ENFILE、ENOBUFS、ENOLCK、ENOSPC、EWOULDBLOCK
ENOMEM:Not enough space (POSIX.1),有时ENOMEM也是非致命性错误
EBUSY:Device or resource busy (POSIX.1),指明共享资源正在使用
EINTR:中断一个慢速系统调用(可能永远阻塞的系统调用,比如read、recv、select之类的)时,可以将它作为非致命性出错处理
用户标识
- 用户ID:用户ID为0的用户为root或者超级用户(superuser),其他用户的id也是在创建用户的时候就确定了,不能更改
- 组ID: 多个用户可能会被分到一个用户组,组ID就是这个组的标识
- 附属组:一个
- 使用id命令可以查看当前用户的id、组id、附属组
[root@localhost part_1]# id
uid=0(root) gid=0(root) groups=0(root)
[root@localhost part_1]#
- 获取当前进程的id
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
printf("uid = %d, gid = %d\n", getuid(), getgid());
exit(EXIT_SUCCESS);
}
result:
// 用root用户去运行
uid = 0, gid = 0
// 用一般用户去运行
uid = 1000, gid = 1000
信号
- 作用:信号用于通知进程发生了某种情况,让进程进行相应的处理
- 信号的处理方式:
- 忽略信号
- 按系统默认方式处理(不同信号的默认处理方式是不同的)
- 提供一个信号处理函数
这个我们在第10章详细讨论,这个主意的细节比较多
时间值
- 日历时间:用time_t来保存系统时间戳
- 进程时间:也称为CPU时间,使用clock_t保存这种时间值。第2章回来讨论如何使用sysconfig得到时钟滴答数
- 进程时间值:时钟时间(进程运行的时间总量)、用户cpu时间(用户指令所用的时间)、系统cpu时间(内核服务指令使用的时间,如read、write)
第8章来使用内置函数来获取这三个时间
* 使用命令来获取某个进程的这3个时间
[root@localhost part_1]# cd /usr/include/
[root@localhost include]# time -p grep _POSIX_SOURCE */*.h > /dev/null
real 0.02
user 0.00
sys 0.01
[root@localhost include]#
系统调用和库函数
-
区别一如图:
系统调用和库函数
- 区别二:系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能