SpringBoot与Docker

1、简介

Docker是一个开源的应用容器引擎;是一个轻量级容器技术;

Docker支持将软件编译成一个镜像;然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;

运行中的这个镜像称为容器,容器启动是非常快速的。
Image text

Image text

2、核心概念

docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上);

docker客户端(Client):连接docker主机进行操作;

docker仓库(Registry):用来保存各种打包好的软件镜像;

docker镜像(Images):软件打包好的镜像;放在docker仓库中;

docker容器(Container):镜像启动后的实例称为一个容器;容器是独立运行的一个或一组应用
Image text
使用Docker的步骤:

1)、安装Docker

2)、去Docker仓库找到这个软件对应的镜像;

3)、使用Docker运行这个镜像,这个镜像就会生成一个Docker容器;

4)、对容器的启动停止就是对软件的启动停止;

3、安装Docker

1)、安装linux虚拟机

​ 1)、VMWare、VirtualBox(安装);

​ 2)、导入虚拟机文件centos7-atguigu.ova;

​ 3)、双击启动linux虚拟机;使用 root/ 123456登陆

​ 4)、使用客户端连接linux服务器进行命令操作;

​ 5)、设置虚拟机网络;

​ 桥接网络===选好网卡====接入网线;

​ 6)、设置好网络以后使用命令重启虚拟机的网络

1
service network restart

7)、查看linux的ip地址

1
ip addr

8)、使用客户端连接linux;

2)、在linux虚拟机上安装docker

步骤:

1、检查内核版本,必须是3.10及以上
uname -r
2、安装docker
yum install docker
3、输入y确认安装
4、启动docker
[root@localhost ~]# systemctl start docker
[root@localhost ~]# docker -v
Docker version 1.12.6, build 3e8e77d/1.12.6
5、开机启动docker
[root@localhost ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
6、停止docker
systemctl stop docker

4、Docker常用命令&操作

1)、镜像操作
Image text
docker仓库:https://hub.docker.com/

2)、容器操作

软件镜像(QQ安装程序)—-运行镜像—-产生一个容器(正在运行的软件,运行的QQ);

步骤:

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
1、搜索镜像
[root@localhost ~]# docker search tomcat
2、拉取镜像
[root@localhost ~]# docker pull tomcat
3、根据镜像启动容器
docker run --name mytomcat -d tomcat:latest
4、docker ps
查看运行中的容器
5、 停止运行中的容器
docker stop 容器的id
6、查看所有的容器
docker ps -a
7、启动容器
docker start 容器id
8、删除一个容器
docker rm 容器id
9、启动一个做了端口映射的tomcat
[root@localhost ~]# docker run -d -p 8888:8080 tomcat
-d:后台运行
-p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口

10、为了演示简单关闭了linux的防火墙
service firewalld status ;查看防火墙状态
service firewalld stop:关闭防火墙
11、查看容器的日志
docker logs container-name/container-id

更多命令参看
https://docs.docker.com/engine/reference/commandline/docker/
可以参考每一个镜像的文档

3)、安装MySQL示例

1
docker pull mysql

错误的启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost ~]# docker run --name mysql01 -d mysql
42f09819908bb72dd99ae19e792e0a5d03c48638421fa64cce5f8ba0f40f5846

mysql退出了
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
42f09819908b mysql "docker-entrypoint.sh" 34 seconds ago Exited (1) 33 seconds ago mysql01
538bde63e500 tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago compassionate_
goldstine
c4f1ac60b3fc tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago lonely_fermi
81ec743a5271 tomcat "catalina.sh run" About an hour ago Exited (143) About an hour ago sick_ramanujan


//错误日志
[root@localhost ~]# docker logs 42f09819908b
error: database is uninitialized and password option is not specified
You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD;这三个参数必须指定一个

正确的启动

1
2
3
4
5
[root@localhost ~]# docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
b874c56bec49fb43024b3805ab51e9097da779f2f572c22c695305dedd684c5f
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b874c56bec49 mysql "docker-entrypoint.sh" 4 seconds ago Up 3 seconds 3306/tcp mysql01

做了端口映射

1
2
3
4
5
[root@localhost ~]# docker run -p 3306:3306 --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
ad10e4bc5c6a0f61cbad43898de71d366117d120e39db651844c0e73863b9434
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ad10e4bc5c6a mysql "docker-entrypoint.sh" 4 seconds ago Up 2 seconds 0.0.0.0:3306->3306/tcp mysql02

几个其他的高级操作

1
2
3
4
5
6
docker run --name mysql03 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面
改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/conf/mysql)

docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
指定mysql的一些配置参数

Spring的bean管理(注解方式)

注解

1、代码里面的特殊标记,使用注解可以完成某些功能
2、注解写法:@注解名称(属性名称=属性值)
3、注解使用在类、方法、属性上面

注解开发准备

1、创建类User

1
2
3
4
5
6
7
package com

public class User{
public void add(){
System.out.println("add.......");
}
}

2、创建配置文件,引入约束
(1)IOC基本功能,引入beans约束
(2)IOC注解开发,还需引入spring-context约束

3、

1
2
3
4
5
6
7
8
9
10
<!---开启注解扫描
1)到包里面扫描类、方法、属性上面是否有注解
--->
<context:component-scan base-package="com"></context:component-scan>
<!---base-package属性为要开启注解扫描的包--->

<!---
只扫描属性上面是否有注解
--->
<context:annotation-config></context:annotation-config>

注解创建对象

1
2
3
4
5
6
7
8
9
package com

//在类上方使用注解创建对象,相当于<bean id="user" class="com.User"></bean>
@Component(value="user")
public class User{
public void add(){
System.out.println("add.......");
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
public class TestIOC{
@Test
public void testUser(){
//1、加载spring配置文件,扫描注解
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");

//2、得到注解创建的对象
User user=(User)context.getBean("user");
user.add();
}
}

创建对象的四个注解

1、@Component
2、@Controller:web层
3、@Service:业务层
4、@Repository:持久层
目前四个注解功能一样,都是创建对象,后三个注解为了让类标注本身的用途清晰,spring后续版本会对其增强。

注解创建多实例对象

1
2
3
4
5
6
7
8
9
10
package com

//@Scope(value="propertype)指明创建多实例对象
@Component(value="user")
@Scope(value="propertype")
public class User{
public void add(){
System.out.println("add.......");
}
}

注解注入属性

1、创建UserService类,用注解@Component(value=”userService”)创建该类对象userService

1
2
3
4
5
6
7
8
9
@Component(value="userService")
public class UserService{

public void add(){
System.out.println("service........");
//在service中得到Dao对象,才能调用Dao的方法
userDao.add();
}
}

2、创建UserDao类,用注解@Component(value=”userDao”)创建该类对象userDao

1
2
3
4
5
6
7
@Component(value="userDao")
public class UserDao{

public void add(){
System.out.println("dao........");
}
}

3、在 UserService类中,把UserDao作为一个属性,并在该属性上使用注解@Autowired自动装配,完成对象注入(根据类名UserDao找到该类并自动注入值)。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component(value="userService")
public class UserService{
//(1)把UserDao对象作为类型属性
//(2)在UserDao属性上使用注解完成对象注入,@Autowired自动装配
@Autowired
private UserDao userDao;

public void add(){
System.out.println("service........");
//在service中得到Dao对象,才能调用Dao的方法
userDao.add();
}
}

也可以用@Resource(name=”userDao”)完成对象注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component(value="userService")
public class UserService{
//(1)把UserDao对象作为类型属性
//(2)@Resource(name="userDao"),name的属性值为注解创建UserDao对象时的value值,
// 这里创建UserDao对象的注解是@Component(value="userDao")
@Resource(name="userDao")
private UserDao userDao;

public void add(){
System.out.println("service........");
//在service中得到Dao对象,才能调用Dao的方法
userDao.add();
}
}

配置文件和注解混合使用

1、创建对象操作使用配置文件方式实现
2、注入属性操作使用注解方式实现

1)创建UserService类

1
2
3
4
5
6
7
8
public class UserService{

public void add(){
System.out.println("service........");
//在service中得到Dao对象,才能调用Dao的方法
userDao.add();
}
}

2、创建UserDao1类

1
2
3
4
5
6
public class UserDao{

public void add(){
System.out.println("dao1........");
}
}

3、创建UserDao2类

1
2
3
4
5
6
public class UserDao{

public void add(){
System.out.println("dao2........");
}
}

4、创建配置文件创建对象

1
2
3
4
5
6
7
8
9
<!---开启注解扫描
1)到包里面扫描类、方法、属性上面是否有注解
--->
<context:component-scan base-package="com"></context:component-scan>

<!---配置对象--->
<bean id="userService" class="com.UserService"></bean>
<bean id="userDao1" class="com.UserDao1"></bean>
<bean id="userDao2" class="com.UserDao2"></bean>

5、在UserService中使用注解@Resource注入属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserService{
@Resource(name="userDao1")
private UserDao1 userDao1;

@Resource(name="userDao2")
private UserDao2 userDao2;

public void add(){
System.out.println("service........");
//在service中得到Dao对象,才能调用Dao的方法
userDao1.add();
userDao2.add();
}
}

