## 一、shell ### 1.1 shell 解释器 ``` /bin/bash [默认] /bin/sh ``` ### 1.2 shell 脚本调用 系统自动调用: ``` /etc/profile 系统环境变量, 每次系统启动时调用 ~/.bashrc 用户环境变量, 用户登录时调用 ``` 用户调用: ``` 1) chmod +x xxx.sh ./xxx.sh 2) bash xxx.sh 3) . xxx.sh 相当于 source xxx.sh ``` ### 1.3 shell 脚本的定义 第一行: ``` #!/bin/bash ``` ### 1.4 shell 变量 ``` 定义: 变量名=值 引用: $变量名, "$变量名 xxx" 重新赋值: 变量名=新值 算术计算: 变量名=$(( 变量名 算术运算符 值 )) 预定变量: $#, $*, $?, $1~9, $0 进程名, $$ 进程ID 环境变量: $环境变量名 export 变量名=值 设置环境变量 特殊的变量写法: (变量名=值; 其它语句) 不影响外部的同名变量 {变量名=值; 其它语句;} 影响外部的同名变量 ``` ### 1.5 条件测试语句 语法: ``` test 操作符 变量 test 变量1 操作符 变量2 [ 操作符 变量 ] [ 变量1 操作符 变量2 ] ``` 文件相关: ``` -e 是否存在 -d 是目录 -f 是文件 -r 可读 -w 可写 -x 可执行 -L 符号连接 -c 是否字符设备 -b 是否块设备 -s 文件非空 ``` 字符串相关: ``` = 两个字符串相等 != 两个字符串不相等 -z 空串 -n 非空串 ``` 数字相关: ``` -eq 数值相等 -ne 数值不相等 -gt 数 1 大于数 2 -ge 数 1 大于等于数 2 -lt 数 1 小于数 2 -le 数 1 小于等于数 2 ``` 复合测试相关: ``` command1 && command2 左边命令执行成功(即返回 0)shell 才执行右边的命令 command1 || command2 左边的命令未执行成功(即返回非 0)shell 才执行右边的命令 ``` 多重条件判定: ``` -a 多个条件必须都为真(true) 结果才为true -o 多个条件中只需要一个为真,结果为true ! 条件取反 ``` ### 1.6 分支语句 if 分支: ``` if [ 条件1 ]; then 执行第一段程序 elif [条件2 ]; then 执行第二段程序 else 执行第三段程序 fi ``` case 分支: ``` case $变量名称 in “第一个变量内容”) 程序段一 ;; “第二个变量内容”) 程序段二 ;; *) 其它程序段 exit 1 esac ``` for 语句: ``` for (( 初始值; 限制值; 执行步阶 )) do 程序段 done ``` while 语句: ``` while [ condition ] do 程序段 done ``` until 语句: ``` until [ condition ] do 程序段 done ``` 循环语句中可以使用 break 和 continue。 ### 1.7 函数 定义函数: ``` 函数名(){ 命令 ... } function 函数名(){ 命令 ... } ``` 调用函数: ``` 函数名 param1 param2…… ``` 函数体内,可以使用$1,$2 ...$9 读取传递到函数的参数。 ## 二、系统调用和进程(上) ### 2.1 系统调用 I/O 函数 ``` #include #include #include int open(const char *pathname, int flags); 文件存在时 int open(const char *pathname, int flags, mode_t mode); 文件不存在时 #include int close(int fd); ssize_t write(int fd, const void *addr,size_t count); ssize_t read(int fd, void *addr, size_t count); #include int remove(const char *pathname); 【库函数】 ``` ### 2.2 fcntl 函数 ```c // 修改标准的输入设备的标识为非阻塞 int flags = fcntl(STDIN_FILENO, F_GETFL); flags |= O_NONBLOCK; fcntl(STDIN_FILENO, F_SETFL, flags); ``` 取消非阻塞 ```c int flags = fcntl(STDIN_FILENO, F_GETFL); flags ^= O_NONBLOCK; fcntl(STDIN_FILENO, F_SETFL, flags); ``` 获取文件的类型(文件、目录) ``` struct stat info; stat(char *path, &info); info.st_mode & S_IFDIR 是否为目录的验证 info.st_mode & S_IFREG 是否为文件的验证 ``` 打开目录和读取目录: ``` dir *opendir(char *path) dirent *readdir(dir *) ``` ### 2.3 进程的定义 ``` 进程拥有自己独立的处理环境和系统资源(处理器、存储器、I/O 设备、数据、程序) ``` 进程的三种状态: ``` 就绪态: 进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间。 执行态: 该进程正在占用 CPU 运行。 等待态: 进程因不具备某些执行条件而暂时无法继续执行的状态。 ``` 进程控制块(PCB): ``` OS 是根据 PCB 来对并发执行的进程进行控制和管理的。 系统在创建一个进程的时候会开辟一段内存空间存放与此进程相关的 PCB 数据结构。 PCB 是操作系统中最重要的记录型数据结构,包含进程号、返回状态、运行时间等。 ``` 获取进程号: ``` #include #include pid_t getpid(void) 获取本进程号(PID) pid_t getppid(void) 获取父进程号(PPID) pid_t getpgid(pid_t pid) 获取进程组号(PGID),参数为0时返回当前PGID,否则返回参数指定的进程的PGID ``` ### 2.4 进程控制函数 ``` #include #include pid_t fork(void); 创建独立空间的子进程 #include unsigned int sleep(unsigned int sec); 挂起或休眠sec秒 #include #include pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options) ``` 获取子进程的返回状态: ``` WIFEXITED(status) 如果子进程是正常终止的,取出的字段值非零 WEXITSTATUS(status) 返回子进程的退出状态,退出状态保存在 status 变量的 8~16 位(低位的第2个字节)。在用此宏前应先用宏 WIFEXITED 判断子进程是否正常退出,正常退出才可以使用此宏。 ``` ## 三、进程(下),信号 ### 3.1 ps ja 查看进程状态 ``` a 显示终端上的所有进程,包含其他用户的进程 j 显示父进程号、进程组号、会话号(sid)等信息 ``` ### 3.2 进程控制 ``` #include _exit(int status_value) 进程的终止及状态值 #include int atexit(void (*function)(void)); 退出清理回调 pid_t vfork(void) 创建新进程但不复制父进程的内存空间,且子进程先执行 进程替换: 不会创建新的进程,只是将进程中执行的程序替换成新的程序 #include int execl(const char *pathname,const char *arg0,…,NULL); int execlp(const char *filename,const char *arg0,…, NULL); int execle(const char *pathname,const char *arg0,…,NULL, char *const envp[]); int execv(const char *pathname, char *const argv[]); int execvp(const char *filename, char *const argv[]); int execve(const char *pathname, char *const argv[],char *const envp[]) 基于fork+exec执行外部命令或程序 #include int system(const char *command); ``` ### 3.3 进程间通信方式 **进程间通信功能:** ``` 数据传输: 一个进程需要将它的数据发送给另一个进程。 资源共享: 多个进程之间共享同样的资源。 通知事件: 一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。 进程控制: 有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。 ``` Linux 操作系统支持的主要进程间通信的通信机制: ``` UNIX: 信号、无名管道、有名管道、Socket System V/POSIX: 消息队列、共享内存、信号量 ``` ### 3.4 信号 信号是**软件中断**,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。 每个信号的名字都以字符 SIG 开头, 命令:`kill -l`查看所有信号 信号的基本操作 ``` #include #include int kill(pid_t pid, int signum); int raise(int signum); #include unsigned int alarm(unsigned int seconds); int pause(void); 将调用进程挂起直至捕捉到信号为止 #include void abort(void); 向进程发送一个 SIGABRT(无法阻塞的)信号,默认情况下进程会退出。 ``` **处理信号** > 程序中可用函数 signal()改变信号的处理方式。 ``` #include typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); ``` 注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。 handler 的取值: ``` 忽略该信号: SIG_IGN 执行系统默认动作: SIG_DFL 自定义信号处理函数: 信号处理函数名 ``` 返回值: ``` 成功:返回函数地址,该地址为此信号上一次注册的信号处理函数的地址。 失败:返回 SIG_ERR ``` > 使用 sigaction()处理信号时,必须在第一行声明宏 : > > #define \_XOPEN_SOURCE 700 ``` #include int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); ``` 参数: ``` signum:要操作的信号。 act: 要设置的对信号的新处理方式(传入参数)。 oldact:原来对信号的处理方式(传出参数)。 如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact, 可以为NULL。 ``` 返回值: 成功:0 失败:-1 ``` struct sigaction { void (*sa_handler)(int); //旧的信号处理函数指针 void (*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针 sigset_t sa_mask; //信号阻塞集 int sa_flags; //信号处理的方式 void (*sa_restorer)(void); //已弃用 }; ``` 1)sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下: ``` a) SIG_IGN:忽略该信号 b) SIG_DFL:执行系统默认动作 c) 处理函数名:自定义信号处理函数 ``` 2. sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。 3. sa_flags:用于指定信号处理的行为,通常设置为 0,表使用默认属性。它可以是以下值的“按位或”组合: ``` SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃) SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。 SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。 SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。 SA_RESETHAND:信号处理之后重新设置为默认的处理方式。 SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数 ``` 信号处理函数: ``` void(*sa_sigaction)(int signum, siginfo_t *info, void *context); 参数说明: signum:信号的编号。 info:记录信号发送进程信息的结构体。 context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程 ``` ### 3.5 可重入函数 > 可重入函数是指函数可以由多个任务并发使用,而不必担心数据错误。 编写可重入函数: ``` 1、不使用(返回)静态的数据、全局变量(除非用信号量互斥)。 2、不调用动态内存分配、释放的函数。 3、不调用任何不可重入的函数(如标准 I/O 函数)。 ``` ### 3.6 信号集 在 PCB 中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。 这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我 们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。 信号集是用来表示多个信号的数据类型。 信号集数据类型: `sigset_t` 信号集相关的操作主要有如下几个函数: ``` #include int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigismember(const sigset_t *set,int signum); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); ``` ### 3.7 信号阻塞集 创建一个阻塞集合 ``` int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); ``` 参数:how:信号阻塞集合的修改方法 ``` SIG_BLOCK:向信号阻塞集合中添加 set 信号集 SIG_UNBLOCK:从信号阻塞集合中删除 set 集合 SIG_SETMASK:将信号阻塞集合设为 set 集合 ```