- 2024-06-30
-
回复了主题帖:
免费申请 | 最新一代树莓派(Raspberry Pi 5)!
积极支持一下活动!好活动!
- 2024-05-18
-
发表了主题帖:
《python编程快速上手》第5篇:python之字典和结构化数据
“字典”是许多值的集合,但不像列表的索引,字典的索引可以使用许多不同的数据类型,不只是整数。字典的索引被称为“键”,键及其关联的值称为“键-值对”。字典中的项是不排序的,下面就字典与列表差异展开话题。
尽管字典是不排序的,但可以用任意值作为键,这一点让开发人员能够用强大的方式来组织数据。假定你希望程序保存你朋友的生日数据,就可以使用一个字典,用名字作为键,用生日作为值。在Mu编辑工具中,新建一个.py文件,并在编辑器窗口输入以下代码,完成编辑后运行、测试。
birthdays = {'Aloy':'Apr 5','Bob':'Dec 15','Carol':'Mar 18'}
while True:
print('Enter a name:(blank to quit)')
name = input()
if name == '':
break
if name in birthdays:
print(birthdays[name] + 'is the birthday of' + name)
else:
print('I do not have birthday information for '+ name)
print('What is their birthday?')
bday = input()
birthdays[name] = bday
print('Birthday database updated.')
运行后,测试效果如下:
由代码可知,创建了一个初始的字典,将它保存在birthdays。用if name in birthdays中的in关键字,可以查看输入的名字是否作为键存在于字典中,就像查看列表一样。如果该名字在字典中,那么就可以用birthdays[]来访问关联的值。如果不存在,则可以用同样的"birthdays[]=name"方式来赋值操作添加到字典中。
字典中有3个方法,分别对应与字典的键、值和键-值对,即keys()、values()和items()方法。这些方法返回的值并不是真正的列表,它们不能被修改,没有append()方法。利用keys()、values()和items()方法,循环分别可以迭代键、值和键-值对。items()方法返回的是dict_items值包含的是键和值的元组。
以上并不会返回得到真正的列表,如果想通过这些方法得到一个真正的列表,就要把类似列表的返回值传递给list()函数。
跟列表一样,in或not in操作符可以检查值是否存在与列表中,也可以利用这些操作符,检查某个键或值是否存在于字典中。
访问一个键的值之前,必须通过检查该键是否存在于字典中,才能合法访问,这显得很是麻烦,字典中有提供一个get()方法,有带两个参数,分别为要取得其值的键,以及当键不存在时返回的备用值。
在字典中常常需要将某个键设置一个默认值,当该键没有任何值时使用它需要重新设定,setdefault()方法提供了一种方法,可以在一行中完成这样的设置需求,该方法第一个参数是要检查的键,第二个参数是当该键不存在时要设置的值,如果该键确实存在,那么setdefault()方法就会返回键的值,效果如下交互环境。
setdefault()方法是一个很好的快捷方式,可以确保有一个键存在,下面的一个小程序可以诠释,轻松实现计算一个字符串中每个字符出现的次数。在Mu工具的编辑窗口输入以下代码,保存后运行即可输出结果。
message = 'It was a bright cold day in April, and the clocks were striking thirteen.'
count = {}
for character in message:
count.setdefault(character, 0)
count[character] = count[character] + 1
print(count)
运行输出的结果如下:
{'I': 1}
{'I': 1, 't': 1}
{'I': 1, 't': 1, ' ': 1}
{'I': 1, 't': 1, ' ': 1, 'w': 1}
{'I': 1, 't': 1, ' ': 1, 'w': 1, 'a': 1}
{'I': 1, 't': 1, ' ': 1, 'w': 1, 'a': 1, 's': 1}
{'I': 1, 't': 1, ' ': 2, 'w': 1, 'a': 1, 's': 1}
{'I': 1, 't': 1, ' ': 2, 'w': 1, 'a': 2, 's': 1}
{'I': 1, 't': 1, ' ': 3, 'w': 1, 'a': 2, 's': 1}
{'I': 1, 't': 1, ' ': 3, 'w': 1, 'a': 2, 's': 1, 'b': 1}
{'I': 1, 't': 1, ' ': 3, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1}
{'I': 1, 't': 1, ' ': 3, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1}
{'I': 1, 't': 1, ' ': 3, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1}
{'I': 1, 't': 1, ' ': 3, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1}
{'I': 1, 't': 2, ' ': 3, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1}
{'I': 1, 't': 2, ' ': 4, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1}
{'I': 1, 't': 2, ' ': 4, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1}
{'I': 1, 't': 2, ' ': 4, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1}
{'I': 1, 't': 2, ' ': 4, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1}
{'I': 1, 't': 2, ' ': 4, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 1}
{'I': 1, 't': 2, ' ': 5, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 1}
{'I': 1, 't': 2, ' ': 5, 'w': 1, 'a': 2, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2}
{'I': 1, 't': 2, ' ': 5, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2}
{'I': 1, 't': 2, ' ': 5, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1}
{'I': 1, 't': 2, ' ': 6, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 1, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1}
{'I': 1, 't': 2, ' ': 6, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 2, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1}
{'I': 1, 't': 2, ' ': 6, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 2, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1, 'n': 1}
{'I': 1, 't': 2, ' ': 7, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 2, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1, 'n': 1}
{'I': 1, 't': 2, ' ': 7, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 2, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1, 'n': 1, 'A': 1}
{'I': 1, 't': 2, ' ': 7, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 1, 'i': 2, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1, 'n': 1, 'A': 1, 'p': 1}
{'I': 1, 't': 2, ' ': 7, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 2, 'i': 2, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1, 'n': 1, 'A': 1, 'p': 1}
{'I': 1, 't': 2, ' ': 7, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 1, 'd': 2, 'y': 1, 'n': 1, 'A': 1, 'p': 1}
{'I': 1, 't': 2, ' ': 7, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 2, 'y': 1, 'n': 1, 'A': 1, 'p': 1}
{'I': 1, 't': 2, ' ': 7, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 2, 'y': 1, 'n': 1, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 2, ' ': 8, 'w': 1, 'a': 3, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 2, 'y': 1, 'n': 1, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 2, ' ': 8, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 2, 'y': 1, 'n': 1, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 2, ' ': 8, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 2, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 2, ' ': 8, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 2, ' ': 9, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 3, ' ': 9, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 1, 'c': 1, 'o': 1, 'l': 2, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 3, ' ': 9, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 1, 'o': 1, 'l': 2, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1}
{'I': 1, 't': 3, ' ': 9, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 1, 'o': 1, 'l': 2, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1}
{'I': 1, 't': 3, ' ': 10, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 1, 'o': 1, 'l': 2, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1}
{'I': 1, 't': 3, ' ': 10, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 2, 'o': 1, 'l': 2, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1}
{'I': 1, 't': 3, ' ': 10, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 2, 'o': 1, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1}
{'I': 1, 't': 3, ' ': 10, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 2, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1}
{'I': 1, 't': 3, ' ': 10, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1}
{'I': 1, 't': 3, ' ': 10, 'w': 1, 'a': 4, 's': 1, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1, 'k': 1}
{'I': 1, 't': 3, ' ': 10, 'w': 1, 'a': 4, 's': 2, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1, 'k': 1}
{'I': 1, 't': 3, ' ': 11, 'w': 1, 'a': 4, 's': 2, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1, 'k': 1}
{'I': 1, 't': 3, ' ': 11, 'w': 2, 'a': 4, 's': 2, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 1, 'k': 1}
{'I': 1, 't': 3, ' ': 11, 'w': 2, 'a': 4, 's': 2, 'b': 1, 'r': 2, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 2, 'k': 1}
{'I': 1, 't': 3, ' ': 11, 'w': 2, 'a': 4, 's': 2, 'b': 1, 'r': 3, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 2, 'k': 1}
{'I': 1, 't': 3, ' ': 11, 'w': 2, 'a': 4, 's': 2, 'b': 1, 'r': 3, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 1}
{'I': 1, 't': 3, ' ': 12, 'w': 2, 'a': 4, 's': 2, 'b': 1, 'r': 3, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 1}
{'I': 1, 't': 3, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 3, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 1}
{'I': 1, 't': 4, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 3, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 1}
{'I': 1, 't': 4, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 3, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 1}
{'I': 1, 't': 4, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 4, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 1}
{'I': 1, 't': 4, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 4, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 4, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 5, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 2, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 4, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 5, 'g': 1, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 4, ' ': 12, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 5, 'g': 2, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 4, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 5, 'g': 2, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 5, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 5, 'g': 2, 'h': 2, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 5, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 5, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 5, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 4, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 5, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 5, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 6, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 5, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 3, 'k': 2}
{'I': 1, 't': 6, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 5, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 4, 'k': 2}
{'I': 1, 't': 6, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 5, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 3, 'A': 1, 'p': 1, ',': 1, 'e': 5, 'k': 2}
{'I': 1, 't': 6, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 5, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 4, 'A': 1, 'p': 1, ',': 1, 'e': 5, 'k': 2}
{'I': 1, 't': 6, ' ': 13, 'w': 2, 'a': 4, 's': 3, 'b': 1, 'r': 5, 'i': 6, 'g': 2, 'h': 3, 'c': 3, 'o': 2, 'l': 3, 'd': 3, 'y': 1, 'n': 4, 'A': 1, 'p': 1, ',': 1, 'e': 5, 'k': 2, '.': 1}
这样看起来比较费劲,可以引入pprint模块,这样就可以美观输出结果了,显得更直观、更优雅。
输出的结果如下:
{' ': 13,
',': 1,
'.': 1,
'A': 1,
'I': 1,
'a': 4,
'b': 1,
'c': 3,
'd': 3,
'e': 5,
'g': 2,
'h': 3,
'i': 6,
'k': 2,
'l': 3,
'n': 4,
'o': 2,
'p': 1,
'r': 5,
's': 3,
't': 6,
'w': 2,
'y': 1}
>>>
小结一下:通过本章节的学习,掌握了关于字典方面的相关知识,字典是非常有用的,因为开发者可以把一些项(键)映射到另一些项(值),不像列表只包含一系列有序的值。字典中的值是通过方括号访问的,像列表一样。字典不使用整数索引,而是用各种数据类型如整型、浮点型或元组作为键,通过将程序中的值组织成数据结构,开发者可以创建真实世界事物的模型。字典和结构化数据的学习,让我认识到Python这类解释性语言显得更宽泛化,不像C或C++,不拘束于数据类型,不用考虑占用字节长度,提供了另一种编程思想。此次分享告一段落,与大家共勉。
- 2024-05-12
-
发表了主题帖:
《python编程快速上手》第4篇:python之列表应用
Python中的“列表”是一个值,包含由多个值构成的序列。“列表值”是指列表本身,而不是指列表值之内的那些值。列表中的值也称为“表项”。变量名后面方括号内的整数被称为“索引”。列表中第一个值的索引是0,第二个值的索引是1,第三个值的索引是2,依次类推。
由上可知,如果使用的索引超出了列表值的个数,Python将给出IndexError错误信息,索引只能是整数,不能是浮点数。第一个索引表明使用哪个列表值,第二个索引表明该列表值中的值。比如spam[0][1]即第一个列表中的第二值,如果只使用一个索引,程序将输出该索引的完整列表值。
虽然索引从0开始并向上增长,但也可以用负整数作为索引。整数值-1指的是列表中的最后一个索引,-2指的是列表中倒数第二个索引。索引可以从列表中取得单个值一样,“切片”可以从列表中取得多个值,结果是一个新列表。切片用一对方括号来表示它的起止,像索引一样,它有两个由冒号分隔的整数。在一个切片中,第一个整数是切片开始处的索引,第二个整数是切片结束处的索引。切片向上增长,直至第二个索引的值,注但不包括它。作为快捷方法,可以省略切片中冒号两边的一个索引或者两个索引。省略第一个索引相当于使用索引0或从列表的开始处开始。省略第二个索引相当于使用列表的长度,意味着切片直接至列表末尾。
使用len()函数取得列表的长度
使用索引改变列表中的值,方法如下图所示:
列表可以连接和复制,就像字符串一样,使用del语句可以实现从列表中删除某一个值。
此书还介绍了使用列表的基本方法,有用于循环中,in和not in操作符可以确定一个值是否在列表中。多重赋值技巧让你在一行代码中,用列表中的值为多个变量赋值。列表可以与enumerate()函数,random.choice()和random.shuffle()函数一起使用。使用index()方法在列表中查找值,用append()方法和insert()方法在列表中添加值。
使用remove()方法从列表中删除值,使用sort()方法将列表中的值排序,使用reverse()方法反转列表中的值。
此次学习到此结束,受益匪浅,觉得Python的确很灵活,很人性化。
- 2024-05-03
-
发表了主题帖:
《python编程快速上手》第3篇:python之控制流与函数处理
Python的控制流语句的开始部分通常是“条件”,接下来是一个代码块。条件只是在控制流语句的上下文中更具体的名称,条件总是求值为一个布尔值:True或False。代码块的判定遵循以下3条规则:①、缩进增加时,代码块开始。②、代码块可以包含其他代码块。③、缩进减少为零,或与外面包围代码块对齐,代码块就此结束。
控制流语句与C语言大体相同,如if语句、else语句、elif语句、以及while循环语句、break语句、continue语句,for循环中引入了range()函数,则可控制代码块执行的次数,如需提前结束程序,调用sys.exit()函数。
Python程序中可以调用一组基本的函数,这称为“内置函数”,包括print()、input()和len()函数。Python也包括一组模块,称为“标准库”。每个模块都是一个Python程序,包含一组相关的函数,可以嵌入到程序之中,采用import关键字可以导入相关模块,与C语言中的include类似。
书籍循序渐进,有控制流代码块引出函数知识,由def语句开始讲解,这相当于C语音中的宏定义#define,书籍对“定义”“调用”“传递”“参数”和“变元”等术语进行辨析。“调用栈”是Python记住每个函数调用后在哪里返回执行的方式。调用栈不是存储在程序的变量中,而是由python在后台处理它。当程序调用一个函数时,python在调用栈的顶部创建一个“帧对象”。帧对象保存了最初函数调用的行号,使得python可以记住返回的位置。如果进行了另一个函数调用,python会将另一个帧对象放在调用栈中,且在前一个帧对象之上。当函数调用返回时,python从栈顶部删除一个帧对象,并将执行转移至保存在其中的行号。值得注意的是,帧对象始终是从栈顶部添加和删除的,而不是从其他任意位置。
Python中函数存在局域和全局作用域,在被调用函数内赋值的变元和变量。处于该函数的“局部作用域”中。在所有函数之外赋值单变量,处于“全局作用域”中。处于局部作用域中的变量,被称为“局部变量”。处于全局作用域中的变量,被称为“全局变量”。一个变量必属于其中一种,不能既是局部的又是全局的。
一个函数被调用时,就创建一个局部作用域。在这个函数内赋值的所用变量,存在于该局部作用域内。该函数返回时,这个局部作用域就被销毁了,这些变量就丢失了。下次调用这个函数时,局部变量不会记得该函数上次被调用时它们保存的值。作用域非常重要,理由如下:
一、全局作用域中的代码不能使用任何局部变量。
二、局部作用域中的代码可以访问全局变量。
三、一个函数的局部作用域中的代码,不能使用其他局部作用域中的变量。
四、在不同的作用域中,你可以用相同的名字命名不同的变量。
综上所述,函数是将代码逻辑分组的主要方式,函数中的变量存在于它们自己的局部作用域内,一个函数中的代码不能直接影响其他函数中变量的值。这限制了哪些代码才能改变变量的值,对于调式代码是很有帮助的。函数是很好的工具,可帮助编程人员组织代码,它们以参数的形式接收输入,以返回值的形式产生输出。它们内部的代码不会影响其他函数中的变量。
- 2024-05-02
-
发表了主题帖:
《python编程快速上手》第2篇:python基础语法篇
Python编程语言有许多语法结构、标准库函数和交互式开发环境功能。由于之前并没有接触Python脚本程序,但通过对此书的前几章内容学习,感觉与C语言有点类似,但也有许多地方不同之处。
Python数学操作符的优先级与数学中类似。**操作符表指数,*、/、//和%操作符,从左到右;+、- 操作符最后求值,同样的可以使用括号来改变通常的优先级。Python中的数据类型也有整型、浮点型、字符串类型连接和复制小结中,当在操作两个整型或浮点型值时,+是相加操作符,但在两个字符串之间,它则表式将字符串连接起来,成为“字符串连接”操作符。
Python语法中,“变量”就像计算机内存中的一个盒子,其中可以存放一个值。变量名命名也是有规则的,①、只能是一个词,不带空格。②、只能包含字母、数字和下划线“_”字符。③、不能以数字开头。变量名有区分大小写的。
接着引出了print()、input()、len()、str()、int()以及float()函数,可谓是受益匪浅,print()和input()函数处理简单的文本输出到屏幕和键盘输入;len()函数接收一个字符串,并求值为该字符串中字符的数目。str()、int()和float()函数将传入它们的值求值为字符串、整数或浮点数形式。
每个知识点,文中都有相关的程序举例,并且附带详细的程序剖析,可以让小白无门槛学习Python语法知识。书籍中还附带一本《Python学习效率手册》笔记本,8个星期的随学随记,“一个人走得慢,一群人学得稳”,经过基本语法的学习,觉得Python语言的编写格式有严格的讲究,包括首字符的缩进,没有C语言那么随意。
-
发表了主题帖:
《python编程快速上手》第1篇:开启扉页
本书的全名《Python编程快速上手---让繁琐工作自动化 第2版》,收到有段时间了,次书籍在异步社区有提供出售。书籍采用薄膜包装,防尘防水。书籍的正面、底面纸质华丽。
拆开新鲜薄膜,打开书籍的扉页,此书由中国工信出版集团,人民邮电出版社,字数618千字,2023年7月河北第15次印刷,本书分为两部分,第一部分介绍了基本的Python编程概念,第二部分介绍了一些不同的任务。前言部分作者申明了此书中的程序运行在Python3上,有一部分程序如果在Python2上也行不能正常运行。笔者早在之前就已经安装好python了,在计算机终端中输入:python -v,可知版本为3.11。
下载基于windows x64版本Mu编辑器软件,并傻瓜式安装,默认是安装到C盘个人本地路径下,并以python编程风格启动Mu。
此书集各大编程爱好者之精华,用户可通过扫码添加异步助手,获取本书籍的配套资源,如广大读者有发现错误,可到异步社区搜索此书名,进入本书页面,“提交勘误”,审核通过后可获得相应的奖励。前言篇到此结束,后续精彩继续。
- 2024-04-12
-
回复了主题帖:
读书入围名单:《Python编程快速上手 让繁琐工作自动化 第2版》
个人信息无误,确认可以完成阅读分享计划。
- 2024-03-28
-
回复了主题帖:
《原子Linux驱动开发》下载mp135开发板的SDK
这不是发阅读心得吗?怎么在拉取SDK包,调试代码了啊
- 2024-03-02
-
发表了主题帖:
基于MSPM0L1306 LaunchPad的板卡温度传感器实测
本帖最后由 yin_wu_qing 于 2024-3-2 23:39 编辑
根据宣传海报可知,LP-MSPM0L1306 LaunchPad开发套件是基于MSPM0L1306的易用型评估模块, 包含在MSPM0L1306 M0+ MCU 平台上开始开发所需要的全部资源,包括用于编程、调试和能量测量的板载调试探针。 该板包含三个按钮、两个LED(其中一个是RGB LED)、模拟温度传感器和光传感器。此次就板载的“TMP6131DECT”温度传感器进行检测。
通过上方的电路原理图可知,如果需要对温度传感器进出检测,则需要将J15通过跳线帽短接,形成电阻上拉部分电路。于此同时,还需要将J1的1、2脚短接在一起,这样才能将温度传感器连接到电路中。如果短接2、3脚,则是连接到光敏传感器。
关于这款温度传感器,TI官方提供的数据手册如下:
如何使用该温度传感器,需要借助ADC转换接口,TI专门提供了设计工具,使用该工具可设计多款传感器的基本配置代码。
这里使用“Thermistor Design Tool - Voltage Bias”文档来设置,如下图所示,设置好对应的参数,该工具可得出转换接口函数。
通过CCS从SDK中导出“adc_to_uart_LP_MSPM0L1306_nortos_ticlang”工程,然后双击工程中的“adc_to_uart.syscfg”文件,则可调出sysconfig GUI界面,将ADC的GPIO管脚设置成PA15。
部分的参考代码如下:
编译后自动生成的“ti_msp_dl_config.c”源码如下:
#include "ti_msp_dl_config.h"
/*
* ======== SYSCFG_DL_init ========
* Perform any initialization needed before using any board APIs
*/
SYSCONFIG_WEAK void SYSCFG_DL_init(void)
{
SYSCFG_DL_initPower();
SYSCFG_DL_GPIO_init();
/* Module-Specific Initializations*/
SYSCFG_DL_SYSCTL_init();
SYSCFG_DL_UART_0_init();
SYSCFG_DL_ADC12_0_init();
}
SYSCONFIG_WEAK void SYSCFG_DL_initPower(void)
{
DL_GPIO_reset(GPIOA);
DL_UART_Main_reset(UART_0_INST);
DL_ADC12_reset(ADC12_0_INST);
DL_GPIO_enablePower(GPIOA);
DL_UART_Main_enablePower(UART_0_INST);
DL_ADC12_enablePower(ADC12_0_INST);
delay_cycles(POWER_STARTUP_DELAY);
}
SYSCONFIG_WEAK void SYSCFG_DL_GPIO_init(void)
{
DL_GPIO_initPeripheralOutputFunction(GPIO_UART_0_IOMUX_TX, GPIO_UART_0_IOMUX_TX_FUNC);
}
SYSCONFIG_WEAK void SYSCFG_DL_SYSCTL_init(void)
{
//Low Power Mode is configured to be SLEEP0
DL_SYSCTL_setBORThreshold(DL_SYSCTL_BOR_THRESHOLD_LEVEL_0);
DL_SYSCTL_setSYSOSCFreq(DL_SYSCTL_SYSOSC_FREQ_BASE);
DL_SYSCTL_setMCLKDivider(DL_SYSCTL_MCLK_DIVIDER_DISABLE);
}
static const DL_UART_Main_ClockConfig gUART_0ClockConfig = {
.clockSel = DL_UART_MAIN_CLOCK_BUSCLK,
.divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1
};
static const DL_UART_Main_Config gUART_0Config = {
.mode = DL_UART_MAIN_MODE_NORMAL,
.direction = DL_UART_MAIN_DIRECTION_TX,
.flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE,
.parity = DL_UART_MAIN_PARITY_NONE,
.wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS,
.stopBits = DL_UART_MAIN_STOP_BITS_ONE
};
SYSCONFIG_WEAK void SYSCFG_DL_UART_0_init(void)
{
DL_UART_Main_setClockConfig(UART_0_INST, (DL_UART_Main_ClockConfig *) &gUART_0ClockConfig);
DL_UART_Main_init(UART_0_INST, (DL_UART_Main_Config *) &gUART_0Config);
/*
* Configure baud rate by setting oversampling and baud rate divisors.
* Target baud rate: 115200
* Actual baud rate: 115211.52
*/
DL_UART_Main_setOversampling(UART_0_INST, DL_UART_OVERSAMPLING_RATE_16X);
DL_UART_Main_setBaudRateDivisor(UART_0_INST,UART_0_IBRD_32_MHZ_115200_BAUD,
UART_0_FBRD_32_MHZ_115200_BAUD);
/* Configure FIFOs */
DL_UART_Main_enableFIFOs(UART_0_INST);
DL_UART_Main_setTXFIFOThreshold(UART_0_INST, DL_UART_TX_FIFO_LEVEL_1_2_EMPTY);
DL_UART_Main_enable(UART_0_INST);
}
/* ADC12_0 Initialization */
static const DL_ADC12_ClockConfig gADC12_0ClockConfig = {
.clockSel = DL_ADC12_CLOCK_ULPCLK,
.divideRatio = DL_ADC12_CLOCK_DIVIDE_8,
.freqRange = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32,
};
SYSCONFIG_WEAK void SYSCFG_DL_ADC12_0_init(void)
{
DL_ADC12_setClockConfig(ADC12_0_INST, (DL_ADC12_ClockConfig *) &gADC12_0ClockConfig);
DL_ADC12_configConversionMem(ADC12_0_INST, ADC12_0_ADCMEM_0,
DL_ADC12_INPUT_CHAN_9, DL_ADC12_REFERENCE_VOLTAGE_VDDA, DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0, DL_ADC12_AVERAGING_MODE_DISABLED,
DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED);
DL_ADC12_setSampleTime0(ADC12_0_INST,500);
/* Enable ADC12 interrupt */
DL_ADC12_clearInterruptStatus(ADC12_0_INST,(DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED));
DL_ADC12_enableInterrupt(ADC12_0_INST,(DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED));
DL_ADC12_enableConversions(ADC12_0_INST);
}
“adc_to_uart.c”代码实现如下,这里将uart发送接口函数改成printf打印,这样可以在CCS中的Console窗口实时打印输出温度传感器采样的温度值。
#include "ti_msp_dl_config.h"
#include <stdio.h>
#include <math.h>
volatile bool gCheckADC;
volatile uint16_t gADCResult;
volatile float temperature;
float VBias = 3.30;
unsigned int ADC_BITS = 4096;
float VTEMP = 0;
float THRM_TEMP = 0;
float Thermistor(int raw_ADC)
{
// THRM calculations - 4th order polynomial regression
VTEMP = 0;
int THRM_ADC = raw_ADC;
float THRM_A0 = -4.232811E+02 ;
float THRM_A1 = 4.728797E+02 ;
float THRM_A2 = -1.988841E+02 ;
float THRM_A3 = 4.869521E+01 ;
float THRM_A4 = -1.158754E+00 ;
VTEMP = (VBias/ADC_BITS) * THRM_ADC;
THRM_TEMP =(THRM_A4 * powf( VTEMP,4)) + (THRM_A3 * powf( VTEMP,3)) + (THRM_A2 * powf( VTEMP,2)) + (THRM_A1 * VTEMP) + THRM_A0;
return THRM_TEMP;
}
int main(void)
{
SYSCFG_DL_init();
NVIC_EnableIRQ(ADC12_0_INST_INT_IRQN);
gCheckADC = false;
while (1) {
DL_ADC12_startConversion(ADC12_0_INST);
while (false == gCheckADC) {
__WFE();
}
/*
* UART is 8 bits and the ADC result is 16 bits
* Split the ADC result into 2 then send via UART.
*/
gADCResult = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
// uint8_t lowbyte = (uint8_t)(gADCResult & 0xFF);
// uint8_t highbyte = (uint8_t)((gADCResult >> 8) & 0xFF);
temperature = Thermistor(gADCResult);
printf("ADC=%d, temperature=%.2f\n", gADCResult, temperature);
// DL_UART_Main_transmitData(UART_0_INST, highbyte);
// DL_UART_Main_transmitData(UART_0_INST, lowbyte);
gCheckADC = false;
DL_ADC12_enableConversions(ADC12_0_INST);
}
}
void ADC12_0_INST_IRQHandler(void)
{
switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST)) {
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
gCheckADC = true;
break;
default:
break;
}
}
开发板的硬件连接如下图所示,其中需注意J1处的跳线帽设置。
将编译后的程序下载到开发板后,即可观察到温度传感器实时采样的温度值,此时此刻南方的温度感觉有点冷。如果将手指靠近TMP6131,温度值会立马上升,足见其灵敏度比较高。此次分享内容告一段落,感谢来访阅读,欢迎留言交流。
- 2024-02-26
-
发表了主题帖:
基于MSPM0L1306 LaunchPad的四相五线式步进电机驱动
承接上期的“点灯控制”帖子,这期使用MSPM0L1306 LaunchPad开发板对常见的四相五线式步进电机进行驱动。电机型号:28BYJ48,本次实验是基于“pwm_led_driver_LP_MSPM0L1306_nortos_ticlang”例程展开的,该工程实现LED2指示灯的呼吸效果。
首先打开“Code Composer Studio”工具,导入SDK,勾选需要加载的参考例程,这里选择采用TI官方编译器的“pwm_led_driver_LP_MSPM0L1306_nortos_ticlang”例程。
在工程的根目录下,手动创建“bsp_uln2003.c”与“bsp_uln2003.h”源文件,双击打开“pwm_led_driver.syscfg”文件,调出sysconfig图形设置界面。
根据功能需求,使用到4个GPIO口,实现对四相的控制。
添加4个有效的GPIO口,如下图所示。
编译后,在“Generated Source”目录下自动更新“ti_msp_dl_config.c”与“ti_msp_dl_config.h”源文件。
根据“ti_msp_dl_config.h”源文件,可清晰得知道系统自动分配的有效GPIO口。
由上述系统分配,在“bsp_uln2003.h”源文件中完善管脚的定义。
在工程Properties属性中,包含“bsp_uln2003.h”头文件所在的路径。
接着编写初始化GPIO口及驱动代码,部分代码展示如下:
#include "bsp_uln2003.h"
#include "ti_msp_dl_config.h"
void ULN2003_GPIO_Init(void)
{
DL_GPIO_enableOutput(GPIOA,ULN2003_GPIO_PIN_A);
DL_GPIO_enableOutput(GPIOA,ULN2003_GPIO_PIN_B);
DL_GPIO_enableOutput(GPIOA,ULN2003_GPIO_PIN_C);
DL_GPIO_enableOutput(GPIOA,ULN2003_GPIO_PIN_D);
DL_GPIO_clearPins(GPIOA,ULN2003_GPIO_PIN_A);
DL_GPIO_clearPins(GPIOA,ULN2003_GPIO_PIN_B);
DL_GPIO_clearPins(GPIOA,ULN2003_GPIO_PIN_C);
DL_GPIO_clearPins(GPIOA,ULN2003_GPIO_PIN_D);
}
#include "ti_msp_dl_config.h"
#include "oled.h"
#include "bsp_uln2003.h"
int pwm_count = 1800; // initial ccr count based on 10% duty cycle
int dc = 10; // initialized to 10 % duty cycle
int mode = 1; // 1 = up (will indicate you need to increase dc), 0 = down (will
// decrease dc)
#define STEPMOTOR_DIRECTION 0 // 1:顺时针 0:逆时针
#define STEPMOTOR_SPEED 10 // 速度,该值越小,速度越快,最小不能小于10
// 速度,该值越小,速度越快,最小不能小于10
uint8_t speed=STEPMOTOR_SPEED;
// 转动圈数:28BYJ-48步进电机的步距角度为5.625/64,即每64个脉冲转5.625度
// 要转一圈需要360/5.625*64=4096个脉冲。
// 8个节拍控制:A->AB->B->BC->C->CD->D->DA
uint16_t step_motor[8]={0xFC7F,0xFCFF,0xFCBF,0xFDBF,0xFD3F,0xFF3F,0xFE3F,0xFE7F};
/**
* 函数功能: 输出一个数据给ULN2003从而实现向步进电机发送一个脉冲
* 输入参数: step:序列号,选择step_motor数组对应的数据
* direction:方向选择
* 可选值:0:顺时针
* 1:逆时针
* 返 回 值: 无
*/
static void step_motor_pulse(uint8_t step,uint8_t direction)
{
uint8_t temp=step;
if(direction==0)
{
temp=8-step;
}
switch(temp)
{
case 0:
A_ON();B_OFF();C_OFF();D_OFF();
break;
case 1:
A_ON();B_ON();C_OFF();D_OFF();
break;
case 2:
A_OFF();B_ON();C_OFF();D_OFF();
break;
case 3:
A_OFF();B_ON();C_ON();D_OFF();
break;
case 4:
A_OFF();B_OFF();C_ON();D_OFF();
break;
case 5:
A_OFF();B_OFF();C_ON();D_ON();
break;
case 6:
A_OFF();B_OFF();C_OFF();D_ON();
break;
case 7:
A_ON();B_OFF();C_OFF();D_ON();
break;
default:
break;
}
}
void motor_run(void)
{
static uint8_t count=0,step=0;
static uint16_t pulse_count=0;
for(int time=4500; time>0; time--)
{
count++;
if(count==speed)
{
step_motor_pulse(step,STEPMOTOR_DIRECTION);
step++;
pulse_count++;
if(step==8) step=0;
count=0;
}
if(pulse_count==4096)
{
pulse_count=0;
}
delay_cycles(28000);
}
}
int main(void) {
SYSCFG_DL_init();
ULN2003_GPIO_Init();
NVIC_EnableIRQ(PWM_0_INST_INT_IRQN);
DL_TimerG_startCounter(PWM_0_INST);
while (1) {
motor_run();
__WFI();
}
}
void PWM_0_INST_IRQHandler(void) {
switch (DL_TimerG_getPendingInterrupt(PWM_0_INST)) {
case DL_TIMER_IIDX_LOAD:
if (dc <= 10) {
mode = 1;
} // if reached lowest dc (10%), increase dc
else if (dc >= 90) {
mode = 0;
} // if reached highest dc (90%), decrease dc
if (mode) {
pwm_count -= 10;
dc += 1;
} // up
if (!mode) {
pwm_count += 10;
dc -= 1;
} // down
DL_TimerG_setCaptureCompareValue(PWM_0_INST, pwm_count,DL_TIMER_CC_1_INDEX);
break;
default:
break;
}
}
编译后,直接debug即可将程序下载到开发板中,硬件连线如下:
基本实现了步进电机的驱动,逆时针旋转的演示效果如下所示,此次分享告一段落,谢谢EEWorld提供的评测机会,精彩再续。
[localvideo]0670379b944961843b2144479e0e8b39[/localvideo]
- 2024-02-20
-
发表了主题帖:
基于MSPM0L1306 LaunchPad的点灯控制
MSPM0L1306 LaunchPad开发套件主控为MSPM0L1306器件,提供高达64KB的嵌入式闪存程序存储器和高达4KB的SRAM。它们包含精度为±1%的高速片上振荡器,无需外部晶体。其他特性包括3通道DMA、16位和32位CRC加速器,以及各种高性能模拟外设。如何入手MSPM0L1306 LaunchPad开发套件呢?官方提供了参考短视频。
按照官方推荐的说明文档,①、首先需要准备好TI的官方IDE,即CCS。②、其次下载安装好基于MSPM0L1306的SDK。③、然后需要下载安装好图形化代码生成工具SysConfig。
登录TI官方论坛网站,下载好软件安装包,安装CCS编译工具,具体步骤如下:
下载好基于windows平台下的“mspm0_sdk_1_30_00_03”软件包,并安装好。
下载好“sysconfig-1.19.0_3426-setup”,安装MSPM0的图形化代码生成工具SysConfig。
以上3个必备软件都安装好了,等于基本环境已经构建好,接着需要将SDK与SysConfig导入到CCS工具中。
导入SDK中基于TI官方交叉编译器的点灯例程,点击编译。编译通过,然后使用Micro USB数据线与电脑相连接,如是首次连接,电脑的右下角会弹出提示安装XDS110驱动的对话框。
安装完成后,设备管理器中会自动出现两个串口端口。
此时直接点击CCS中的Debug按钮图标,会弹出XDS110 USB Debug需要升级的提示。
选择update后,进入到自动升级状态,升级完成后再给开发板重新上电。
在CCS中执行debug步骤,开发板进入调试阶段。点击全速运行,则会发现板上的LED1红色指示灯会一定频率闪烁。
void main_blinky(void)
{
/* Create the queue. */
xQueue = xQueueCreate(mainQUEUE_LENGTH, sizeof(uint32_t));
if (xQueue != NULL) {
/*
* Start the two tasks as described in the comments at the top of this
* file.
*/
xTaskCreate(
prvQueueReceiveTask, /* The function that implements the task. */
"Rx", /* The text name assigned to the task - for debug only as it is not used by the kernel. */
configMINIMAL_STACK_SIZE, /* The size of the stack to allocate to the task. */
(void *)
mainQUEUE_RECEIVE_PARAMETER, /* The parameter passed to the task - just to check the functionality. */
mainQUEUE_RECEIVE_TASK_PRIORITY, /* The priority assigned to the task. */
NULL); /* The task handle is not required, so NULL is passed. */
xTaskCreate(prvQueueSendTask, "TX", configMINIMAL_STACK_SIZE,
(void *) mainQUEUE_SEND_PARAMETER, mainQUEUE_SEND_TASK_PRIORITY,
NULL);
/* Start the tasks. */
vTaskStartScheduler();
}
/*
* If all is well, the scheduler will now be running, and the following
* line will never be reached. If the following line does execute, then
* there was insufficient FreeRTOS heap memory available for the idle
* and/or timer tasks to be created. See the memory management section on
* the FreeRTOS web site for more details.
*/
for (;;)
;
}
/*-----------------------------------------------------------*/
static void prvQueueSendTask(void *pvParameters)
{
TickType_t xNextWakeTime;
const unsigned long ulValueToSend = 100UL;
/* Check the task parameter is as expected. */
configASSERT(((unsigned long) pvParameters) == mainQUEUE_SEND_PARAMETER);
/* Initialize xNextWakeTime - this only needs to be done once. */
xNextWakeTime = xTaskGetTickCount();
for (;;) {
/*
* Place this task in the blocked state until it is time to run again.
* The block time is specified in ticks, the constant used converts
* ticks to ms. While in the Blocked state this task will not consume
* any CPU time.
*/
vTaskDelayUntil(&xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS);
/*
* Send to the queue - causing the queue receive task to unblock and
* toggle the LED. 0 is used as the block time so the sending operation
* will not block - it shouldn't need to block as the queue should always
* be empty at this point in the code.
*/
xQueueSend(xQueue, &ulValueToSend, 0U);
}
}
/*-----------------------------------------------------------*/
static void prvQueueReceiveTask(void *pvParameters)
{
unsigned long ulReceivedValue;
static const TickType_t xShortBlock = pdMS_TO_TICKS(50);
/* Check the task parameter is as expected. */
configASSERT(
((unsigned long) pvParameters) == mainQUEUE_RECEIVE_PARAMETER);
for (;;) {
/*
* Wait until something arrives in the queue - this task will block
* indefinitely provided INCLUDE_vTaskSuspend is set to 1 in
* FreeRTOSConfig.h.
*/
xQueueReceive(xQueue, &ulReceivedValue, portMAX_DELAY);
/*
* To get here something must have been received from the queue, but
* is it the expected value? If it is, toggle the LED.
*/
if (ulReceivedValue == 100UL) {
/*
* Blip the LED for a short while so as not to use too much
* power.
*/
DL_GPIO_togglePins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
vTaskDelay(xShortBlock);
DL_GPIO_togglePins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
ulReceivedValue = 0U;
}
}
}
允许Debug后,编译完成后的程序通过XDS110调试器下载到开发板中,此时板上的LED显示状态如下图所示,LED1红灯闪烁。
硬件连接只需一个Micro USB即可实现板子的在线调试。
此次分享到处结束,参考MSPM0L1306入门指南手册可以快速搭建开发环境,熟悉官方主推的CCS集成开发工具,后续再细究MSPM0L1306外设驱动性能。
- 2024-02-19
-
发表了主题帖:
基于MSPM0L1306 LaunchPad的开箱评测
年前收到了MSPM0L1306 LaunchPad开发套件,MSPM0L1306是一款32位Arm®Cortex®-M0+ CPU,频率高达32MHz,该板卡上集成三个按钮、两个LED(其中一个是RGB LED)、模拟温度传感器和光传感器。收到的板卡是采用TI经典颜色的小纸盒包装。
主核心板采用防静电袋封装,附带一根MicroUSB的数据线,纸盒中还有几张关于板卡的管脚分布图及操作说明书,内容言简意赅,步骤有序。可以根据说明书快速获取到有关该板卡的开发资源。
再来看看核心主板,主板具有外部编程选项的板载XDS110调试探针,可用于超低功耗调试的EnergyTrace技术,支持使用GPIO和XDS110调用BSL。
官方推荐使用“TI Code Composer Studio IDE 或 IAR Embedded Workbench IDE”,板上采用多处跳线帽连接各功能接口,靠近Micro USB接口的MSP432是XDS110调试器主控MCU,开发板上的位置概览图如下:
板子的整体方框图如下:
开发板的底面虽然没有集成电子元器件,由于有焊接点,因此为了避免短路,板上的四个角边有预留定位孔,收到板卡时有提供两个定位胶塞卡扣,与两排的母座排插端子齐平,可作为支柱。板上的丝印显示网址:www.ti.com/launchpad
此次分享到此告一段落,关于板卡的SDK的详细资料,可跳转至:MSPM0 SDK 文档。部分参考资料如下:
- 2024-01-25
-
回复了主题帖:
阅读打卡终点站: 进程管理之调试与案例分析—— 《奔跑吧Linux内核(第2版)卷1》
如何查看进程的调度信息?
有时候我们需要查看进程相关的调度信息,如进程的nice信息、优先级、调度策略、vruntime以及量化计算能力等信息。在Linux的proc目录中,为每个进程提供一个独立的目录,该目录包含了与进程相关的信息。下图是进程ID为6839的proc目录。
在进程proc目录里,看到和进程调度相关的节点为sched,可使用cat命令来读取这个节点的信息。
上图中可获取到如下信息:
进程名称为bash,ID是6839,线程有1个;进程的优先级为120;当前进程的虚拟时间(vruntime)是82.644603ms;总运行时间是9658850ms;进程发生过0次迁移,82次进程上下文切换,其中主动调度有74次,被抢占调度有8次;进程的权重se.load.weight和se.runnable_weight相等,都是1048576。注意这两个值均为原本的权重值乘以1024,进程优先级为120,nice值为0,它原本的权重值为1024;当前时刻,进程se.avg.load_sum值和se.avg.runnable_load_sum相等,都是260;进程的se.avg.load_avg和se.avg.runnable_load_avg是相等的,都是5。对于该进程来说,它的量化负载的最大值就等于它的权重值,即1024,这是在100%占用CPU的情况下得到的。进程的量化计算能力(se.avg.util_avg)为5。
Linux内核还提供一个与调度信息相关的数据结构sched_statistics,其中包含了非常多和调度相关的统计信息。要查看这些统计信息,需要打开CONFIG_SCHEDSTATS配置选项,另外还需要打开sched_schedstats节点。
重新查看ID为6839的进程调度信息,我们会发现里面多了很多统计信息,见下图所示:
如何查看CFS的调度信息?
Linux内核在调度方面实现了一些调试接口,为开发者提供窥探调度器内部信息的接口。在proc目录下面有一个sched_debug节点,其信息如下图:
Sched调试信息的版本号:v0.11;Linux内核版本号:4.15.0-45-generic;内核时间(ktime)的值;sched_clock和cpu_clk的值;当前jiffies值;调度相关的sysctl_sched的值:调度周期sysctl_sched_latency为6ms;调度最小粒度为0.75ms;唤醒的最小粒度为1ms;fork调用完成之后禁止子进程先运行。
输出的所有进程的相关信息如下:
如何查看调度域的拓扑关系?
在理解SMP负载均衡机制的过程中,CPU的拓扑关系是一个难点。Linux内核新增一个调节点来帮助开发者理解(详见kernel/sched/topology.c文件)。在编译内核时,不仅需要打开CONFIG_SCHED_DEBUG选项,还需要在内核启动参数里传递sched_debug参数。在内核启动之后,可以通过dmesg命令来得到CPU拓扑关系。由于虚拟机设置了单核,因此拓扑结构此时显示单核。
假设在一个双核处理器的系统中,在Shell界面下运行test程序,CPU0的就绪队列中有4个进程,而CPU1的就绪队列中有1个进程。test程序和这5个进程的nice值都为0。
——请画出test程序在内核空间的运行流程。
——若干时间之后,CPU0和CPU1的就绪队列如何变化?
站在用户空间的角度,在Shell界面运行test程序,Shell程序会调用fork()系统调用函数来创建一个新进程,然后调用exec()系统调用函数来装载test程序,因此新进程便开始运行test程序。站在用户空间的角度看问题,我们只能看到test程序被运行了,但是我们看不到新进程是如何创建的、它会添加到哪个CPU里、它是如何运行的,以及CPU0和CPU1之间如何做负载均衡等。
运行test程序的流程如上图所示,其中的操作步骤如下:
调用系统调用fork()来创建一个新进程。
使用_do_fork()创建新进程
创建新进程的task_struct数据结构。
复制父进程的task_struct数据结构到新进程。
复制父进程相关的页表项到新进程。
设置新进程的内核栈。
父进程调用wake_up_new_task()尝试去唤醒新进程
调用调度类的select_task_rq(),为新进程寻找一个负载最轻的CPU,这里选择CPU1。
调用调度类的enqueue_task()把新进程添加到CPU1的就绪队列里。
CPU1重新选择一个合适的进程来运行。
每次时钟节拍到来时,scheduler_tick()会调用调度类的task_tick()检查是否需要重新调度。Check_preempat_tick()会做检查,当需要重新调度时会设置当前进程的thread_info中的TIF_NEED_RESCHED标志位。假设这时CPU1准备调度新进程,就会设置当前进程的thread_info中TIF_NEED_RESCHED标志位。
在中断返回前会检查当前进程是否需要调度。如果需要调度,调用preempt_schedule_irq()来切换进程运行。
调度器的schedule()函数会调用调度类的pick_next_task()来选择下一个最合适运行的进程。在该场景中,选择新进程。
switch_mm()切换父进程和新进程的页表。
在CPU1上,switch_to()切换新进程来运行。
运行新进程。
新进程第一次运行时会调用ret_from_fork()函数。
返回用户空间运行Shell程序。
Shell程序调用exec()来运行test程序,最终新进程变成了test进程。
实现负载均衡
在每个时钟节拍到来时,检查是否需要触发软中断来实现SMP负载均衡,即调用scheduler_tick()->trigger_load_balance()。下一次实现负载均衡的时间点存放在就绪队列的next_balance成员里。
触发SCHED_SOFTIRQ软中断。
在软中断处理函数run_rebalance_domains()里,从当前CPU开始遍历CPU拓扑关系,从调度域的低层往高层遍历调度域,并寻找有负载不均匀的调度组。本例子中的CPU拓扑关系很简单,只有一层MC层级的调度域。
CPU0对应的调度域是domain_mc_0,对应的调度组是group_mc_0;CPU1对应的调度域是domain_mc_1,对应的调度组是group_mc_1。CPU0的调度域domain_mc_0管辖CPU0和CPU1,其中group_mc_0和group_mc_1这两个调度组会被链接到domian_mc_0的一个链表中。同理,CPU1的调度域domain_mc_1管理着group_mc_1和group_mc_0这两个调度组。
假设当前运行的CPU是CPU1,也就是说,运行run_rebalance_domains()函数的CPU为CPU1,那么在当前MC的调度域(domain_mc_1)里找哪个调度组是最繁忙的。很容易发现CPU0的调度组(group_mc_0)是最繁忙的,然后计算需要迁移多少负载到CPU1上才能保持两个调度组负载平衡。
从CPU0迁移部分进程到CPU1。
-
回复了主题帖:
测评入围名单(最后一批):年终回炉,FPGA、AI、高性能MCU、书籍等42个测品
个人信息无误,确认可以完成评测计划
- 2024-01-19
-
回复了主题帖:
阅读打卡第八站:进程管理之调度与负载均衡 ——《奔跑吧Linux内核(第2版)卷1》
请简述进程优先级、nice值和权重之间的关系。
操作系统中经典的进程调度算法是基于优先级调度的。优先级调度的核心思想是把进程按照优先级进行分类,紧急的进程优先级高,不紧急、不重要的进程优先级低。调度器总是从就绪队列中选择优先级高的进程进行调度,而且优先级高的进程分配的时间片会比优先级低的进程长,这体现一种等级制度。Linux系统最早采用nice值来调整进程的优先级。nice值的思想是要对其他进程友好,降低优先级来支持其他进程消耗更多的处理器时间。它的范围是-20 ~ +19,默认值是0。nice值越大,优先级反而越低;nice值越低,优先级越高。nice值-20表示这个进程是非常重要的,优先级最高;而nice值19则表示允许其他进程比这个线程优先享有宝贵的CPU时间,这也是nice值的由来。内核使用0~139的数值表示进程的优先级,数值越小,优先级越高。优先级0~99给实时进程使用,100~139给普通进程使用。另外,在用户空间中有一个传统的变量nice,它用于映射普通进程的优先级,即100~139。
请简述CFS是如何工作的。
CFS调度器抛弃以前固定时间片和固定调度周期的算法,采用进程权重值的比重来量化和计算实际运行时间。引入虚拟时钟的概念,每个进程的虚拟时间是实际运行时间相对nice值为0的权重的比例值。进程按照各自不同的速率比在物理时钟节拍内前进。nice值小的进程,优先级高且权重大,其虚拟时钟比真实时钟跑得慢,但是可以获得比较多的运行时间;反之,nice值大的进程,优先级低,权重也低,其虚拟时钟比真实时钟跑得快,获得比较少的运行时间。CFS调度器总是选择虚拟时钟跑得慢的进程,类似一个多级变速箱,nice值为0的进程是基准齿轮,其他各个进程在不同变速比下相互追赶,从而达到公正公平。
CFS中vruntime是如何计算的?
在CFS中有一个计算虚拟时间的核心函数calc_delta_fair(),它的计算公式vruntime=(delta_exec*nice_0_weight)/weight。其中,delta_exec为实际运行时间,nice_0_weight为nice为0的权重值,weight表示该进程的权重值。在update_curr()函数中,完成了该值的计算,此时,为了计算高效,将计算方式变成了乘法和移位vruntime= (delta_exec*nice_0_weight*inv_weight)>>shift,其中inv_weight=2^32/weight,是预先计算好存放在prio_to_wmult中的。
- 2024-01-17
-
回复了主题帖:
领取审核名单(第五批): 辞旧,年底清仓,100+板卡来袭,有缘来领
个人信息无误,已知晓需自己支付邮费,使用E金币支付,谢谢!
-
回复了主题帖:
EEWorld 社区 2023年度人物颁奖
个人信息确认无误,感谢论坛!
-
发表了主题帖:
一起读《奔跑吧Linux内核(第2版)卷1:基础架构》之进程管理与调度
本帖最后由 yin_wu_qing 于 2024-1-17 11:53 编辑
一起读《奔跑吧Linux内核(第2版)卷1:基础架构》之进程管理
本书籍的第八、九章节谈及进程管理之调度与调试。从操作系统定义来讲,进程是运行中的程序。若操作系统中有且只有一个进程,则不需要考虑优先级和调度等问题,因为整个CPU都是这个进程的。
正常情况下,操作系统中不能只用一个进程,而是有成千上万个进程,这些进程为操作系统提供各种不同的服务,一个CPU服务多个进程,这样的话那如何为多个进程提供公平的服务呢?这关系到进程的本质,进程(线程)是用于分配CPU时间(CPU资源)的基本单位。
这么多的进程(线程)在一个CPU中执行,谁先执行谁后运行,书籍中引出了进程优先级的问题,优先级高的进程会占用更多的CPU资源,而优先级低的进程获得CPU资源就相对较少些。优先级的高低由nice值、进程时间片、进程虚拟时间、就绪队列量化负载、权重等因素组成。
进程管理需要用到调度器,Linux内核发展了三代调度器,调度器的出现是因为需求总是大于供应,所以需要进行调度。__schedule()是调度器的核心函数,作用是让调度器选择和切换到一个合适进程并运行。在操作系统中把当前正在运行的进程挂起并且恢复以前的某个进程的执行,这个过程称为进程切换或者上下文切换。Linux内核实现进程切换的核心函数是context_switch(),所谓上文是指已执行过的进程指令和相关数据;下文指待执行的指令和数据。
调度器在设计和选择过程中面临很多挑战。调度器设计目标根据不同的应用场景而不同,没有一种调度器能适用所有场景,因此内核默认提供了5个调度器,Linux内核使用struct sched_class来对调度器进行抽象,如下图所示:
Stop调度器(stop_sched_class):优先级最高的调度类,可以抢占其他所有进程,不能被其他进程抢占;
Deadline调度器(dl_sched_class):使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;
RT调度器(rt_sched_class):实时调度器,为每个优先级维护一个队列;
CFS调度器(cfs_sched_class):完全公平调度器,采用完全公平调度算法,引入虚拟运行时间概念;
IDLE-Task调度器(idle_sched_class):空闲调度器,每个CPU都会有一个idle线程,当没有其他进程可以调度时,调度运行idle线程。
小结一下:调度程序主要依靠几个函数来完成调度工作,如schedule()、schedule_tick()、hrtick()、wake_up_process()函数。
schedule()函数,是进程调度的核心函数,整体流程如下:
时钟中断处理程序中调用schedule_tick()函数,时钟中断是调度器的脉搏,内核依靠周期性的时钟来决定CPU的控制权,框架如下:
高精度时钟调度,与周期性调度类似,不同点在于周期调度的精度为ms级别,而高精度调度的精度为ns级别,不过高精度时钟调度需要有相对应的硬件支持才行,框架如下:
唤醒进程时调用wake_up_process()函数,被唤醒的进程可能抢占当前的进程,整体流程如下:
以上几个调度处理函数是从书本中的第八、九章节中,延伸出来的各调度执行流程图。此书在进程调试、案例场景方面介绍也非常详细,值得后续细细品读。
- 2024-01-14
-
发表了主题帖:
一起读《奔跑吧Linux内核(第2版)卷1:基础架构》之写时复制技术
一起读《奔跑吧Linux内核(第2版)卷1:基础架构》之写时复制技术
本书籍的第7章节就进程管理中的基本概念展开讲述。进程是程序执行的一个实例,进程是执行中的程序,即一个程序加载到内存后变成了进程。然后根据最新版的POSIX标准谈及进程创建fork()和execve()函数族,进程终止wait()、waitpid()、kill()、以及exit()函数族。由fork()引申vfork()和clone()两个原语。
一个简单程序的执行,会调用fork()来创建一个新的进程,然后会调用execve()函数来执行这个所谓的新程序。fork()通过写时复制技术复制当前进程的相关信息来创建一个全新的子进程。此时此刻,子进程和父进程在各自的进程地址空间执行,但是共享相同的内容。
早期创建新进程会复制父进程所拥有的所有资源,因此效率很低。每次创建子进程时都要把父进程的进程地址空间中的内容复制到子进程,但是子进程不一定全盘接收,有时甚至完全不用父进程的资源。子进程在调用了execve()函数之后,很可能会脱离父进程的共享资源。由此引出了写时复制技术。
写时复制技术就是父进程在创建子进程时不需要复制进程地址空间的内容到子进程,只需要复制父进程的进程地址空间的页表子进程,这样的话,父子进程就共享了相同的物理内存。
如果父子进程中有某一方需要修改某个物理页面的内容时,会触发写保护的缺页异常,然后才复制共享页面的内容,从而让父子进程都拥有各自的副本。进程地址空间以只读方式共享,当需要写入时才发生复制。写时复制是一种可以推迟甚至避免复制数据的技术,在现代操作系统中有广泛的应用。
发生写时复制之前的父子进程映射到同一个物理页帧地址:
发生写时复制之后的父子进程映射到不同的物理页帧地址:
结合以上图示得知,在采用了写时复制技术的Linux内核中,用fork()函数创建一个新进程的开销变得很小,免去了复制父进程整个进程地址空间中的内容的巨大开销,现在只需要复制父进程页表的一点开销。
阅读小结:根据以上特性,我们可以知道写时复制技术的优缺点。优点:对于一些读多写少的数据,写入时复制的做法就很不错,例如配置、黑名单、物流地址等变化非常少的数据,这是一种无锁的实现。可以帮我们实现程序更高的并发。缺点:数据一致性的问题。这种实现只是保证数据的最终一致性,在添加到拷贝数据而还没进行替换的时候,读到的仍然是旧数据。
- 2024-01-11
-
回复了主题帖:
阅读打卡第七站:进程管理之基本概念 ——《奔跑吧Linux内核(第2版)卷1》
本帖最后由 yin_wu_qing 于 2024-1-11 22:44 编辑
进程是什么?
顾名思义,进程是执行中的程序,即一个程序加载到内存后变成了进程,公式表达如下:进程 = 程序 + 执行
进程是一段执行中的程序,是一个有“生命力”的个体。一个进程除了包含可执行的代码(如代码段),还包含进程的一些活动信息和数据,如用来存放函数形参、局部变量以及返回值的用户栈,用于存放进程相关数据的数据段,用于切换内核中进程的内核栈,以及用于动态分配内存的堆等。进程是用于实现多进程并发执行的一个实体,实现对CPU的虚拟化,让每个进程都认为自己独立拥有一个CPU。实现这个CPU虚拟化的核心技术是上下文切换以及进程调度。
操作系统如何描述和抽象一个进程?
进程是操作系统中调度的一个实体,需要对进程所拥有的资源进行抽象,这个抽象形式称为进程控制块(Process Control Block,PCB),本书也称其为进程描述符。进程描述符是用于描述进程运行状况以及控制进程运行所需要的全部信息,是操作系统用来感知进程存在的一个非常重要的数据结构。任何一个操作系统的实现都需要有一个数据结构来描述进程描述符,所以Linux内核采用一个名为task_struct的结构体。task_struct数据结构包含的内容很多,它包含进程所有相关的属性和信息。
进程是否有生命周期?
在进程的生命周期内,进程要和内核的很多模块进行交互,如内存管理模块、进程调度模块以及文件系统模块等。因此,它还包含了内存管理,进程调度、文件管理等方面的信息和状态。Linux内核把所有进程的进程描述符task_struct数据结构链接成一个单链表(task_struct->tasks),task_struct数据结构定义在include/linux/sched.h文件中。
如何标识一个进程?
在创建时会分配唯一的号码来标识进程,这个号码就是进程标识符(Process Identifier,PID)。PID存放在进程描述符的pid字段中,PID是整数类型。为了循环使用PID,内核使用bitmap机制来管理当前已经分配的PID和空闲的PID,bitmap机制可以保证每个进程创建时都能分配到唯一的PID。
除了PID之外,Linux内核还引入了线程组的概念。一个线程组中所有的线程使用和该线程组中主线程相同的PID,即该组中第一个进程的ID,它会被存入task_struct数据结构的tgid成员中。这与POSIX 1003.1c标准里的规定有关系,一个多线程应用程序中所有的线程必须有相同的PID,这样可以把指定信号发送给组里所有的线程。如一个进程创建之后,只有这个进程,它的PID和线程组ID(Thread Group ID,TGID)是一样的。这个进程创建了一个新的线程之后,新线程有属于自己的PID,但是它的TGID还是指父进程的TGID,因为它和父进程同属一个线程组。
进程与进程之间的关系如何?
进程间的关系
成 员
描 述
real_parent
指向创建了进程A的描述符,如果进程A的父进程不存在了,则指向进程1(init进程)的描述符
parent
指向进程的当前父进程,通常和real_parent一致
children
所有的子进程都链接成一个链表,这是链表头
sibling
所有兄弟进程都链接成一个链表,链表头在父进程的sibling成员中
Linux操作系统的第0个进程是什么?
系统中所有进程的task_struct数据结构都通过list_head类型的双向链表链接在一起,因此每个进程的task_struct数据结构包含一个list_head类型的tasks成员。这个进程链表的头是init_task进程,也就是所谓的进程0。init_task进程的tasks.prev字段指向链表中最后插入进程的task_struct数据结构的tasks成员。另外,若这个进程下面的有线程组(即PID==TGID),那么线程会添加到线程组的thread_group链表中。
Linux操作系统的第1个进程是什么?
Linux内核初始化函数start_kernel()在初始化完内核所需要的所有数据结构之后会创建另一个内核线程,这个内核线程就是进程1或init进程。进程1的ID为1,与进程0共享进程所有的数据结构。
进程1会执行kernel_int()函数,它会调用execve()系统调用来装入可执行程序init,最后进程1变成了一个普通进程。这些init程序就是常见的/sbin/init、/bin/init或者/bin/sh等可执行的init以及systemd程序。进程1从内核线程变成普通进程init之后,它的主要作用是根据/etc/inittab文件的内容启动所需要的任务,包括初始化系统配置、启动一个登录对话等。
请简述fork()、vfork()和clone()之间的区别。
在Linux内核中,fork()、vfork()、clone()以及创建内核线程的接口函数都是通过调用_do_fork()函数来完成的,只是调用的参数不一样。
//fork()实现
_do_fork(SIGCHLD, 0,0,NULL,NULL,0);
//vfork()实现
_do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0 , 0, NULL, NULL, 0);
//clone()实现
_do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
//内核线程
_do_fork(flags|CLONE_VM |CLONE_UNTRACED,(unsigned long)fn,(unsigned long)arg,NULL,NULL,0);
fork()函数通过系统调用进入Linux内涵,然后通过_do_fork()函数实现。
fork函数只使用SIGCHLD标志位,在子进程终止后发送SIGCHLD信号通知父进程。fork()是重量级调用,为子进程建立了一个基于父进程的完整副本,然后子进程基于此执行。为了减少工作量,子进程采用写时复制技术,只复制父进程的页表,不会复制页面内容。当子进程需要写入新内容时才触发写时复制机制,并为子进程创建一个副本。
fork()函数也有一些缺点,尽管使用了写时复制机制技术,但是它还需要复制父进程的页表,在某些场景下会比较慢,所以有了后来的vfork()原语和clone()原语。
vfork()函数和fork()函数类似,但是vfork()的父进程会一直阻塞,直到子进程调用exit()或者execve()为止。在fork()实现写时复制之前,UNIX系统的设计着很关心fork()之后马上执行execve()所造成的地址空间浪费和效率低下问题,因此设计了vfork()系统调用。
clone()函数通常用于创建用户线程。在Linux内核中没有专门的线程,而是把线程当成普通进程来看待,在内核中还以task_struct数据结构来描述线程,并没有使用特殊的数据结构或者调度算法来描述线程。
clone()函数功能强大,可以传递众多参数,可以有选择地继承父进程的资源,如可以和vfork()一样,与父进程共享一个进程地址空间,从而创建线程;也可以不和父进程共享进程地址空间,甚至可以创建兄弟关系进程。