Spring-jdbcTemplate

简介

1、spring是一站式框架,针对javaEE三层,每一层都有解决技术
web层:springMVC
service层:spring的IOC
dao层:spring的jdbcTemplate

2、Spring对不同的持久化技术都进行了封装,例JDBC、Hibemate5.0、MyBatis,jdbcTemplate对JDBC进行封装。

jdbcTemplate的crud操作

1、增加
2、修改
3、删除
4、查询

spring-IOC

spring概念

1、是开源的轻量级框架

2、核心两部分

1)aop:面向切面编程,扩展功能不是修改源代码实现
2)ioc:控制反转,比如有一个类,类里有方法(不是静态方法),若要调用该类的方法,则需要先创建类的对象,通过对象来调用方法,而创建对象需要用new, 而控制反转则是对象的创建不通过new方式实现,而是交给spring框架配置创建类的对象,控制权不在开发者手中,而在上层框架中。

3、一站式框架

spring在JavaEE三层结构中,每一层都提供不同的解决技术
web层:springMVC
service层:spring的IOC
dao层:spring的jdbcTemplate

注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

IOC底层原理

1、底层原理使用技术

  • 1)xml配置文件
  • 2)dom4j解析xml
  • 3)工厂设计模式
  • 4)反射

例:

1
2
public class UserService{
}

1
2
3
4
5
6
public class UserServlet{
//得到UserService的对象
//常规方法:new创建
//IOC方法:工厂模式创建
UserFactory. getService();
}

第一步:创建xml配置文件,配置要创建对象的类

1
2
<bean id="userService" class="com.ct.UserService">
//class属性里要写全类名

第二步:创建工厂类,使用dom4j解析配置文件+反射

1
2
3
4
5
6
7
8
9
10
11
12
public class UserFactory{
//得到UserService对象的方法
public static UserService getService(){
//1、使用dom4j解析xml文件,根据id值userService得到class属性值
String classValue="class属性值";
//2、使用反射创建类对象
Class clazz=Class.forName(classValue);
//3、创建类对象
UserService service=clazz.newInstance();
return service;
}
}

spring的IOC操作

1、把对象的创建交给spring进行管理

2、IOC操作两部分
1)IOC的配置文件方式
2)IOC的注解方式

spring的bean管理(xml方式)

bean实例化的三种方式
1)实用类的默认无参构造函数创建(重点)
若类中没有无参构造函数,则出现异常

2)使用静态工厂创建
创建静态方法,返回类对象
例:

1
2
3
4
5
6
public class UserFactory{
//得到UserService对象的静态方法
public static UserService getService(){
return new UserService();
}
}

1
2
3
<!---使用静态工厂创建对象--->
<bean id="userService" class="com.UserFactioy" factory-method="getService"></bean>
<!---class属性值为工厂类的全类名,factory-method的属性值为返回所要创建对象的静态方法名--->

3)使用实例工厂创建
创建不是静态方法,返回类对象

1
2
3
4
5
6
public class UserFactory{
//得到UserService对象的普通方法
public UserService getService(){
return new UserService();
}
}

1
2
3
4
5
<!---使用实例工厂创建对象--->
<!---创建工厂对象--->
<bean id="userFactory" class="com.UserFactioy"></bean>
<bean id="userService" class="com.UserFactioy" factory-method="getService"></bean>
//class属性值为工厂类的全类名,factory-method的属性值为返回所要创建对象的方法名

属性注入

1、创建对象的时候,向类里面的属性设置属性值

2、Java中属性注入方式

1)set方法注入

1
2
3
4
5
6
7
8
public class User{
private String name;

//setName方法为属性name注入值
public void setName(String name){
this.name=name;
}
}

2)有参数构造方法注入

1
2
3
4
5
6
7
8
public class User{
private String name;

//有参构造方法为属性name注入值
public User(String name){
this.name=name;
}
}

3)接口注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义接口
public interface User{
public void userName(String name);
}

//接口实现类
public class UserImpl implements User{
private String name;

//在实现类中实现方法并注入属性
public void userName(String name){
this.name=name;
}
}

3、spring中属性注入方法
spring中只支持前两种属性注入方法

1)set方法注入(重点)

1
2
3
4
5
<!---set方法注入--->
<bean id="user" class="com.User">
<!---将value中的值注入到User类的属性name中--->
<property name="name" value="张三李四" </property>
</bean>

2)有参数构造方法注入

1
2
3
4
5
<!---有参数构造方法注入--->
<bean id="user" class="com.User">
<!---将value中的值注入到User类的属性name中--->
<constructor-arg name="name" value="张三李四" </constructor-arg>
</bean>

注入对象类型属性(重点)

例:
1、创建UserService和UserDao类
1)在UserService中得到UserDao对象
具体实现:
(1)在UserService里把UserDao对象作为类型属性
(2)创建生成UserDao对象属性的set方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserService{
//(1)把UserDao对象作为类型属性
private UserDao userDao;

//(2)set方法
public void setUserDao(UserDao userDao){
//(1)在UserService里把UserDao对象作为类型属性
this.userDao=userDao;
}
public void add(){
System.out.println("service........");
//在service中得到Dao对象,才能调用Dao的方法
userDao.add();
}
}

1
2
3
4
5
6
public class UserDao{

public void add(){
System.out.println("dao........");
}
}

(3)在配置文件中配置注入关系

1
2
3
4
5
6
7
8
9
10
11
<!---注入对象类型属性--->
<!---1、配置UserService和UserDao对象--->
<bean id="userDao" class="com.UserDao"></bean>
<bean id="userService" class="com.UserService">
<!---注入UserDao对象
name属性值:UserService类里的对象属性名称
ref属性:UserDao配置的bean标签中的id值
由ref属性代替value属性,因为这里配置的是对象,value的值为字符串
--->
<property name="userDao" ref="userDao" </property>
</bean>

p名称空间注入

1、加一条schema约束

1
xmlns:p="http://www.springframework.org/schema/p"

2、创建User类及其属性userName

1
2
3
4
5
6
7
8
public class User{
private String userName;

//有参构造方法为属性name注入值
public User(String userName){
this.userName=userName;
}
}

3、注入属性

1
<bean id="user" class="com.User" p:userName="张三"></bean>

spring注入复杂数据

1、数组
2、list集合
3、map集合
4、properties类型

第一步:编写测试类Person

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
import java.util.List;
import java.util.Map;
import java.util.Properties;

public class Person {
private String pname;

public void setPname(String pname) {
this.pname = pname;
}

private String[] arrs;
private List<String> list;
private Map<String,String> map;
private Properties properties;

public void setArrs(String[] arrs) {
this.arrs = arrs;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}

第二步:配置bean

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
42
<!---注入复杂类型属性值--->
<bean id="person" class="com.Person">
<!---1、数组--->
<property name="arrs">
<!---name中为要注入的属性名称--->
<list>
<value>小王</value>
<value>小李</value>
<value>小马</value>
</list>
</property>

<!---2、list--->
<property name="list">
<!---name中为要注入的属性名称--->
<list>
<value>小五</value>
<value>小六</value>
<value>小七</value>
</list>
</property>

<!---3、map--->
<property name="map">
<!---name中为要注入的属性名称--->
<map>
<entry key="aa" value="小喵"></entry>
<entry key="bb" value="小猪"></entry>
<entry key="cc" value="小狗"></entry>
</map>
</property>

<!---4、properties--->
<property name="properties">
<!---name中为要注入的属性名称--->
<props>
<prop key="driverclass">com.mysql.jdbc.Driver</prop>
<prop key="username">root</prop>
</props>
</property>

</bean>

IOC与DI的区别

1、IOC:控制反转,把对象创建交给spring进行配置(本质工作是创建对象)
例:在spring中利用bean配置来创建对象

1
2
3
4
<!---创建User类的对象--->
<bean id="user" class="com.User">

</bean>

2、DI:依赖注入,向类里面的属性注入值(本质工作是为一个已经创建好的对象设置它的属性值)
例:

1
2
3
4
<bean id="user" class="com.User">
<!---为User类的对象中的username属性设置值为张三李四--->
<property name="username" value="张三李四" </property>
</bean>

3、关系:依赖注入不能单独存在,需要在IOC基础之上完成操作(想要为一个对象设置它的属性值,必须要先创建对象)

Spring整合web项目原理

例:如下
1、Person类

1
2
3
4
5
6
7
8
9
10
11
public class Person {
private String pname;

public void setPname(String pname) {
this.pname = pname;
}

public void test1(){
System.out.println(pname+":test");
}
}

2、bean配置

1
2
3
4
<bean id="person" class="com.Person">
<!---将value中的值注入到User类的属性name中--->
<property name="pname" value="张三李四" </property>
</bean>

3、得到Person对象并调用其test1()方法

1
2
3
4
5
6
7
8
9
10
11
public class TestIOC{
@Test
public void testUser(){
//1、加载spring配置文件,根据配置文件创建对象
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");

//2、得到配置文件中创建的对象
Person person=(Person)context.getBean("person");
person.test1();
}
}

整合原理:
1、加载spring核心配置文件

1
2
//1、加载spring配置文件,根据配置文件创建对象
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");

ApplicationContext是多实例的,每次new ClassPathXmlApplicationContext()这个对象将会使得效率很低,因为每创建一个对象就会调用一次。

2、改善思想:把加载配置文件和创建对象过程,在服务器启动时完成,把压力交给服务器。

3、实现原理:
1)ServletContext对象
2)监听器

