Linux高级编程
Go to file
flykhan 0903b37b35 管道作业前6题 2023-08-18 01:09:47 +08:00
day1 shell 作业 2023-08-14 20:13:50 +08:00
day2 day2: 作业8 写法二 2023-08-16 10:34:26 +08:00
day3 进程(下),信号 2023-08-17 09:20:18 +08:00
day4 管道作业前6题 2023-08-18 01:09:47 +08:00
.gitignore gitignore规则 2023-08-14 15:53:25 +08:00
README.md 进程(下),信号 2023-08-17 09:20:18 +08:00

README.md

一、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  左边命令执行成功(即返回 0shell 才执行右边的命令
command1 || command2  左边的命令未执行成功(即返回非 0shell 才执行右边的命令

多重条件判定:

-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 <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);  文件不存在时

#include <unistd.h>
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 <stdio.h>
int remove(const char *pathname);  【库函数】

2.2 fcntl 函数

// 修改标准的输入设备的标识为非阻塞
int flags = fcntl(STDIN_FILENO, F_GETFL);
flags |= O_NONBLOCK;
fcntl(STDIN_FILENO, F_SETFL, flags);

取消非阻塞

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 <sys/types.h>
#include <unistd.h>

pid_t getpid(void) 获取本进程号(PID)
pid_t getppid(void)  获取父进程号(PPID)
pid_t getpgid(pid_t pid) 获取进程组号(PGID)参数为0时返回当前PGID否则返回参数指定的进程的PGID

2.4 进程控制函数

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);   创建独立空间的子进程



#include <unistd.h>
unsigned int sleep(unsigned int sec);  挂起或休眠sec秒


#include <sys/types.h>
#include <sys/wait.h>

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 <unistd.h>
_exit(int status_value) 进程的终止及状态值

#include <stdlib.h>
int atexit(void (*function)(void));   退出清理回调


pid_t vfork(void)  创建新进程但不复制父进程的内存空间,且子进程先执行


进程替换: 不会创建新的进程,只是将进程中执行的程序替换成新的程序
#include <unistd.h>
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 <stdlib.h>
int system(const char *command);

3.3 进程间通信方式

进程间通信功能:

数据传输: 一个进程需要将它的数据发送给另一个进程。
资源共享: 多个进程之间共享同样的资源。
通知事件: 一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
进程控制: 有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。

Linux 操作系统支持的主要进程间通信的通信机制:

UNIX   信号、无名管道、有名管道、Socket
System V/POSIX: 消息队列、共享内存、信号量

3.4 信号

信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。

每个信号的名字都以字符 SIG 开头, 命令:kill -l查看所有信号

信号的基本操作

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signum);
int raise(int signum);

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
int pause(void);  将调用进程挂起直至捕捉到信号为止

#include <stdlib.h>
void abort(void);  向进程发送一个 SIGABRT无法阻塞的信号默认情况下进程会退出。


处理信号

程序中可用函数 signal()改变信号的处理方式。

#include <signal.h>

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 <signal.h>
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);                      //已弃用
};

1sa_handler、sa_sigaction信号处理函数指针和 signal() 里的函数指针用法一样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:

a) SIG_IGN忽略该信号
b) SIG_DFL执行系统默认动作
c) 处理函数名:自定义信号处理函数
  1. sa_mask信号阻塞集在信号处理函数执行过程中临时屏蔽指定的信号。

  2. 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 <signal.h>
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 集合