(1)在服务器启动的时候,为每个项目创建一个ServletContext对象
(2)在ServletContext对象创建的时候,使用监听器可以知道ServletContext对象什么时候创建
(3)当监听器知道ServletContext对象创建时,就加载spring配置文件,把配置文件配置的对象创建出来
(4)把创建出来的对象放到ServletContext域对象里面(setAttribute方法可以实现该功能)
(5)获取对象时,到ServletContext域中得到(getAttribute方法得到)

Spring-AOP

AOP概念

1、AOP:面向切面(方面)编程,扩展功能不修改源代码实现
2、AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)

AOP原理

1、创建User类

1
2
3
4
5
6
7
public class User{
//添加数据的用户方法
public void add(){
//添加逻辑
//若要添加日志功能,需在此修改源代码添加日志逻辑
}
}

2、纵向抽取机制(使用继承实现来增强功能)

1
2
3
4
5
6
7
8
9
public class baseUser extends User{
//添加数据的用户方法
public void add(){
//添加逻辑
//功能扩展,添加日志功能
//调用父类方式实现日志功能
super.add();
}
}

缺点:若父类的方法名称发生改变,在子类调用的方法也要改变

3、横向抽取机制(AOP来增强功能)
底层使用动态代理实现:

第一种情况:使用jdk动态代理,针对有接口的情况
1)创建接口Dao

1
2
3
public class Dao{
public void add();
}

2)创建接口的实现类

1
2
3
4
5
public class DaoImpl implements Dao{
public void add(){
//添加逻辑
}
}

3)使用动态代理方式,创建接口实现类的代理对象
注:这里具体指创建和DaoImpl类的平级对象,这个对象不是真正的对象,但是代理对象可以实现和DaoImpl相同的功能,需要扩展功能时,通过代理对象来扩展。

第二种情况:使用cglib动态代理实现:针对没有接口的情况
1)、创建User类

1
2
3
4
5
6
public class User{
//添加数据的用户方法
public void add(){
//若要增强功能,使用动态代理
}
}

2)cglib动态代理实现:创建User的子类的代理对象,在子类里调用父类的方法来完成功能增强。

AOP相关术语

1、Joinpoint(连接点):指那些被拦截到的点。spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
2、Pointcut(切入点):指我们要对哪些Joinpoint(连接点)进行拦截的定义
3、Advice(通知/增强):通知是指拦截到JoinPoint之后要做的事情,分为前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)
4、Aspect(切面):是切入点和通知的结合
5、Introduction(引介):引介是一种特殊的通知在不修改代码的前提下,引介可以在运行期为类动态地添加一些方法或Field
6、Target(目标对象):代理的目标对象(要增强的类)
7、Weaving(织入):把增强应用到目标的过程
8、Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类

例:有一个User类

1
2
3
4
5
6
public class User{
public void add(){}
public void update(){}
public void delete(){}
public void findAll(){}
}

1)连接点:类里面那些可以被增强的方法,例如User类中的add()、update()、delete()、findAll()方法。
2)切入点:类面边实际被增强的方法,例如User类中只有add()和update()方法被增强,则add()和update()方法被称为切入点。
3)通知/增强:实现功能增强的实际逻辑,例如扩展日志功能,这个功能则称为增强。
4)切面:把增强应用到具体的方法上面的过程称为切面(把增强用到切入点的过程)
5)引介:可以动态向类中添加属性和方法
6)目标对象:增强方法所在的类,例如要增强add()方法。add()方法所在的类是User,User则称为目标对象。
7)织入:把增强用到类的过程
8)代理:一个类被AOP织入增强后,就产生一个结果代理类

通知的几种类型:
1)前置通知:在方法之前执行
2)后置通知:在方法之后执行
3)异常通知:在方法出现异常时执行
4)最终通知:在后置之后执行(类似于try{}catch{}finall{})
5)环绕通知:在方法之前和之后执行

Spring的AOP操作

1、在spring里面进行AOP操作,使用Aspectj实现
(1)Aspectj是一个面向切面的框架,基于Java语言,Aspectj2.0以后新增了对Aspectj切点表达式的支持。
(2)Aspectj不是spring的一部分,和spring一起使用进行AOP操作

2、使用Aspectj实现AOP的两种方式
(1)基于Aspectj的xml配置文件方式
(2)基于Aspectj的注解方式

3、需额外导入spring-aop约束

基于Aspectj的xml配置文件方式

1、创建Book类

1
2
3
4
5
6
7
package aop;

public class Book{
public void add(){
System.out.println("add.....");
}
}

2、创建增强类MyBook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package aop;

public class MyBook {
public void before1() {
System.out.println("前置增强。。。。。。");
}

public void after1() {
System.out.println("后置增强。。。。。。");
}

public void around1(ProceedingJoinPoint proceedingJoinPoint) {
//方法之前环绕
System.out.println("方法之前环绕增强。。。。。。");

//执行被增强的方法
proceedingJoinPoint.proceed();

//方法之前后
System.out.println("方法之后环绕增强。。。。。。");
}
}

3、使用表达式来配置切入点
1)切入点:实际增强的方法
2)常用的表达式:

1
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)

几种常用表达式写法:
(1)execution( aop.Book.add(..))
(2)execution(
aop.Book.(..))
(3)execution(
.(..))
(4)execution( .add*(..)),匹配所有以add开头的方法

4、配置文件

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
<!---1、配置对象--->
<bean id="book" class="aop.Book"></bean>
<bean id="myBook" class="aop.MyBook"></bean>

<!---2、配置aop操作--->
<aop:config>
<!---1)配置切入点--->
<aop:pointcut expression="execution(* aop.Book.add(..))" id="pointcut1"/>

<!---2)配置切面--->
<aop:aspect ref="myBook">
<!---配置增强类型
method:增强类里面实际要用来做增强的方法
--->

<!---(1)前置增强--->
<aop:before method="before1" pointcut-ref="pointcut1"/>

<!---(2)后置增强--->
<aop:after-returning method="after11" pointcut-ref="pointcut1"/>

<!---(3)环绕增强--->
<aop:around method="around1" pointcut-ref="pointcut1"/>

</aop:aspect>
</aop:config>

5、测试

1
2
3
4
5
6
7
8
9
10
package aop;

public class AopTest {
@Test
public void testService() {
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
Book book=context.getBean("book");
book.add();
}
}

基于Aspectj的注解方式

1、创建Book类

1
2
3
4
5
6
7
package aop;

public class Book{
public void add(){
System.out.println("add.....");
}
}

2、创建增强类MyBook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package aop;

public class MyBook {
public void before1() {
System.out.println("前置增强。。。。。。");
}

public void after1() {
System.out.println("后置增强。。。。。。");
}

public void around1(ProceedingJoinPoint proceedingJoinPoint) {
//方法之前环绕
System.out.println("方法之前环绕增强。。。。。。");

//执行被增强的方法
proceedingJoinPoint.proceed();

//方法之前后
System.out.println("方法之后环绕增强。。。。。。");
}
}

3、配置文件
创建对象并开启AOP操作

1
2
3
4
5
6
<!---1、开启aop操作--->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

<!---2、配置对象--->
<bean id="book" class="aop.Book"></bean>
<bean id="myBook" class="aop.MyBook"></bean>

4、在增强类MyBook上使用注解完成AOP操作

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
package aop;

//1)在类上加@Aspect
@Aspect
public class MyBook {
//2、在方法上加具体的注解
@Before(value="execution(* aop.Book.add(..))")
public void before1() {
System.out.println("前置增强。。。。。。");
}

@AfterRuturning(value="execution(* aop.Book.add(..))")
public void after1() {
System.out.println("后置增强。。。。。。");
}

@Around(value="execution(* aop.Book.add(..))")
public void around1(ProceedingJoinPoint proceedingJoinPoint) {
//方法之前环绕
System.out.println("方法之前环绕增强。。。。。。");

//执行被增强的方法
proceedingJoinPoint.proceed();

//方法之前后
System.out.println("方法之后环绕增强。。。。。。");
}
}

5、测试

1
2
3
4
5
6
7
8
9
10
package aop;

public class AopTest {
@Test
public void testService() {
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
Book book=context.getBean("book");
book.add();
}
}

shell运算符

Shell 基本运算符

Shell 和其他编程语言一样,支持多种运算符,包括:
算数运算符
关系运算符
布尔运算符
字符串运算符
文件测试运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
例如,两个数相加(注意使用的是反引号 ` 而不是单引号 ‘):

1
2
3
4
5
6
#!/bin/bash

ec#!/bin/bash

val=`expr 2 + 2`
echo "两数之和为 : $val"

输出:

1
两数之和为 : 4

两点注意:
表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
完整的表达式要被 包含,注意这个字符不是常用的单引号,在 Esc 键下边。

算术运算符

下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:

1
2
3
4
5
6
7
8
+	加法	`expr $a + $b` 结果为 30。
- 减法 `expr $a - $b` 结果为 -10。
* 乘法 `expr $a \* $b` 结果为 200。
/ 除法 `expr $b / $a` 结果为 2。
% 取余 `expr $b % $a` 结果为 0。
= 赋值 a=$b 将把变量 b 的值赋给 a。
== 相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false
!= 不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true

注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]。
例子:

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
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

a=10
b=20

val=`expr $a + $b`
echo "a + b : $val"

val=`expr $a - $b`
echo "a - b : $val"

val=`expr $a \* $b`
echo "a * b : $val"

val=`expr $b / $a`
echo "b / a : $val"

val=`expr $b % $a`
echo "b % a : $val"

if [ $a == $b ]
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi

输出

1
2
3
4
5
6
a + b : 30
a - b : -10
a * b : 200
b / a : 2
b % a : 0
a 不等于 b

注意:
乘号(*)前边必须加反斜杠()才能实现乘法运算;

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:

1
2
3
4
5
6
-eq	检测两个数是否相等,相等返回 true。	[ $a -eq $b ] 返回 false
-ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true

例子:

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
#!/bin/bash

a=10
b=20

if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
echo "$a -ne $b: a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
echo "$a -gt $b: a 大于 b"
else
echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
echo "$a -lt $b: a 小于 b"
else
echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
echo "$a -ge $b: a 大于或等于 b"
else
echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
echo "$a -le $b: a 小于或等于 b"
else
echo "$a -le $b: a 大于 b"
fi

输出

1
2
3
4
5
6
10 -eq 20: a 不等于 b
10 -ne 20: a 不等于 b
10 -gt 20: a 不大于 b
10 -lt 20: a 小于 b
10 -ge 20: a 小于 b
10 -le 20: a 小于或等于 b

布尔运算符

假定变量 a 为 10,变量 b 为 20:

1
2
3
!	非运算,表达式为 true 则返回 false,否则返回 true。	[ ! false ] 返回 true
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false

例子:

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
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

a=10
b=20

if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ $a -lt 100 -a $b -gt 15 ]
then
echo "$a 小于 100 且 $b 大于 15 : 返回 true"
else
echo "$a 小于 100 且 $b 大于 15 : 返回 false"
fi
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a 小于 100 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 100 或 $b 大于 100 : 返回 false"
fi
if [ $a -lt 5 -o $b -gt 100 ]
then
echo "$a 小于 5 或 $b 大于 100 : 返回 true"
else
echo "$a 小于 5 或 $b 大于 100 : 返回 false"
fi

输出

1
2
3
4
10 != 20 : a 不等于 b
10 小于 100 且 20 大于 15 : 返回 true
10 小于 100 或 20 大于 100 : 返回 true
10 小于 5 或 20 大于 100 : 返回 false

逻辑运算符

假定变量 a 为 10,变量 b 为 20:

1
2
&&	逻辑的 AND	[[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash

a=10
b=20

if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

if [[ $a -lt 100 || $b -gt 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

输出

1
2
返回 false
返回 true

字符串运算符

假定变量 a 为 “abc”,变量 b 为 “efg”:

1
2
3
4
5
=	检测两个字符串是否相等,相等返回 true。	[ $a = $b ] 返回 false
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false
-n 检测字符串长度是否为0,不为0返回 true。 [ -n "$a" ] 返回 true
$ 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true

例子:

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
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

a="abc"
b="efg"

if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a != $b: a 等于 b"
fi
if [ -z $a ]
then
echo "-z $a : 字符串长度为 0"
else
echo "-z $a : 字符串长度不为 0"
fi
if [ -n "$a" ]
then
echo "-n $a : 字符串长度不为 0"
else
echo "-n $a : 字符串长度为 0"
fi
if [ $a ]
then
echo "$a : 字符串不为空"
else
echo "$a : 字符串为空"
fi

输出

1
2
3
4
5
abc = efg: a 不等于 b
abc != efg : a 不等于 b
-z abc : 字符串长度不为 0
-n abc : 字符串长度不为 0
abc : 字符串不为空

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
-b file	检测文件是否是块设备文件,如果是,则返回 true。	[ -b $file ] 返回 false
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true

例子:
变量 file 表示文件”/var/www/runoob/test.sh”,它的大小为100字节,具有 rwx 权限。下面的代码,将检测该文件的各种属性:

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
42
43
44
45
46
47
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com

file="/var/www/runoob/test.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi
if [ -w $file ]
then
echo "文件可写"
else
echo "文件不可写"
fi
if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi
if [ -f $file ]
then
echo "文件为普通文件"
else
echo "文件为特殊文件"
fi
if [ -d $file ]
then
echo "文件是个目录"
else
echo "文件不是个目录"
fi
if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi

输出

1
2
3
4
5
6
7
文件可读
文件可写
文件可执行
文件为普通文件
文件不是个目录
文件不为空
文件存在

shell编程

简介

Shell 既是一种命令语言,又是一种程序设计语言。

Shell 脚本

Shell 脚本(shell script),是一种为 shell 编写的脚本程序。
业界所说的 shell 通常都是指 shell 脚本,但读者朋友要知道,shell 和 shell script 是两个不同的概念。

Shell 环境

Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Linux 的 Shell 种类众多,常见的有:
1)Bourne Shell(/usr/bin/sh或/bin/sh)
2)Bourne Again Shell(/bin/bash)
3)C Shell(/usr/bin/csh)
4)K Shell(/usr/bin/ksh)
5)Shell for Root(/sbin/sh)
……
Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。
在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash。
“#!” 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。

运行 Shell 脚本的两种方法

例:新建一个文件 test.sh,扩展名为 sh(sh代表shell),扩展名并不影响脚本执行

1
2
#!/bin/bash
echo "Hello World !"

注:#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。
echo 命令用于向窗口输出文本。

1、作为可执行程序运行shell
将上面的代码保存为 test.sh,并 cd 到相应目录:

1
2
chmod +x ./test.sh  #使脚本具有执行权限
./test.sh #执行脚本

注意,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。

2、作为解释器参数
这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,如:

1
/bin/sh test.sh

这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

shell变量

定义变量时,变量名不加美元符号($,PHP语言中变量需要),如:

1
your_name="xiaogaigai"

注意:
1)变量名和等号之间不能有空格
2)命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
3)中间不能有空格,可以使用下划线(_)。
4)不能使用标点符号。
5)不能使用bash里的关键字(可用help命令查看保留关键字)
另外:除了显式地直接赋值,还可以用语句给变量赋值,如:

1
2
3
for file in `ls /etc`

for file in $(ls /etc)

以上语句将 /etc 下目录的文件名循环出来。

使用变量

1)使用一个定义过的变量,只要在变量名前面加美元符号即可,如:

1
2
3
your_name="qinjx"
echo $your_name
echo ${your_name}

2)变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

1
2
3
for skill in Ada Coffe Action sh; do
echo "I am good at ${skill}Script"
done

如果不给skill变量加花括号,写成echo “I am good at $skillScript”,解释器就会把$skillScript当成一个变量(其值为空),推荐给所有变量加上花括号,这是个好的编程习惯。
3)已定义的变量,可以被重新定义,如:

1
2
3
4
your_name="tom"
echo $your_name
your_name="alibaba"
echo $your_name

这样写是合法的,但注意,第二次赋值的时候不能写$your_name=”alibaba”,使用变量的时候才加美元符($)。

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
下面的例子尝试更改只读变量,结果报错:

1
2
3
4
#!/bin/bash
myUrl="http://www.google.com"
readonly myUrl
myUrl="http://www.runoob.com"

运行结果:

1
/bin/sh: NAME: This variable is read only.

删除变量

使用 unset 命令可以删除变量。语法:

1
unset variable_name

注:变量被删除后不能再次使用。unset 命令不能删除只读变量。
例:

1
2
3
4
#!/bin/sh
myUrl="http://www.runoob.com"
unset myUrl
echo $myUrl

以上实例执行将没有任何输出。

变量类型

运行shell时,会同时存在三种变量:
1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。

Shell 字符串

字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似。

单引号

1
str='this is a string'

单引号字符串的限制:
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号

1
2
3
your_name='runoob'
str="Hello, I know you are \"$your_name\"! \n"
echo -e $str

输出

1
Hello, I know you are "runoob"!

双引号的优点:
双引号里可以有变量
双引号里可以出现转义字符

拼接字符串

1
2
3
4
5
6
7
8
9
your_name="runoob"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2 $greeting_3

输出

1
2
hello, runoob ! hello, runoob !
hello, runoob ! hello, ${your_name} !

获取字符串长度

1
2
string="abcd"
echo ${#string} #输出 4

提取子字符串

以下实例从字符串第 2 个字符开始截取 4 个字符:

1
2
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo

查找子字符串

查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):

1
2
string="runoob is a great site"
echo `expr index "$string" io` # 输出 4

注意:

1
以上脚本中 ` 是反引号,而不是单引号 '。

Shell 数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。
类似于 C 语言,数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。

定义数组

在 Shell 中,用括号来表示数组,数组元素用”空格”符号分割开。定义数组的一般形式为:

1
数组名=(值1 值2 ... 值n)

例如:

1
array_name=(value0 value1 value2 value3)

或者

1
2
3
4
5
6
array_name=(
value0
value1
value2
value3
)

还可以单独定义数组的各个分量:

1
2
3
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

可以不使用连续的下标,而且下标的范围没有限制。

读取数组

读取数组元素值的一般格式是:

1
${数组名[下标]}

例如:

1
valuen=${array_name[n]}

使用 @ 符号可以获取数组中的所有元素,例如:

1
echo ${array_name[@]}

获取数组的长度

获取数组长度的方法与获取字符串长度的方法相同,例如:

1
2
3
4
5
6
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

shell注释

以 # 开头的行就是注释,会被解释器忽略。
通过每一行加一个 # 号设置多行注释,像这样:

1
2
3
4
5
6
7
8
9
#--------------------------------------------
# 这是一个注释
##### 用户配置区 开始 #####
#
#
# 这里可以添加脚本描述信息
#
#
##### 用户配置区 结束 #####

如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,每一行加个#符号太费力了,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。

多行注释

多行注释还可以使用以下格式:

1
2
3
4
5
:<<EOF
注释内容...
注释内容...
注释内容...
EOF

EOF 也可以使用其他符号:

1
2
3
4
5
6
7
8
9
10
11
:<<'
注释内容...
注释内容...
注释内容...
'

:<<!
注释内容...
注释内容...
注释内容...
!

Shell 传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……
例:
以下实例我们向脚本传递三个参数,并分别输出,其中 $0 为执行的文件名:

1
2
3
4
5
6
7
#!/bin/bash

echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";

为脚本设置可执行权限,并执行脚本,输出结果如下所示:

1
2
3
4
5
6
7
$ chmod +x test.sh 
$ ./test.sh 1 2 3
Shell 传递参数实例!
执行的文件名:./test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3

另外,还有几个特殊字符用来处理参数:

1
2
3
4
5
6
7
1)"$#":传递到脚本的参数个数
2)"$*":以一个单字符串显示所有向脚本传递的参数。如"$*"用「"」括起来的情况、以"$1 $2$n"的形式输出所有参数。
3)"$$":脚本运行的当前进程ID号
4)"$!":后台运行的最后一个进程的ID号
5)"$@":与"$*"相同,但是使用时加引号,并在引号中返回每个参数。如"$@"用「"」括起来的情况、以"$1" "$2""$n" 的形式输出所有参数。
6)"$-":显示Shell使用的当前选项,与set命令功能相同。
7)"$?":显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

例:

1
2
3
4
5
6
7
#!/bin/bash

echo "Shell 传递参数实例!";
echo "第一个参数为:$1";

echo "参数个数为:$#";
echo "传递的参数作为一个字符串显示:$*";

输出:

1
2
3
4
5
6
$ chmod +x test.sh 
$ ./test.sh 1 2 3
Shell 传递参数实例!
第一个参数为:1
参数个数为:3
传递的参数作为一个字符串显示:1 2 3

“$” 与“ $@” 区别:
相同点:都是引用所有参数。
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 “
“ 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。
例:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

echo "-- \$* 演示 ---"
for i in "$*"; do
echo $i
done

echo "-- \$@ 演示 ---"
for i in "$@"; do
echo $i
done

输出

1
2
3
4
5
6
7
8
$ chmod +x test.sh 
$ ./test.sh 1 2 3
-- $* 演示 ---
1 2 3
-- $@ 演示 ---
1
2
3

参考:http://www.runoob.com/linux/linux-shell-variable.html

操作系统-进程管理

1、程序
1.1 顺序程序
我们把一个具有独立功能的程序独占处理机直至最终结束的过程称为程序的顺序执行

特征:
– 顺序性:各条指令按照严格的顺序执行。
– 封闭性:程序执行得到的最终结果由给定的初始条
件决定,独占资源,不受外界影响。
– 可再现性:初始条件相同,则重复执行的结果相同
(与执行速度无关)。

1.2 并发程序
– 并发的目的:增强处理能力,提高资源利用率。
– 并发的含义:在一定时间内有多个程序同时处于运
行但尚未结束的状态,并且次序事先确定的

特征:
– 间断(异步)性:由于并发程序之间的制约,导致
“走走停停”,一个程序可能走到中途停下来;
– 失去封闭性:共享资源,受其他程序的影响。如:
一个程序写到存储器中的数据可能被另一个程序修改,
失去原有的不变特征。
– 失去可再现性:失去封闭性 ->失去可再现性;外
界环境在程序的两次执行期间发生变化,失去原有的可
重复特征。

2、进 程
2.1 定义:Process
程序在执行过程中分配和管理资源的基本单位
(这里的程序指的是一组操作序列,具有动态特性)。

2.2 程序与进程之间的区别:
– 程序是静态的,进程是动态的,强调执行过程
– 进程具有并行特征,即不考虑资源共享的情况下,
各个进程的执行是独立的,执行速度是异步的。而程序
不反映执行过程,不具有并行特征
– 进程是竞争系统资源的基本单位
– 不同的进程可以包含同一程序,只要对应的数据集
不同

2.3 进程的特征
(1)并发性。可以同其他进程一道向前推进。
(2)动态性。进程是程序的执行过程,动态产生,
动态消亡,状态变换。
(3)独立性。一个进程是一个相对完整的资源分配
单位。
(4)交往性。进程之间的相互作用。
(5)异步性。各个进程按照各自独立的、不可预知
的速度向前推进。

2.4 进 程 的 描 述
进程的静态描述由三部分组成:PCB,程序段,数据
集。
• PCB是系统感知进程的唯一实体;
• 程序描述进程所需要完成的功能;
• 而数据集是程序执行的对象

2.5 进程控制块(Process Control Block)
– 系统为了管理进程设置的一个专门的数据结构,用
它来记录进程的外部特征,描述进程的运动变化过程
– 系统利用PCB来控制和管理进程,所以PCB是系统感
知进程存在的唯一标志
– 进程与PCB是一一对应的

2.6 进 程 状 态 及 其 转 换
– 运行态(Running):
进程占有CPU,并在CPU上运行
– 就绪态(Ready):
一个进程已经具备运行条件,但由于无CPU暂
时不能运行的状态(当调度给其CPU时,立即可以
运行)
– 等待态(Waiting/Blocked):
指进程因等待某种事件的发生而暂时不能运行
的状态(即使CPU空闲,该进程也不可运行)

1)三状态图:
Image text

2)五状态图:
Image text

3)七状态图:
Image text

3、线 程
进程:资源分配单位(存储器、文件)和CPU调度(分派)单位。
又称为”任务(task) “

线程:作为CPU调度单位,而进程作为其他资源分配单位。一个进
程内的基本调度单位。(轻权进程)

–线程:有时称轻量级进程
• 进程中的一个运行实体
• 是一个CPU调度单位
• 只拥有必不可少的资源,如:线程状态、寄存器上下文和栈
• 同样具有就绪、阻塞和执行三种基本状态

线程的优点:减小并发执行的时间和空间开销(线程的创建、退
出和调度),因此容许在系统中建立更多的线程来提高并发程度(进
程间的并发到进程内的并发)。提高系统执行效率,减少处理机空转
时间。
• 线程的创建时间比进程短;
• 线程的终止时间比进程短;
• 同进程内的线程切换时间比进程短;
• 由于同进程内线程间共享内存和文件资源,可直接进行不通过
内核的通信

4、进程与线程比较
– 进程:
• 资源分配的基本单位(PCB);
• 抢占处理机的调度单位;
• 完整的虚拟地址空间;
• 由正文集,数据集和PCB组成;
• 进程切换时,涉及到有关资源信息的保存和地址空间的变化
• 进程调度与切换:操作系统内核完成;
– 线程:
• 与资源分配无关,与所属进程内的其他线程共享进程资源;
• 与所属进程内的其他线程共享同一地址空间;
• 由相关堆栈(系统栈或用户栈)寄存器和TCB组成,寄存器用来存放
存储在线程内的局部量;
• 线程切换时,不涉及到资源信息的保存和地址空间的变化,减少系统
的开销;
• 线程调度与切换:既可由操作系统内核完成,也可由用户程序进行;

线 程 的 分 类:
– 基本类型
• 用户级线程
• 系统级线程(核心级、内核级

4、进 程 控 制
– 所谓进程控制,就是系统使用一些具有特定功能的程序段来创建、撤销进程以及完成进程各状态间的转换,从而达到多进程高效率并发执行和协调,实现资源共享的目的。

– 原语:把系统下执行的某些具有特定功能的程序段称为原语,原语是不能被中断的。用于进程控制的原语有创建原语、撤销原语、阻塞原语、唤醒原语等等。

– 临界区:把系统中不允许同时多个进程访问的资源称为临界资源,而在进程中访问临界资源的那段程序称为临界区
注意:临界区不是资源,而是程序段

– 互斥:把不允许两个以上的共享某公有资源的并发进程同时进入临界区称为互斥
互斥的原则:
(1)空闲让进
(2)忙则等待
(3)有限等待
(4)让权等待

5、信 号量 和 P,V原 语
信号量(Semaphore):信号量是一种特殊的变量,它的表面形式是一个整数附加一个队列。
信号量用于管理临界区的公有资源,信号量sem是一个整数
• sem大于等于0时代表可供并发进程使用的资源实体数
• sem小于0时则表示正在等待使用临界区的进程数。

P 原 语:(堵塞条件:s.value < 0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
semaphore s;
P(s)
{
封锁中断;//该原语执行过程中不允许中断
s.value = s.value – 1
if (s.value < 0)
{
保护当前进程CPU现场;
该进程状态置为等待状态;
将该进程的PCB插入相应的等待队列末尾s.queue;
转进程调度;
}
开中断;
}

V 原 语:(唤醒条件:s.value <= 0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
semaphore s;
V(s)
{
封锁中断;
s.value = s.value + 1
if (s.value < = 0)
{
唤醒相应等待队列s.queue中的一个等待进程;
改变其状态为就绪态;
并将其插入就绪队列;
转进程调度;
}
开中断;
}

6、进 程 同 步
进程同步:把一组并发进程,因直接制约而相互发送消息进行互相合作,互相等待,使得各进程按一定的速度执行的过程称为进程间的同步
私 用 信 号 量:一般来说,也可以把各进程之间发送的消息作为信号量看待。与进程互斥时不同的是,这里的信号量只与制约进程及被制约进程有关,而不是与整组并发进程有关。因此,该信号量为私用信号量。
互斥时使用的信号量为公用信号量,同步时使用的信号量为私用信号量。

举例:司 机 — 售 票 员 问 题
Image text
Image text

7、管 程
定义:指关于共享资源的数据及在其上操作的一组过程或共享数据结构及其规定的所有操作

管程有如下几个要素:
(一)管程中的共享变量在管程外部是不可见的,外部只能通过调用管程中所说明的外部过程(函数)来间接地访问管程中的共享变量
(二)为了保证管程共享变量的数据完整性,规定管程互斥进入
(三)管程通常是用来管理资源的,因而在管程中应当设有进程等待队列以及相应的等待及唤醒操作

8、进 程 通 信
进程通信:进程之间互相交换信息的工作称之为进程通信IPC(InterProcess Communication)。
8.1 进程间通信的类型
– 低级通信:只能传递状态和整数值(控制信息),包括进程互斥和同步所采用的信号量机制。
– 高级通信:能够传送任意数量的数据,包括三类:共享存储区、管道、消息。
– 直接通信:信息直接传递给接收方,如管道。
间接通信:借助于收发双方进程之外的共享数据结构作为通信中转,如消息队列。

8.2 共享存储区通信机制
内存中开辟一个共享存储区,诸进程通过该区实现通信,这是进程通信中最快的方法。

8.3 管 道 通 信 机 制
管道(pipe)是连接读写进程的一个特殊文件,允许进程按先进先出方式传送数据。发送进程以字符流形式
把大量数据送入管道,接收进程从管道中接收数据,所以,也叫管道通信
Image text

8.4 消息传递机制
1)直接通信方式
在直接通信方式下,企图发送或接收消息的每个进程必须指出信件发给谁或从谁那里接收消息,可用send原语和receive原语为实现进程之间的通信
2)间接通信方式
采用间接通信方式时,进程间发送或接收消息通过一个信箱来进行,消息可以被理解成信件,每个信箱有一个唯一的标识符。

9、死锁
现象:
Image text

定义:一组进程中,每个进程都无限等待被该组进程中另一进程所占有的资源,因而永远无法得到资源,这种现象称为进程死锁,这一组进程就称为死锁进程
– 参与死锁的进程最少是两个(两个以上进程才会出现死锁)
– 参与死锁的进程至少有两个已经占有资源
– 参与死锁的所有进程都在等待资源
– 参与死锁的进程是当前系统中所有进程的子集

资源:
– 永久性资源:可以被多个进程多次使用(可重用资
源)
• 可抢占资源
• 不可抢占资源
– 临时性资源:只可使用一次的资源;如信号量,中断
信号,同步信号等(可消耗性资源)
• “申请–分配–使用–释放”模式

例 子:
Image text
Image text

10、产生死锁的四个必要条件

1
2
3
4
互斥使用(资源独占)
不可强占(不可剥夺)
请求和保持(部分分配,占有已分配)
循环等待(环路等待)

解决死锁的方法:

1
2
3
4
5
1) 不考虑此问题(鸵鸟政策)
2) 预防死锁(破坏死锁条件)
3) 避免死锁(分配过程中采用策略)
4) 检测死锁(允许发生死锁)
5) 解除死锁(与检测死锁配套)

1)鸵鸟政策
最简单的方法是象鸵鸟一样对死锁视而不见。

2) 死锁预防(破坏死锁条件)
在系统设计时确定资源分配算法,限制进程对资源的申请,从而保证不发生死锁。
具体的办法是破坏产生死锁的必要条件:
(1)破坏“不可剥夺”条件
一个进程在申请新资源的要求不能立即得到满足时,便处于等待状态。而一个处于等待状态的进程的全部资源可以被剥夺。该进程重新获得它原有的资源以及得到新申请的资源时,才能重新启动执行。

(2)破坏“请求和保持”条件
方法一:采用静态分配策略
每个进程在开始执行前就申请他所需要的所有资源,只有当系统能够把资源一次性分配,该进程才能执行。
缺点:资源浪费严重。
方法二:
如果进程已经占用资源同时再去申请资源,则它应该首先释放已占有的资源再重新申请新资源

(3)破坏“循环等待”条件
采用资源有序分配法:
把系统中所有资源编号,进程在申请资源时必须严格按资源编号的递增次序进行,否则操作系统不予分配;释放资源时,应按编号递减次序进行。

3) 死锁避免(分配过程中采用策略)
在系统运行过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统可能发生死锁,则不予分配,否则予以分配

与死锁预防的区别:
死锁预防是设法破坏产生死锁的必要条件,严格防止死锁的发生。而死锁避免则没有这么严格,它是一种动态策略

安全状态:
如果存在一个由系统中所有进程构成的安全序列P1,„Pn,则系统处于安全状态。
一个进程序列{P1,„,Pn}是安全的,如果对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和,系统处于安全状态,不会发生死锁。

不安全状态:
不存在一个安全序列,不安全状态不一定导致死锁

银行家算法:
银行家算法的基本思想是:
在安全状态下系统收到一个进程的资源请求后,先把资源试探性分配给它。在进程集合中找到剩余资源能满足最大需求量的进程,从而保证这个进程运行完毕并归还全部资源。这时,把这个进程从集合中去掉, 系统的剩余资源更多了,再反复执行上述步骤。
最后,检查进程集合,若为空表明本次申请可行,系统处于安全状态,可真正实施本次分配;否则,有进程执行不完,系统处于不安全状态,本次资源分配暂不实施,让申请进程等待。

银行家例子:第一次分配后的系统状态
Image text
Image text

新 的 系 统 状 态:
Image text

4)死锁检测
允许死锁发生,操作系统不断监视系统进展情况,判断死锁是否发生。
最常用的检测死锁的方法就是对资源分配图进行化简。
①在图中找一个请求边均能立即满足的一个进程顶点Pi;
②若找到了这样的Pi,则将与Pi相连的边全部删去,转①

如果化简后所有的进程顶点都成了孤立点,则称该图可完全化简,否则不可完全化简(是产生死锁的充分必要条件,非孤立点的进程处于死锁状态。

5)死锁解除
一般通过破坏循环等待条件
从死锁进程集合中选择一个或多个进程予以删除,并剥夺它们的资源给其它的进程使用。选择要删除的进程时,一般从优先级、已运行时间及已用多少资源等几个方面去考虑,使系统损失最小

6)死锁的综合处理
一般来说,无论哪种方法都无法适用于每类资源,可以把系统中的全部资源分成几大类,整体上采用有序资源分配法,再对每类资源根据其特点选择最合适的方法。
例如,系统中有以下几类资源:
①主存;
②作业资源(打印机、磁带驱动器、文件等);
③辅存。
将①②③类资源编号为1、2、3,按有序资源申请。对第①类
采用剥夺法;
对第②类采用死锁避免法;
对第③类采用静态资源分配法;

11、资 源 分 配 图
资源类(资源的不同类型)
-用方框表示
资源实例(存在于每个资源中)
-用方框中的黑圆点表示
进程
-用圆圈中加进程名表示
分配边:
资源实例→进程的一条有向边
申请边:
进程→资源类的一条有向边

11.1 有环有死锁
Image text

11.2 有环无死锁
Image text

11.3 死锁定理
如果资源分配图中没有环路,则系统中没有死锁,如果图中存在环路则系统中可能存在死锁
如果每个资源类中只包含一个资源实例,则环路是死锁存在的充分必要条件

操作系统-设备管理

1、概 述
1 、 I / O 的 特 点
(1)I/O性能经常成为系统性能的瓶颈,CPU性能越高,与I/O差距越大
(2)操作系统庞大复杂的原因之一是:资源多、杂,并发,均来自I/O
(3)异步性
处理机与I/O设备各自以不同的速度工作,可以并行工作,无需相互等待,通过中断方式或DMA方式交互。
(4)接口通用性
I/O设备与处理机连接通过通用接口。

1.1 设备的分类
(1)按使用特性分类
存储型设备:用来保存信息的设备,比如磁盘磁带等。
输入型设备:键盘、鼠标、物理传感设备
输出型设备:打印机、绘图仪(可保存)
输入输出型设备(交互型设备)

(2)按信息交换单位分类
块设备:
以数据块为单位存储、传输信息;比如磁盘、磁带、光盘等
字符设备:
以字符为单位存储、传输信息;不寻址,没有查找操作;比如键盘、鼠标、打印机等

(3)按外部设备的从属关系分
系统设备:
指操作系统生成时,登记在系统中的标准设备(如终端、打印机、磁盘机等);
用户设备:
指在系统生成时,未登记在系统中的非标准设备(如鼠标)。

(4)按资源分配角度分
独占设备:
在一段时间内只能有一个进程使用的设备,一般为低速I/O设备(如打印机,磁带等)
共享设备:
在一段时间内可有多个进程共同使用的设备,多个进程以交叉的方式来使用设备,其资源利用率高(如硬盘)
虚拟设备:
在一类设备上模拟另一类设备,常用共享设备模拟独占设备,目的是为了提高利用率

1.2 设 备 管 理 的 目 标 和 任 务
(1)按照用户的请求,控制设备的各种操作,完成I/O设备与内存之间的数据交换(包括设备分配与回收;设备中断处理;缓冲区管理),
最终完成用户的I/O请求
– 设备分配与回收
– 建立统一的独立于设备的接口
– 实现真正的I/O操作
– 处理外部设备的中断处理
– 管理I/O缓冲区,减少外设和内存及CPU之间的速度不匹配问题

(2)向用户提供使用外部设备的方便接口,使用户摆脱繁琐的编程负担

(3)充分利用各种技术(通道,中断,缓冲等)提高CPU与设备、设备与设备之间的并行工作能力,充分利用资源,提高资源利用率

2、数据传输控制
2.1 程序控制I/O技术
Image text
– 由处理器提供I/O相关指令来实现
– I/O处理单元处理请求并设置I/O状态寄存器相关位
– 不中断处理器,也不给处理器警告信息
– 处理器定期轮询I/O单元的状态,直到处理完毕
– I/O软件包含直接操纵I/O的指令
• 控制指令: 用于激活外设,并告诉它做什么
• 状态指令: 用于测试I/O控制中的各种状态和条件
• 数据传送指令: 用于在设备和主存之间来回传送数据

– 程序直接控制方式简单,不需要多少硬件支持,但是存在以下缺点:
• CPU和外设只能串行工作。由于CPU处理速度远快于外设,CPU的大量时间都处于循环测试等待状态,使得处理机利用率大大降低
• CPU在一段时间内只能和一台外设交换数据,从而不能实现设备之间的并行工作
• 由于程序直接控制方式通过不断测试状态位来控制数据传送,因此无法发现和处理其他设备的问题

2.2 中断驱动I/O技术
Image text
– 首先,进程发出指令启动外设,同时将状态寄存器中的中断允许位打开;
– 进程发出指令后,该进程放弃处理机,进程调度程序调度其他就绪进程占据处理机;
– 当输入完成时,I/O控制器通过中断请求线向CPU发出中断信号,CPU接收到信号后做相应处理(将缓冲寄存器中数据送到指定内存单元,
把阻塞进程唤醒,再返回到被中断进程继续执行)
– 在以后的“某个时刻”,被唤醒进程(就绪态)被进程调度程序选中重新占据处理机,从约定的内存单元中取数据进一步处理
(如果数据没完,重复)
– 优点:
• 不再循环测试状态位,提高CPU利用率且支持设备和设备以及设备和CPU之间的并行工作。
– 缺点:
• 由于数据缓冲寄存器较小,故中断次数较多,这仍将损耗大量CPU时间,同时有可能造成数据丢失(数据缓冲寄存器的数据来不及取走)

2.3 D M A 技 术
• 直接存储器访问(DMA:Direct Memory Access)
Image text
• 在外设和内存之间开辟直接的数据交换通路,目的是为了减少CPU的干预
• 自动控制数据在内存和I/O单元间的传送
• 大大提高处理I/O的效能
– 当处理器需要读写数据时给DMA控制单元发送一条命令。
– 处理器发送完命令后就可处理其它事情
– DMA控制器将自动管理数据的传送
– 当这个过程完成后,它会给处理器发一个中断
– 处理器只在开始传送和传送结束时关注一下即可
Image text

DMA方式与中断的主要区别:
– 中断方式是在数据缓冲寄存器满后,发中断请求,CPU进行中断处理
– DMA方式则是在所要求传送的数据块全部传送结束时要求CPU进行中断处理
– 大大减少了CPU进行中断处理的次数
– 中断方式的数据传送是由CPU控制完成的
– 而DMA方式则是在DMA控制器的控制下不经过CPU控制完成的

DMA 方式的局限性;
– 数据传送方向、内存始址、数据长度等都由CPU控制;并且每台设备需一个DMA控制器,当外设较多时控制会进一步复杂化。

3、通 道
– 通道又称为I/O处理机
– 引入通道的目的:
• 为了使CPU从I/O事务中解脱出来
• 为了提高CPU与设备、设备与设备之间的并行度

– 定义:
• 通道是独立于CPU的专门负责数据输入/输出传输工作的处理机,对外部设备实现统一管理,代替CPU对输入/输出操作进行控制,从而使输入,输出操作可与CPU并行操作。
– 通道有自己的通道指令。

通道分类:
1)字节多路通道
• 字节多路通道以字节为单位传输信息,它可以分时地执行多个通道程序。
– 主要连接以字节为单位的低速I/O设备。如打印机,终端。
– 以字节为单位交叉传输,当一台传送一个字节后,立即转去为另一台传送字节

2)选择通道
• 选择通道是以成组方式工作的,即每次传送一批数据,故传送速度很高。选择通道在一段时间内只能执行一个通道程序,
只允许一台设备进行数据传输

3)数组(成组)多路通道
• 它结合了选择通道传送速度高和字节多路通道能进行分时并行操作的优点。它先为一台设备执行一条通道指令,然后自动转接,
为另一台设备执行一条通道指令
– 主要连接高速设备

4、中断技术
– 中断机制是操作系统得以正常工作的最重要的手段
– 它使得OS可以捕获普通程序发出的系统功能调用
– 及时处理设备的中断请求
– 防止用户程序中破坏性的活动等等

4.1 中断的概念
• 指CPU对系统中或系统外发生随机事件的响应
• 如外部设备完成数据传输,出现异常等
• CPU对系统发生的某个事件作出的一种反应
– CPU暂停正在执行的程序,保留现场后自动转去执行相应事件的处理程序,处理完成后返回断点,继续执行被打断的程序
Image text

引入中断的目的
• 解决主机与外设的并行工作问题
• 提高可靠性
• 实现实时控制
– 特点:
• 中断是随机的
• 中断是可恢复的
• 中断是自动处理的
– 中断源:引起中断发生的事件
– 中断寄存器:记录中断
– 中断字:中断寄存器的内容

– 系统堆栈:
在内存开辟的一块区域,用于临时保存现场

– 关中断:某些情况下,尽管中断源发出了中断请求,但CPU的PSW的中断允许位已被消除,从而不允许CPU响应中断
– 开中断:当设置PSW的中断允许位时,CPU可以接受中断
– 开中断和关中断是为了保证某些程序执行的原子性
– 中断屏蔽:指系统用软件方式有选择地封锁部分中断而允许其他部分的中断仍能得到响应。比如PSW可以设置优先级,可以屏蔽掉优先级低的中断

4.2 中断类型
强迫性中断
• 正在运行的程序所不期望的,由于某种硬件故障或外部请求引起的
– 自愿性中断
• 用户在程序中有意识安排的中断,是由于用户在编制程序时因为要求操作系统提供服务,有意使用“访管”指令或系统调用,使中断发生

强迫性中断
• 输入/输出(I/O)中断:主要来自外部设备通道
• 程序性中断:运行程序中本身的中断(如溢出,缺页中断,缺段中断,地址越界)
• 时钟中断
• 硬件故障

自愿性中断
• 执行I/O,创建进程,分配内存
• 信号量操作,发送/接收消息

– 外中断
来自处理机和内存外部的中断,包括I/O中断、时钟中断等,也称为中断
– 内中断
与外中断相反,比如溢出、系统调用等,称为陷阱

在多级中断系统中,可能同时有多个中断请求,CPU接受中断优先级为最高的那个中断
– 忽略其中断优先级较低的那些中断
– 在一些机器中,中断优先级按中断类型划分:
• 机器故障中断的优先级最高
• 程序中断和访问管理程序中断次之
• 外部中断更次之
• 输入输出的优先级最低

中断和陷阱的区别:
陷阱的优先级一般高于中断
• 陷阱通常由正在执行的指令引起的(比如系统调用),而中断则是由与现行指令无关的中断源引起的
• 陷阱处理程序的服务为当前进程所用,而中断处理程序提供的服务则不是为了当前进程的
• CPU在执行完一条指令后,下一条指令之前响应中断,而在一条指令执行中也可以响应陷阱

中断处理过程:
(1)设备给处理器发一个中断信号
(2)处理器处理完当前指令后响应中断,延迟非常短(前提是处理器没有关闭中断)
(3)保存中断点的程序执行上下文环境,这通常包括程序状态字PSW,程序计数器PC中的下一条指令位置,一些寄存器的值,它们通常保存在系统栈中,处理器状态被切换到管态
(4)处理器根据中断源查询中断向量表,获得与该中断相联系的处理程序入口地址,并将PC置成该地址,处理器开始一个新的指令周期,控制转移到中断处理程序
(5)中断处理程序开始工作,包括检查I/O相关的状态信息,操纵I/O设备或者在设备和主存之间传送数
据等等
(6)中断处理结束时,处理器检测到中断返回指令,被中断程序的上下文环境从系统栈中被恢复,处理器状态恢复成原来的状态。
(7)PSW和PC被恢复成中断前的值,处理器开始一个新的指令周期,中断处理结束

多个中断的处理:
– 若中断处理过程中又发生中断,引起多中断处理问题
– 两种策略方法:
– 第一种:
• 处理一个中断时禁止中断,对任何新中断置之不理,在这期间发生的中断将保持挂起状态
– 实现方法:
• 在任何中断处理前使用禁止中断指令
• 在处理结束后开放中断指令
• 所有中断严格按照发生顺序处理
• 不考虑中断紧急程度,无法达到较严格时间要求

– 第二种:中断按照优先度分级
• 允许优先级高中断打断优先级低的中断处理过程
• 这样中断优先级技术将引起中断处理的嵌套
• 只要合适地定义中断的优先级别,方法一的弊端大都可以克服

典型的中断处理:
1)时钟中断
• 维护软件时钟:系统有若干个软件时钟,控制定时任务以及进程的处理器时间配额,时钟中断需要维护、定时更新这些软件时钟
• 处理器时间调度:维护当前进程时间片软件时钟,并在当前进程时间片到时以后运行调度程序选择下一个被调度的进程
• 控制系统定时任务:通过软件时钟和调度程序定时激活一些系统任务,如监测死锁、系统记帐、系统审计等

2)硬件故障中断
– 保存现场,使用一定警告手段,提供些辅助诊断信息
– 在高可靠系统中,中断处理程序还要评估系统可用性,尽可能恢复系统

5、缓冲技术
– 引入原因
• 缓和CPU和I/O设备速度失配的矛盾
• 减少CPU中断频率,放宽对中断响应时间的限制。
• 提高CPU和设备之间的并行性
– 缓冲的实现方法
• 采用硬件缓冲器,比如数据缓冲寄存器
• 在内存中划出专用缓冲区存放数据

5.1 缓冲种类
根据系统设置的缓冲器的个数,可把缓冲技术分为:
(1) 单缓冲
单缓冲是在设备和处理机之间设置一个缓冲器。设备和设备之间不能通过单缓冲达到并行操作。

(2) 双缓冲
双缓冲只是一种说明设备和设备、CPU和设备并行操作的简单模型,并不能用于实际系统中的并行操作。

(3) 多缓冲( 缓冲队列 、 环形缓冲队列)
多缓冲是把多个缓冲区连接起来组成两部分,一部分专门用于输入,另一部分专门用于输出的缓冲结构。

(4) 缓冲池
缓冲池则是把多个缓冲区连接起来统一管理,既可用于输入又可用于输出的缓冲结构

缓冲池的结构
缓冲池由多个缓冲区组成。而一个缓冲区由两部分组成: 一部分是用来标识该缓冲区和用于管理的缓冲首部,另一部分是用于存放数据的缓冲体。
这两部分有一一对应的映射关系。对缓冲池的管理是通过对每一个缓冲器的缓冲首部进行操作实现的
系统把各缓冲区按其使用状况连成三种队列:
• 空白缓冲队列em,其队首指针为F(em),队尾指针为L(em);
• 装满输入数据的输入缓冲队列in,其队首指针为F(in),队尾指针为L(in);
• 装满输出数据的输出缓冲队列out,其队首指针为F(out),队尾指针为L(out)。

6、设备分配
设备分配用数据结构:
– 设备控制表DCT
系统中每个设备都必须有一张DCT,且在系统生成时或在该设备和系统连接时创建,但表中的内容则根据系统执行情况而被动态地修改。

– 控制器控制表COCT
– COCT也是每个控制器一张,它反映I/O控制器的使用状态以及和通道的连接情况等(在DMA方式时,该项是没有的)。

– 通道控制表CHCT
该表只在通道控制方式的系统中存在,也是每个通道一张。包括通道标识符、通道忙/闲标识、等待获得
该通道的进程等待队列的队首指针与队尾指针等

– 系统设备表SDT
系统设备表SDT整个系统一张,它记录已被连接到系统中的所有物理设备的情况,并为每个物理设备设一表项

6.1 设备分配
(1) 先请求先分配
– 当有多个进程对某一设备提出I/O请求时,或者是在同一设备上进行多次I/O操作时,系统按提出I/O请求的先后顺序,将进程发出的I/O请求命令排成队列,其队首指向被请求设备的DCT。

(2) 优先级高者先分配
系统能从I/O请求队列队首取下一个具有最高优先级进程发来的I/O请求命令,并将设备分配给发出该命令的进程
Image text

操作系统的硬件环境

1、中央处理器(CPU)
构成:处理器由运算器、控制器、一系列的寄存器以及
高速缓存构成
– 运算器:实现指令中的算术和逻辑运算
– 控制器:负责控制程序运行的流程
– 寄存器:具有最快的访问速度
– 高速缓存:处于CPU和物理内存之间,访问速度快
于内存,低于寄存器

两类寄存器:
(1)用户编程寄存器
—数据寄存器
—地址寄存器
—条件码寄存器

(2)控制状态寄存器
—程序计数器PC
—指令寄存器IR
—程序状态字PSW
—中断现场保护寄存器

处理器的状态:
– 管态:操作系统管理程序运行的状态,
较高的特权级别,又称为特权态(特态)、
系统态
– 目态:用户程序运行时的状态,较低的
特权级别,又称为普通态(普态)、用户态
– 特权指令:只能由操作系统使用的指令

管态和目态的差别:
– 处理器处于管态时:
• 全部指令(包括特权指令)可以执行
• 可使用所有资源
• 并具有改变处理器状态的能力
– 处理器处于目态时:
• 只有非特权指令能执行

CPU 状 态 的 转 换:
目态——–管态
其转换的唯一途径就是通过中断

管态——–目态
可用设置PSW实现

2、程 序 状 态 字 P S W
特殊寄存器,用以表明处理器当前的工作状态。

通常包括以下状态码:
(1)CPU的工作状态码——指明管态还是目态
(2)条件码——反映指令执行后的结果特征
(3)中断屏蔽码——指出是否允许中断

3、存储系统
Image text

存 储 访 问 局 部 性 原 理:
提高存储系统效能关键点:程序存储访问局部性原理,处理器主要和存储器的局部打交道

4、中 断 机 制
中断机制是操作系统得以正常工作的最重
要的手段:
–它使得OS可以捕获普通程序发出的系统功能调用
–及时处理设备的中断请求
–防止用户程序中破坏性的活动等等

中 断:– CPU对系统发生的某个事件作出的一种反应
– CPU暂停正在执行的程序,保留现场后自动转去执行
相应事件的处理程序,处理完成后返回断点,继续执行
被打断的程序

Image text

– 引入中断的目的
• 解决主机与外设的并行工作问题
• 提高可靠性
• 实现实时控制
– 特点:
• 中断是随机的
• 中断是可恢复的
• 中断是自动处理的

中 断 的 有 关 概 念:
– 中断源:引起中断发生的事件
– 中断寄存器:记录中断
– 中断字:中断寄存器的内容
– 系统堆栈:
在内存开辟的一块区域,用于临时保存现场

中断类型:
– 强迫性中断
• 正在运行的程序所不期望的,由于某种硬件故障
或外部请求引起的
– 自愿性中断
• 用户在程序中有意识安排的中断,是由于用户在
编制程序时因为要求操作系统提供服务,有意使用
“访管”指令或系统调用,使中断发生

强迫性中断
• 输入/输出(I/O)中断:主要来自外部设备通道
• 程序性中断:运行程序中本身的中断
(如溢出,缺页中断,缺段中断,地址越界)
• 时钟中断
• 硬件故障

自愿性中断
• 执行I/O,创建进程,分配内存
• 信号量操作,发送/接收消息
Image text

5、缓 冲 技 术
缓冲区是硬件设备之间进行数据传输时,
用来暂存数据的一个存储区域

–缓冲技术三种用途:
• 处理器与主存储器之间
• 处理器和其它外部设备之间
• 设备与设备之间的通信

–目的:解决部件之间速度不匹配的问题、使得部件并
行工作