', {
56 | 'href': gitbook.state.basePath + '/' + res.url,
57 | 'text': res.title
58 | });
59 |
60 | var content = res.body.trim();
61 | if (content.length > MAX_DESCRIPTION_SIZE) {
62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...';
63 | }
64 | var $content = $('').html(content);
65 |
66 | $link.appendTo($title);
67 | $title.appendTo($li);
68 | $content.appendTo($li);
69 | $li.appendTo($searchList);
70 | });
71 | }
72 |
73 | function launchSearch(q) {
74 | // Add class for loading
75 | $body.addClass('with-search');
76 | $body.addClass('search-loading');
77 |
78 | // Launch search query
79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS)
80 | .then(function(results) {
81 | displayResults(results);
82 | })
83 | .always(function() {
84 | $body.removeClass('search-loading');
85 | }), 1000);
86 | }
87 |
88 | function closeSearch() {
89 | $body.removeClass('with-search');
90 | $bookSearchResults.removeClass('open');
91 | }
92 |
93 | function launchSearchFromQueryString() {
94 | var q = getParameterByName('q');
95 | if (q && q.length > 0) {
96 | // Update search input
97 | $searchInput.val(q);
98 |
99 | // Launch search
100 | launchSearch(q);
101 | }
102 | }
103 |
104 | function bindSearch() {
105 | // Bind DOM
106 | $searchInput = $('#book-search-input input');
107 | $bookSearchResults = $('#book-search-results');
108 | $searchList = $bookSearchResults.find('.search-results-list');
109 | $searchTitle = $bookSearchResults.find('.search-results-title');
110 | $searchResultsCount = $searchTitle.find('.search-results-count');
111 | $searchQuery = $searchTitle.find('.search-query');
112 |
113 | // Launch query based on input content
114 | function handleUpdate() {
115 | var q = $searchInput.val();
116 |
117 | if (q.length == 0) {
118 | closeSearch();
119 | }
120 | else {
121 | launchSearch(q);
122 | }
123 | }
124 |
125 | // Detect true content change in search input
126 | // Workaround for IE < 9
127 | var propertyChangeUnbound = false;
128 | $searchInput.on('propertychange', function(e) {
129 | if (e.originalEvent.propertyName == 'value') {
130 | handleUpdate();
131 | }
132 | });
133 |
134 | // HTML5 (IE9 & others)
135 | $searchInput.on('input', function(e) {
136 | // Unbind propertychange event for IE9+
137 | if (!propertyChangeUnbound) {
138 | $(this).unbind('propertychange');
139 | propertyChangeUnbound = true;
140 | }
141 |
142 | handleUpdate();
143 | });
144 |
145 | // Push to history on blur
146 | $searchInput.on('blur', function(e) {
147 | // Update history state
148 | if (usePushState) {
149 | var uri = updateQueryString('q', $(this).val());
150 | history.pushState({ path: uri }, null, uri);
151 | }
152 | });
153 | }
154 |
155 | gitbook.events.on('page.change', function() {
156 | bindSearch();
157 | closeSearch();
158 |
159 | // Launch search based on query parameter
160 | if (gitbook.search.isInitialized()) {
161 | launchSearchFromQueryString();
162 | }
163 | });
164 |
165 | gitbook.events.on('search.ready', function() {
166 | bindSearch();
167 |
168 | // Launch search from query param at start
169 | launchSearchFromQueryString();
170 | });
171 |
172 | function getParameterByName(name) {
173 | var url = window.location.href;
174 | name = name.replace(/[\[\]]/g, '\\$&');
175 | var regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)', 'i'),
176 | results = regex.exec(url);
177 | if (!results) return null;
178 | if (!results[2]) return '';
179 | return decodeURIComponent(results[2].replace(/\+/g, ' '));
180 | }
181 |
182 | function updateQueryString(key, value) {
183 | value = encodeURIComponent(value);
184 |
185 | var url = window.location.href;
186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'),
187 | hash;
188 |
189 | if (re.test(url)) {
190 | if (typeof value !== 'undefined' && value !== null)
191 | return url.replace(re, '$1' + key + '=' + value + '$2$3');
192 | else {
193 | hash = url.split('#');
194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '');
195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null)
196 | url += '#' + hash[1];
197 | return url;
198 | }
199 | }
200 | else {
201 | if (typeof value !== 'undefined' && value !== null) {
202 | var separator = url.indexOf('?') !== -1 ? '&' : '?';
203 | hash = url.split('#');
204 | url = hash[0] + separator + key + '=' + value;
205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null)
206 | url += '#' + hash[1];
207 | return url;
208 | }
209 | else
210 | return url;
211 | }
212 | }
213 | });
214 |
--------------------------------------------------------------------------------
/_book/gitbook/gitbook-plugin-fontsettings/fontsettings.js:
--------------------------------------------------------------------------------
1 | require(['gitbook', 'jquery'], function(gitbook, $) {
2 | // Configuration
3 | var MAX_SIZE = 4,
4 | MIN_SIZE = 0,
5 | BUTTON_ID;
6 |
7 | // Current fontsettings state
8 | var fontState;
9 |
10 | // Default themes
11 | var THEMES = [
12 | {
13 | config: 'white',
14 | text: 'White',
15 | id: 0
16 | },
17 | {
18 | config: 'sepia',
19 | text: 'Sepia',
20 | id: 1
21 | },
22 | {
23 | config: 'night',
24 | text: 'Night',
25 | id: 2
26 | }
27 | ];
28 |
29 | // Default font families
30 | var FAMILIES = [
31 | {
32 | config: 'serif',
33 | text: 'Serif',
34 | id: 0
35 | },
36 | {
37 | config: 'sans',
38 | text: 'Sans',
39 | id: 1
40 | }
41 | ];
42 |
43 | // Return configured themes
44 | function getThemes() {
45 | return THEMES;
46 | }
47 |
48 | // Modify configured themes
49 | function setThemes(themes) {
50 | THEMES = themes;
51 | updateButtons();
52 | }
53 |
54 | // Return configured font families
55 | function getFamilies() {
56 | return FAMILIES;
57 | }
58 |
59 | // Modify configured font families
60 | function setFamilies(families) {
61 | FAMILIES = families;
62 | updateButtons();
63 | }
64 |
65 | // Save current font settings
66 | function saveFontSettings() {
67 | gitbook.storage.set('fontState', fontState);
68 | update();
69 | }
70 |
71 | // Increase font size
72 | function enlargeFontSize(e) {
73 | e.preventDefault();
74 | if (fontState.size >= MAX_SIZE) return;
75 |
76 | fontState.size++;
77 | saveFontSettings();
78 | }
79 |
80 | // Decrease font size
81 | function reduceFontSize(e) {
82 | e.preventDefault();
83 | if (fontState.size <= MIN_SIZE) return;
84 |
85 | fontState.size--;
86 | saveFontSettings();
87 | }
88 |
89 | // Change font family
90 | function changeFontFamily(configName, e) {
91 | if (e && e instanceof Event) {
92 | e.preventDefault();
93 | }
94 |
95 | var familyId = getFontFamilyId(configName);
96 | fontState.family = familyId;
97 | saveFontSettings();
98 | }
99 |
100 | // Change type of color theme
101 | function changeColorTheme(configName, e) {
102 | if (e && e instanceof Event) {
103 | e.preventDefault();
104 | }
105 |
106 | var $book = gitbook.state.$book;
107 |
108 | // Remove currently applied color theme
109 | if (fontState.theme !== 0)
110 | $book.removeClass('color-theme-'+fontState.theme);
111 |
112 | // Set new color theme
113 | var themeId = getThemeId(configName);
114 | fontState.theme = themeId;
115 | if (fontState.theme !== 0)
116 | $book.addClass('color-theme-'+fontState.theme);
117 |
118 | saveFontSettings();
119 | }
120 |
121 | // Return the correct id for a font-family config key
122 | // Default to first font-family
123 | function getFontFamilyId(configName) {
124 | // Search for plugin configured font family
125 | var configFamily = $.grep(FAMILIES, function(family) {
126 | return family.config == configName;
127 | })[0];
128 | // Fallback to default font family
129 | return (!!configFamily)? configFamily.id : 0;
130 | }
131 |
132 | // Return the correct id for a theme config key
133 | // Default to first theme
134 | function getThemeId(configName) {
135 | // Search for plugin configured theme
136 | var configTheme = $.grep(THEMES, function(theme) {
137 | return theme.config == configName;
138 | })[0];
139 | // Fallback to default theme
140 | return (!!configTheme)? configTheme.id : 0;
141 | }
142 |
143 | function update() {
144 | var $book = gitbook.state.$book;
145 |
146 | $('.font-settings .font-family-list li').removeClass('active');
147 | $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active');
148 |
149 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, '');
150 | $book.addClass('font-size-'+fontState.size);
151 | $book.addClass('font-family-'+fontState.family);
152 |
153 | if(fontState.theme !== 0) {
154 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, '');
155 | $book.addClass('color-theme-'+fontState.theme);
156 | }
157 | }
158 |
159 | function init(config) {
160 | // Search for plugin configured font family
161 | var configFamily = getFontFamilyId(config.family),
162 | configTheme = getThemeId(config.theme);
163 |
164 | // Instantiate font state object
165 | fontState = gitbook.storage.get('fontState', {
166 | size: config.size || 2,
167 | family: configFamily,
168 | theme: configTheme
169 | });
170 |
171 | update();
172 | }
173 |
174 | function updateButtons() {
175 | // Remove existing fontsettings buttons
176 | if (!!BUTTON_ID) {
177 | gitbook.toolbar.removeButton(BUTTON_ID);
178 | }
179 |
180 | // Create buttons in toolbar
181 | BUTTON_ID = gitbook.toolbar.createButton({
182 | icon: 'fa fa-font',
183 | label: 'Font Settings',
184 | className: 'font-settings',
185 | dropdown: [
186 | [
187 | {
188 | text: 'A',
189 | className: 'font-reduce',
190 | onClick: reduceFontSize
191 | },
192 | {
193 | text: 'A',
194 | className: 'font-enlarge',
195 | onClick: enlargeFontSize
196 | }
197 | ],
198 | $.map(FAMILIES, function(family) {
199 | family.onClick = function(e) {
200 | return changeFontFamily(family.config, e);
201 | };
202 |
203 | return family;
204 | }),
205 | $.map(THEMES, function(theme) {
206 | theme.onClick = function(e) {
207 | return changeColorTheme(theme.config, e);
208 | };
209 |
210 | return theme;
211 | })
212 | ]
213 | });
214 | }
215 |
216 | // Init configuration at start
217 | gitbook.events.bind('start', function(e, config) {
218 | var opts = config.fontsettings;
219 |
220 | // Generate buttons at start
221 | updateButtons();
222 |
223 | // Init current settings
224 | init(opts);
225 | });
226 |
227 | // Expose API
228 | gitbook.fontsettings = {
229 | enlargeFontSize: enlargeFontSize,
230 | reduceFontSize: reduceFontSize,
231 | setTheme: changeColorTheme,
232 | setFamily: changeFontFamily,
233 | getThemes: getThemes,
234 | setThemes: setThemes,
235 | getFamilies: getFamilies,
236 | setFamilies: setFamilies
237 | };
238 | });
239 |
240 |
241 |
--------------------------------------------------------------------------------
/_book/gitbook/gitbook-plugin-fontsettings/website.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Theme 1
3 | */
4 | .color-theme-1 .dropdown-menu {
5 | background-color: #111111;
6 | border-color: #7e888b;
7 | }
8 | .color-theme-1 .dropdown-menu .dropdown-caret .caret-inner {
9 | border-bottom: 9px solid #111111;
10 | }
11 | .color-theme-1 .dropdown-menu .buttons {
12 | border-color: #7e888b;
13 | }
14 | .color-theme-1 .dropdown-menu .button {
15 | color: #afa790;
16 | }
17 | .color-theme-1 .dropdown-menu .button:hover {
18 | color: #73553c;
19 | }
20 | /*
21 | * Theme 2
22 | */
23 | .color-theme-2 .dropdown-menu {
24 | background-color: #2d3143;
25 | border-color: #272a3a;
26 | }
27 | .color-theme-2 .dropdown-menu .dropdown-caret .caret-inner {
28 | border-bottom: 9px solid #2d3143;
29 | }
30 | .color-theme-2 .dropdown-menu .buttons {
31 | border-color: #272a3a;
32 | }
33 | .color-theme-2 .dropdown-menu .button {
34 | color: #62677f;
35 | }
36 | .color-theme-2 .dropdown-menu .button:hover {
37 | color: #f4f4f5;
38 | }
39 | .book .book-header .font-settings .font-enlarge {
40 | line-height: 30px;
41 | font-size: 1.4em;
42 | }
43 | .book .book-header .font-settings .font-reduce {
44 | line-height: 30px;
45 | font-size: 1em;
46 | }
47 | .book.color-theme-1 .book-body {
48 | color: #704214;
49 | background: #f3eacb;
50 | }
51 | .book.color-theme-1 .book-body .page-wrapper .page-inner section {
52 | background: #f3eacb;
53 | }
54 | .book.color-theme-2 .book-body {
55 | color: #bdcadb;
56 | background: #1c1f2b;
57 | }
58 | .book.color-theme-2 .book-body .page-wrapper .page-inner section {
59 | background: #1c1f2b;
60 | }
61 | .book.font-size-0 .book-body .page-inner section {
62 | font-size: 1.2rem;
63 | }
64 | .book.font-size-1 .book-body .page-inner section {
65 | font-size: 1.4rem;
66 | }
67 | .book.font-size-2 .book-body .page-inner section {
68 | font-size: 1.6rem;
69 | }
70 | .book.font-size-3 .book-body .page-inner section {
71 | font-size: 2.2rem;
72 | }
73 | .book.font-size-4 .book-body .page-inner section {
74 | font-size: 4rem;
75 | }
76 | .book.font-family-0 {
77 | font-family: Georgia, serif;
78 | }
79 | .book.font-family-1 {
80 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
81 | }
82 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal {
83 | color: #704214;
84 | }
85 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal a {
86 | color: inherit;
87 | }
88 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1,
89 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2,
90 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h3,
91 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h4,
92 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h5,
93 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 {
94 | color: inherit;
95 | }
96 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1,
97 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2 {
98 | border-color: inherit;
99 | }
100 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 {
101 | color: inherit;
102 | }
103 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal hr {
104 | background-color: inherit;
105 | }
106 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal blockquote {
107 | border-color: inherit;
108 | }
109 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre,
110 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code {
111 | background: #fdf6e3;
112 | color: #657b83;
113 | border-color: #f8df9c;
114 | }
115 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal .highlight {
116 | background-color: inherit;
117 | }
118 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table th,
119 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table td {
120 | border-color: #f5d06c;
121 | }
122 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr {
123 | color: inherit;
124 | background-color: #fdf6e3;
125 | border-color: #444444;
126 | }
127 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) {
128 | background-color: #fbeecb;
129 | }
130 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal {
131 | color: #bdcadb;
132 | }
133 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal a {
134 | color: #3eb1d0;
135 | }
136 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1,
137 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2,
138 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h3,
139 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h4,
140 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h5,
141 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 {
142 | color: #fffffa;
143 | }
144 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1,
145 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2 {
146 | border-color: #373b4e;
147 | }
148 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 {
149 | color: #373b4e;
150 | }
151 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal hr {
152 | background-color: #373b4e;
153 | }
154 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal blockquote {
155 | border-color: #373b4e;
156 | }
157 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre,
158 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code {
159 | color: #9dbed8;
160 | background: #2d3143;
161 | border-color: #2d3143;
162 | }
163 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal .highlight {
164 | background-color: #282a39;
165 | }
166 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table th,
167 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table td {
168 | border-color: #3b3f54;
169 | }
170 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr {
171 | color: #b6c2d2;
172 | background-color: #2d3143;
173 | border-color: #3b3f54;
174 | }
175 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) {
176 | background-color: #35394b;
177 | }
178 | .book.color-theme-1 .book-header {
179 | color: #afa790;
180 | background: transparent;
181 | }
182 | .book.color-theme-1 .book-header .btn {
183 | color: #afa790;
184 | }
185 | .book.color-theme-1 .book-header .btn:hover {
186 | color: #73553c;
187 | background: none;
188 | }
189 | .book.color-theme-1 .book-header h1 {
190 | color: #704214;
191 | }
192 | .book.color-theme-2 .book-header {
193 | color: #7e888b;
194 | background: transparent;
195 | }
196 | .book.color-theme-2 .book-header .btn {
197 | color: #3b3f54;
198 | }
199 | .book.color-theme-2 .book-header .btn:hover {
200 | color: #fffff5;
201 | background: none;
202 | }
203 | .book.color-theme-2 .book-header h1 {
204 | color: #bdcadb;
205 | }
206 | .book.color-theme-1 .book-body .navigation {
207 | color: #afa790;
208 | }
209 | .book.color-theme-1 .book-body .navigation:hover {
210 | color: #73553c;
211 | }
212 | .book.color-theme-2 .book-body .navigation {
213 | color: #383f52;
214 | }
215 | .book.color-theme-2 .book-body .navigation:hover {
216 | color: #fffff5;
217 | }
218 | /*
219 | * Theme 1
220 | */
221 | .book.color-theme-1 .book-summary {
222 | color: #afa790;
223 | background: #111111;
224 | border-right: 1px solid rgba(0, 0, 0, 0.07);
225 | }
226 | .book.color-theme-1 .book-summary .book-search {
227 | background: transparent;
228 | }
229 | .book.color-theme-1 .book-summary .book-search input,
230 | .book.color-theme-1 .book-summary .book-search input:focus {
231 | border: 1px solid transparent;
232 | }
233 | .book.color-theme-1 .book-summary ul.summary li.divider {
234 | background: #7e888b;
235 | box-shadow: none;
236 | }
237 | .book.color-theme-1 .book-summary ul.summary li i.fa-check {
238 | color: #33cc33;
239 | }
240 | .book.color-theme-1 .book-summary ul.summary li.done > a {
241 | color: #877f6a;
242 | }
243 | .book.color-theme-1 .book-summary ul.summary li a,
244 | .book.color-theme-1 .book-summary ul.summary li span {
245 | color: #877f6a;
246 | background: transparent;
247 | font-weight: normal;
248 | }
249 | .book.color-theme-1 .book-summary ul.summary li.active > a,
250 | .book.color-theme-1 .book-summary ul.summary li a:hover {
251 | color: #704214;
252 | background: transparent;
253 | font-weight: normal;
254 | }
255 | /*
256 | * Theme 2
257 | */
258 | .book.color-theme-2 .book-summary {
259 | color: #bcc1d2;
260 | background: #2d3143;
261 | border-right: none;
262 | }
263 | .book.color-theme-2 .book-summary .book-search {
264 | background: transparent;
265 | }
266 | .book.color-theme-2 .book-summary .book-search input,
267 | .book.color-theme-2 .book-summary .book-search input:focus {
268 | border: 1px solid transparent;
269 | }
270 | .book.color-theme-2 .book-summary ul.summary li.divider {
271 | background: #272a3a;
272 | box-shadow: none;
273 | }
274 | .book.color-theme-2 .book-summary ul.summary li i.fa-check {
275 | color: #33cc33;
276 | }
277 | .book.color-theme-2 .book-summary ul.summary li.done > a {
278 | color: #62687f;
279 | }
280 | .book.color-theme-2 .book-summary ul.summary li a,
281 | .book.color-theme-2 .book-summary ul.summary li span {
282 | color: #c1c6d7;
283 | background: transparent;
284 | font-weight: 600;
285 | }
286 | .book.color-theme-2 .book-summary ul.summary li.active > a,
287 | .book.color-theme-2 .book-summary ul.summary li a:hover {
288 | color: #f4f4f5;
289 | background: #252737;
290 | font-weight: 600;
291 | }
292 |
--------------------------------------------------------------------------------
/freertos-de-fa-hang.md:
--------------------------------------------------------------------------------
1 | # FreeRTOS 的发行
2 |
3 | ## 引言与范围
4 |
5 | FreeRTOS 作为单个 zip 文件存档分发,包含所有官方 FreeRTOS 移植和大量预配置的演示应用程序。
6 |
7 | ### 范围
8 |
9 | 本章旨在帮助用户通过以下方式使用 FreeRTOS 文件和目录来定位自己:
10 |
11 | * 提供 FreeRTOS 目录结构的顶级视图。
12 | * 描述任何特定 FreeRTOS 项目实际需要哪些文件。
13 | * 介绍演示应用程序。
14 | * 提供有关如何创建新项目的信息。
15 |
16 | 此处的描述仅涉及官方 FreeRTOS 的发行版。 本书附带的示例使用略有不同的组织。
17 |
18 | ## 了解 FreeRTOS 的分发
19 |
20 | ### 定义:FreeRTOS 移植
21 |
22 | FreeRTOS 可以使用大约 20 种不同的编译器构建,并且可以运行在 30 多种不同的处理器架构上。 每个受支持的编译器和处理器组合都被视为一个单独的 FreeRTOS 移植。
23 |
24 | ### 编译 FreeRTOS
25 |
26 | FreeRTOS 可以被认为是一个库,它可以提供多任务功能,而不是裸机应用程序。
27 |
28 | FreeRTOS 是以一组 C 源文件提供给用户。 某些源文件对所有移植都是通用的,而某些源文件则特定于移植。 构建源文件作为项目的一部分,以使 FreeRTOS API 可用于您的应用程序。 为了方便您使用,每个官方 FreeRTOS 移植都配有演示应用程序。 演示应用程序已预先配置为构建正确的源文件,并包含正确的头文件。
29 |
30 | 演示应用程序应该 “开箱即用”,虽然有些演示比其他演示更旧,并且自演示程序发布以来构建工具的自身的更新可能会引起报错。 第 1.3 节描述了演示应用程序。
31 |
32 | ### FreeRTOSConfig.h
33 |
34 | FreeRTOS 由名为 `FreeRTOSConfig.h` 的头文件配置。
35 |
36 | `FreeRTOSConfig.h` 用于定制 FreeRTOS 以用于特定应用程序。 例如,`FreeRTOSConfig.h` 包含常量,如 `configUSE_PREEMPTION`,它的设置定义了将使用协作调度算法还是抢占调度算法。 由于 `FreeRTOSConfig.h` 包含特定于应用程序的定义,因此它应位于正在构建的应用程序部分的目录中,而不是位于包含 FreeRTOS 源代码的目录中。
37 |
38 | 为每个 FreeRTOS 移植提供演示应用程序,每个演示应用程序包含一个 `FreeRTOSConfig.h` 文件。 因此,永远不必从头开始创建 `FreeRTOSConfig.h` 文件。 相反,建议从 FreeRTOS 移植提供的演示应用程序中的 `FreeRTOSConfig.h` 开始,然后根据需求修改它。
39 |
40 | ### FreeRTOS 的官方发行版
41 |
42 | FreeRTOS 发布在一个 zip 文件中。 该 zip 文件包含所有 FreeRTOS 移植的源代码,以及所有 FreeRTOS 演示应用程序的项目文件。 它还包含一系列 FreeRTOS+ 生态系统组件,以及一系列 FreeRTOS+ 生态系统演示应用程序。
43 |
44 | 不要被 FreeRTOS 发行版中的文件数量吓到! 任何一个应用程序中只需要非常少量的文件。
45 |
46 | ### FreeRTOS 发行版的顶级目录
47 |
48 | 图 1 显示并描述了FreeRTOS发行版的第一级和第二级目录。
49 |
50 | ```text
51 | FreeRTOS
52 | │ │
53 | │ ├─Source 包含 FreeRTOS 源文件的目录
54 | │ │
55 | │ └─Demo 包含预配置和移植特定 FreeRTOS 演示项目的目录
56 | │
57 | FreeRTOS-Plus
58 | │
59 | ├─Source 包含一些 FreeRTOS+ 生态系统组件的源代码的目录
60 | │
61 | └─Demo 包含 FreeRTOS+ 生态系统组件的演示项目的目录
62 | ```
63 |
64 | 图1. FreeRTOS 发行版中的顶级目录
65 |
66 | zip 文件只包含 FreeRTOS 源文件的一个副本; 所有 FreeRTOS 演示项目和所有 FreeRTOS+ 演示项目都希望在 `FreeRTOS/Source` 目录中找到 FreeRTOS 源文件,如果目录结构发生变化,可能无法构建。
67 |
68 | ### 所有移植共有的 FreeRTOS 源文件
69 |
70 | 核心 FreeRTOS 源代码仅包含在所有 FreeRTOS 移植共有的两个 C 文件中。 这些被称为 `tasks.c` 和 `list.c`,它们直接位于 `FreeRTOS/Source` 目录中,如图 2 所示。除了这两个文件之外,以下源文件位于同一目录中:
71 |
72 | * **queue.c**:`queue.c` 提供队列和信号量服务,如本书后面所述。 几乎总是需要 `queue.c`。
73 | * **timers.c**:`timers.c` 提供软件计时器功能,如本书后面所述。 如果实际中将使用软件定时器,它只需要包含在构建中。
74 | * **event\_groups.c**:`event_groups.c` 提供事件组功能,如本书后面所述。 如果实际要使用事件组,它只需要包含在构建中。
75 | * **croutine.c**:`croutine.c` 实现了 FreeRTOS 协同例程功能。 如果实际要使用协同例程,它只需要包含在构建中。协同程序旨在用于非常小的微控制器,现在很少使用,因此不能保持与其他 FreeRTOS 功能相同的水平。本例中没有描述协同例程。
76 |
77 | ```text
78 | FreeRTOS
79 | │
80 | └─Source
81 | │
82 | ├─tasks.c FreeRTOS 源文件 -总是需要
83 | ├─list.c FreeRTOS 源文件 -总是需要
84 | ├─queue.c FreeRTOS 源文件 -几乎总是需要
85 | ├─timers.c FreeRTOS 源文件 -可选
86 | ├─event_groups.c FreeRTOS 源文件 -可选
87 | └─croutine.c FreeRTOS 源文件 -可选
88 | ```
89 |
90 | 图2. FreeRTOS 目录树中的核心 FreeRTOS 源文件
91 |
92 | 有的人会意识到文件名可能导致命名空间冲突,因为许多项目已经包含具有相同名称的文件。 然而,我们认为现在更改文件的名称会有问题,这样做会破坏与使用 FreeRTOS 的数千个项目以及自动化工具和 IDE 插件的兼容性。
93 |
94 | ### FreeRTOS 特定移植的源文件
95 |
96 | 特定 FreeRTOS 移植的源文件包含在 `FreeRTOS/Source/portable` 目录中。 可移植目录按层次结构排列,首先是编译器,然后是处理器体系结构。 层次结构如图 3 所示。
97 |
98 | 如果你在使用编译器 \[_compiler\]_ 的架构 \[_architecture\]_ 的处理器上运行 FreeRTOS,除了核心 FreeRTOS 源文件之外,你还必须构建位于 `FreeRTOS/Source/portable/[compiler]/[architecture]` 目录中的文件。
99 |
100 | 正如将在第 2 章堆内存管理中所描述的,FreeRTOS 还认为堆内存分配是便携式层的一部分。 使用早于V9.0.0 的 FreeRTOS 版本的项目必须包含堆内存管理器。 从 FreeRTOS V9.0.0 开始,只有在 `FreeRTOSConfig.h` 中将 `configSUPPORT_DYNAMIC_ALLOCATION` 设置为 1 或者未定义 `configSUPPORT_DYNAMIC_ALLOCATION` 时才需要堆内存管理器。
101 |
102 | FreeRTOS 提供了五个示例堆分配方案。 这五个方案名为 heap\_1 到 heap\_5,分别由源文件 `heap_1.c` 到`heap_5.c` 实现。 示例堆分配方案包含在 `FreeRTOS/Source/portable/MemMang` 目录中。 如果已将 FreeRTOS 配置为使用动态内存分配,则必须在项目中构建这五个源文件中的一个,除非您的应用程序提供了替代实现。
103 |
104 | ```text
105 | FreeRTOS
106 | │
107 | └─Source
108 | │
109 | └─portable 包含所有特定移植源文件的目录
110 | │
111 | ├─MemMang 包含 5 个备用堆分配源文件的目录
112 | │
113 | ├─[compiler 1] 包含特定移植编译器 1 文件的目录
114 | │ │
115 | │ ├─[architecture 1] 包含移植编译器 1 架构 1 的文件
116 | │ ├─[architecture 2] 包含移植编译器 1 架构 2 的文件
117 | │ └─[architecture 3] 包含移植编译器 1 架构 3 的文件
118 | │
119 | └─[compiler 2] 包含特定移植编译器 2 文件的目录
120 | │
121 | ├─[architecture 1] 包含移植编译器 2 架构 1 的文件
122 | ├─[architecture 2] 包含移植编译器 2 架构 2 的文件
123 | └─[etc.]
124 | ```
125 |
126 | 图3. FreeRTOS 目录树中的特定移植源文件
127 |
128 | ### 包含路径
129 |
130 | FreeRTOS 要求在编译器的 include 路径中包含三个目录。 如下:
131 |
132 | 1. FreeRTOS 核心头文件的路径,它始终是 `FreeRTOS / Source / include`。
133 | 2. 正在使用特定移植的 FreeRTOS 的源文件的路径。 如上所述,这是 `FreeRTOS/Source/portable/[compiler]/[architecture]`
134 | 3. `FreeRTOSConfig.h` 头文件的路径。
135 |
136 | ### 头文件
137 |
138 | 使用 FreeRTOS API 的源文件必须包含 `FreeRTOS.h`,然后是包含正在使用的 API 函数原型的头文件:`task.h`,`queue.h`,`semphr.h`,`timers.h` 或 `event_groups.h`。
139 |
140 | ## 演示应用程序
141 |
142 | 每个 FreeRTOS 移植都附带至少一个可以构建的演示应用程序,不会生成任何错误或警告,尽管某些演示比其他演示更旧,并且自演示程序发布以来构建工具的自身的更新可能会引起报错。
143 |
144 | Linux _\*\*_用户须知:FreeRTOS 是在 Windows 主机上开发和测试的。 在 Linux 主机上构建演示项目时,偶尔会导致构建错误。 构建错误几乎总是与引用文件名时使用的字母大小写或文件路径中使用的斜杠字符的方向有关。 请使用 FreeRTOS 联系表([http://www.FreeRTOS.org/contact](http://www.FreeRTOS.org/contact))提醒我们任何此类错误。
145 |
146 | 演示应用程序有几个目的:
147 |
148 | * 提供一个工作和预配置项目的示例,包含正确的文件,并设置正确的编译器选项。
149 | * 允许以最小的设置或先验知识进行 “开箱即用” 的实验。
150 | * 作为如何可以使用 FreeRTOS API 的演示。
151 | * 作为可以创建真实应用程序的基础。
152 |
153 | 每个演示项目都位于 `FreeRTOS/Demo` 目录下的唯一子目录中。 子目录的名称表示演示项目所涉及的移植。
154 |
155 | 每个演示应用程序也由 FreeRTOS.org 网站上的网页描述。网页包含以下信息:
156 |
157 | * 如何在 FreeRTOS 目录结构中找到演示的项目文件。
158 | * 项目配置使用的硬件。
159 | * 如何设置运行演示的硬件。
160 | * 如何构建演示。
161 | * 如何演示预期行为。
162 |
163 | 所有演示项目都创建了常见演示任务的子集,其实现包含在 `FreeRTOS/Demo/Common/Minimal` 目录中。 常见的演示任务纯粹是为了演示如何使用 FreeRTOS API —— 它们没有实现任何特定的有用功能。
164 |
165 | 最近的演示项目也可以构建一个初学者的 'blinky' 项目。 Blinky 项目是非常基本的。 通常,他们只创建两个任务和一个队列。
166 |
167 | 每个演示项目都包含一个名为 `main.c` 的文件。 它包含 `main()` 函数,从中创建所有演示应用程序任务。 有关该演示的特定信息,请参阅各个 `main.c` 文件中的注释。
168 |
169 | `FreeRTOS/Demo` 目录层次结构如图 4 所示。
170 |
171 | ```text
172 | FreeRTOS
173 | │
174 | └─Demo 包含所有演示项目的目录
175 | │
176 | ├─[Demo x] 包含构建演示'x'的项目文件
177 | │
178 | ├─[Demo y] 包含构建演示'y'的项目文件
179 | │
180 | ├─[Demo z] 包含构建演示'z'的项目文件
181 | │
182 | └─Common 包含由所有演示应用程序构建的文件
183 | ```
184 |
185 | 图 4. 演示目录层次结构
186 |
187 | ## 创建一个 FreeRTOS 项目
188 |
189 | ### 适配其中一个提供的演示项目
190 |
191 | 每个 FreeRTOS 移植都附带至少一个预配置的演示应用程序,该应用程序应该构建时不会出现错误或警告。 建议通过适配其中一个现有项目来创建新项目; 这将允许项目包含正确的文件,安装正确的中断处理程序以及正确的编译器选项集。
192 |
193 | 要从现有演示项目开始新的应用程序:
194 |
195 | 1. 打开提供的演示项目,确保按预期构建和执行。
196 | 2. 删除定义演示任务的源文件。 可以从项目中删除位于 `Demo/Common` 目录中的任何文件。
197 | 3. 除了 `prvSetupHardware()` 和 `vTaskStartScheduler()` 之外,删除 `main()` 中的所有函数调用,如清单1.4 所示。
198 | 4. 检查项目是否仍可构建。
199 |
200 | 按照这些步骤创建一个包含正确 FreeRTOS 源文件的项目,但不定义任何功能。
201 |
202 | ```c
203 | int main( void )
204 | {
205 | /* 执行必要的任何硬件设置。 */
206 | prvSetupHardware();
207 |
208 | /* ---可以在这里创建应用程序任务 ---*/
209 | /* 启动已创建的任务。 */
210 | vTaskStartScheduler();
211 |
212 | /* 只有当没有足够的堆来启动调度程序时,执行才会到达这里。 */
213 | for( ;; );
214 | return 0;
215 | }
216 | ```
217 |
218 | 清单 1. 新 `main()` 函数的模板
219 |
220 | ### 从头开始创建一个新项目
221 |
222 | 如前所述,建议从现有的演示项目创建新项目。 如果不希望这样,则可以使用以下过程创建新项目:
223 |
224 | 1. 使用您选择的工具链,创建一个尚未包含任何 FreeRTOS 源文件的新项目。
225 | 2. 确保可以构建新项目,下载到目标硬件并执行。
226 | 3. 只有当您确定已经有一个工作项目,将表 1 中详述的 FreeRTOS 源文件添加到项目中。
227 | 4. 将为移植提供的演示项目使用的 `FreeRTOSConfig.h` 头文件复制到项目目录中。
228 | 5. 将以下目录添加到路径中,项目将搜索以查找头文件:
229 | 1. `FreeRTOS/Source/include`
230 | 2. `FreeRTOS/Source/portable/[compiler]/[architecture]` 其中 `[compiler]` 和 `[architecture]` 适合您选择的移植
231 | 3. 包含 `FreeRTOSConfig.h` 头文件的目录
232 | 6. 从相关演示项目中复制编译器设置。
233 | 7. 安装可能需要的任何 FreeRTOS 中断处理程序。 使用描述正在使用移植的网页以及为使用移植提供的演示项目作为参考。
234 |
235 | 表 1. 要包含在项目中的 FreeRTOS 源文件
236 |
237 | | 文件 | 位置 |
238 | | :--- | :--- |
239 | | tasks.c | FreeRTOS/Source |
240 | | queue.c | FreeRTOS/Source |
241 | | list.c | FreeRTOS/Source |
242 | | timers.c | FreeRTOS/Source |
243 | | event\_groups.c | FreeRTOS/Source |
244 | | 所有 C 和汇编文件 | FreeRTOS/Source/portable/\[compiler\]/\[architecture\] |
245 | | heap\_n.c | FreeRTOS/Source/portable/MemMang,n 可以是 1,2,3,4,5。这个文件在 FreeRTOS V9.0.0 中是可选的。 |
246 |
247 | 使用早于 V9.0.0 的 FreeRTOS 版本的项目必须构建一个 `heap_n.c` 文件。从 FreeRTOS V9.0.0 开始,只有在 `FreeRTOSConfig.h` 中将 `configSUPPORT_DYNAMIC_ALLOCATION` 设置为 1 或未定义 `configSUPPORT_DYNAMIC_ALLOCATION`时才需要 `heap_n.c` 文件。 有关更多信息,请参阅第 2 章堆内存管理。
248 |
249 | ## 数据类型和编码风格指南
250 |
251 | ### 数据类型
252 |
253 | FreeRTOS 的每个端口都有一个唯一的 `portmacro.h` 头文件,其中包含两个端口特定数据类型的定义(其中包括):`TickType_t` 和 `BaseType_t`。 表 2 中描述了这些数据类型。
254 |
255 | 表 2. FreeRTOS使用的端口特定数据类型
256 |
257 | | 使用的宏或 typedef | 实际类型 |
258 | | :--- | :--- |
259 | | TickType\_t | FreeRTOS 配置一个称为节拍中断的周期性中断。 自 FreeRTOS 应用程序启动以来发生的节拍中断数称为节拍计数。 刻度计数用作时间的度量。 两次滴答中断之间的时间称为滴答期。 时间被指定为滴答期的倍数。 `TickType_t` 是用于保存刻度计数值和指定时间的数据类型。 `TickType_t` 可以是无符号 16 位类型,也可以是无符号 32 位类型,具体取决于`FreeRTOSConfig.h` 中 `configUSE_16_BIT_TICKS` 的设置。如果 `configUSE_16_BIT_TICKS` 设置为 1,则 `TickType_t` 定义为 `uint16_t`。 如果 `configUSE_16_BIT_TICKS` 设置为 0,则 `TickType_t` 定义为 `uint32_t`。使用 16 位类型可以极大地提高 8 位和 16 位体系结构的效率,但严重限制了可以指定的最大块周期。 没有理由在 32 位架构上使用 16 位类型。 |
260 | | BaseType\_t | 这始终被定义为架构的最有效数据类型。 通常,这是 32 位架构上的 32 位类型,16 位架构上的 16 位类型和 8 位架构上的8位类型。 `BaseType_t` 通常用于只能采用非常有限的值范围的返回类型,以及 `pdTRUE`/`pdFALSE` 类型的布尔值。 |
261 |
262 | 有些编译器会使所有不合格的 `char` 变量无符号,而其他编译器会对它们进行有符号处理。 出于这个原因,FreeRTOS 源代码代码显式限定 `char` 的每个用户都使用 `signed` 或 `unsigned`,除非 char 用于保存 ASCII 字符,或者指向 `char` 的指针用于指向字符串。
263 |
264 | 不使用普通 int 类型。
265 |
266 | ### 变量名
267 |
268 | 变量的前缀是它们的类型:
269 |
270 | * `c` 表示 `char`
271 | * `s` 表示 `int16_t`(`short`)
272 | * `l`表示 `int32_t`(`long`)
273 | * `x` 表示 `BaseType_t` 和任何其他非标准类型(结构,任务句柄,队列句柄等)
274 |
275 | 如果变量是无符号的,则它也带有 `u` 前缀。 如果变量是指针,则它也带有 `p` 前缀。 例如,`uint8_t` 类型的变量将以 `uc` 为前缀,而指向 `char` 的类型指针的变量将以 `pc` 为前缀。
276 |
277 | ### 函数名
278 |
279 | 函数以它们返回的类型和它们在其中定义的文件为前缀。 例如:
280 |
281 | * _v_**Task**PrioritySet\(\) 返回一个 `void`,并定义在 **task**.c 中。
282 | * _x_**Queue**Receive\(\) 返回一个 `BaseType_t`类型的变量,并定义在 **queue**.c 中。
283 | * _pv_**Timer**GetTimerID\(\) 返回一个 `void`类型的指针,并定义在 **timers**.c 中。
284 |
285 | 文件范围(私有)函数以 `prv` 为前缀。
286 |
287 | ### 格式化
288 |
289 | 1 个 Tab 总是等于 4 个空格。
290 |
291 | ### 宏名
292 |
293 | 大多数宏以大写字母书写,并以小写字母为前缀,表示宏的定义位置。 表 3 提供了前缀列表。
294 |
295 | 表 3. 宏的前缀
296 |
297 | | 前缀 | 宏定义的位置 | 例子 |
298 | | :--- | :--- | :--- |
299 | | port | portable.h 或 portmacro.h | **port**MAX\_DELAY |
300 | | task | task.h | **task**ENTER\_CRITICAL\(\) |
301 | | pd | projdefs.h | **pd**TRUE |
302 | | config | FreeRTOSConfig.h | **config**USE\_PREEMPTION |
303 | | err | projdefs.h | **err**QUEUE\_FULL |
304 |
305 | 请注意,信号量 API 几乎完全是作为一组宏编写的,但遵循函数命名约定,而不是宏命名约定。表 4 中定义的宏在整个 FreeRTOS 源代码中使用。
306 |
307 | 表 4. 常见的宏定义
308 |
309 | | 宏 | 值 |
310 | | :--- | :--- |
311 | | pdTRUE | 1 |
312 | | pdFALSE | 0 |
313 | | pdPASS | 1 |
314 | | pdFAIL | 0 |
315 |
316 | ### 构造中间过度类型的理由
317 |
318 | FreeRTOS 源代码可以使用不同的编译器进行编译,所有编译器的编写方式和生成警告的方式都有所不同。 特别是,不同的编译器希望以不同的方式使用转换。 因此,FreeRTOS 源代码包含比通常保证更多的类型转换。
319 |
320 |
--------------------------------------------------------------------------------
/_book/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 掌握 FreeROTS™ 实时内核 · GitBook
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 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
325 |
326 |
327 |
328 |
results matching ""
329 |
330 |
331 |
332 |
333 |
334 |
No results matching ""
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
--------------------------------------------------------------------------------
/dui-nei-cun-guan-li.md:
--------------------------------------------------------------------------------
1 | # 堆内存管理
2 |
3 | 从 FreeRTOS V9.0.0 开始 FreeRTOS 应用程序可以完全静态分配,无需包含堆内存管理器。
4 |
5 | ## 引言与范围
6 |
7 | ### 先决条件
8 |
9 | FreeRTOS 是作为一组 C 源文件提供的,因此作为一名称职的 C 程序员是使用 FreeRTOS 的先决条件,因此本章假定读者熟悉以下概念:
10 |
11 | * 如何构建C项目,包括不同的编译和链接阶段。
12 | * 栈和堆是什么.
13 | * 标准 C 库 `malloc()` 和 `free()` 函数。
14 |
15 | ### 动态内存分配及其与 FreeRTOS 的相关性
16 |
17 | 从 FreeRTOS V9.0.0 开始,内核对象可以在编译时静态分配,也可以在运行时动态分配:本书的后续章节将介绍内核对象,如任务,队列,信号量和事件组。 为了使 FreeRTOS 尽可能易于使用,这些内核对象在编译时不是分配的,而是在运行时动态分配的; 每次创建内核对象时,FreeRTOS 都会分配 RAM,并在每次删除内核对象时释放 RAM。 此策略减少了设计和计划工作,简化了 API,并最大限度地减少了 RAM 占用空间。
18 |
19 | 本章讨论动态内存分配。 动态内存分配是一种 C 编程概念,而不是 FreeRTOS 或多任务特有的概念。 它与 FreeRTOS 相关,因为内核对象是动态分配的,而通用编译器提供的动态内存分配方案并不总是适用于实时应用程序。
20 |
21 | 可以使用标准 C 库中的 `malloc()` 和 `free()` 函数分配内存,但是由于以下一个或多个原因,它们可能不合适:
22 |
23 | * 它们在小型嵌入式系统中并不总是可用。
24 | * 它们的实现可能相对较大,占用了宝贵的代码空间。
25 | * 它们很少是线程安全的。
26 | * 它们不是确定性的; 执行功能所花费的时间因调用而异。
27 | * 它们可能会遭受碎片的折磨。
28 | * 它们会使链接器配置复杂化。
29 | * 如果允许堆空间增长到其他变量使用的内存,它们可能是难以调试出来的错误来源。
30 |
31 | ### 动态内存分配的选项
32 |
33 | 从 FreeRTOS V9.0.0 开始,内核对象可以在编译时静态分配,也可以在运行时动态分配:早期版本的FreeRTOS 使用内存池分配方案,从而在编译时预先分配不同大小的内存块池,然后由内存分配功能。 虽然这是在实时系统中使用的常见方案,但它被证明是许多支持请求的来源,主要是因为它无法有效地使用 RAM 以使其适用于非常小的嵌入式系统,因此该方案被放弃。
34 |
35 | FreeRTOS 现在将内存分配视为可移植层的一部分(而不是核心代码库的一部分)。 这是因为不同的嵌入式系统具有不同的动态存储器分配和时序要求,因此单个动态存储器分配算法将仅适用于应用的子集。 此外,从核心代码库中删除动态内存分配使应用程序编写者能够在适当时提供自己的特定实现。
36 |
37 | 当 FreeRTOS 需要 RAM 时,它调用 `pvPortMalloc()`,而不是调用 `malloc()`。 当释放 RAM 时,内核调用 `vPortFree()` 而不是调用 `free()`。 `pvPortMalloc()` 与标准 C 库 `malloc()` 函数具有相同的原型,而 `vPortFree()` 与标准 C 库 `free()` 函数具有相同的原型。
38 |
39 | `pvPortMalloc()` 和 `vPortFree()` 是公共函数,因此也可以从应用程序代码中调用它们。
40 |
41 | 从 FreeRTOS V9.0.0 开始,内核对象可以在编译时静态分配,也可以在运行时动态分配:FreeRTOS 带有`pvPortMalloc()` 和 `vPortFree()` 的五个示例实现,所有这些都在本章中记录。FreeRTOS 应用程序可以使用其中一个示例实现,或提供自己的。
42 |
43 | 这五个示例分别在 `heap_1.c`,`heap_2.c`,`heap_3.c`,`heap_4.c` 和 `heap_5.c` 源文件中定义,所有这些文件都位于 `FreeRTOS/Source/portable/MemMang` 目录中。
44 |
45 | ### 范围
46 |
47 | 本章的目的是让读者更好地理解:
48 |
49 | * 什么时候 FreeRTOS 分配RAM。
50 | * FreeRTOS 提供的 5 个内存分配方案示例。
51 | * 选择哪种内存分配方案。
52 |
53 | ## 内存分配方案示例
54 |
55 | 从 FreeRTOS V9.0.0 可以完全静态分配 FreeRTOS 应用程序,无需包含堆内存管理器。
56 |
57 | ### Heap\_1
58 |
59 | 小型专用嵌入式系统通常只在调度程序启动之前创建任务和其他内核对象。 在这种情况下,内存仅在应用程序开始执行任何实时功能之前由内核动态分配,并且内存在应用程序的生命周期内保持分配。 这意味着所选择的分配方案不必考虑任何更复杂的内存分配问题,例如确定性和分段,而只需考虑代码大小和简单性等属性。
60 |
61 | `heap_1.c` 实现了 `pvPortMalloc()` 的一个非常基本的版本,并没有实现 `vPortFree()`。 永远不会删除任务或其他内核对象的应用程序可能会使用 `heap_1`。
62 |
63 | 一些禁止使用动态内存分配的商业关键系统和安全关键系统也可能使用 `heap_1`。由于与非确定性、内存碎片和失败的分配相关的不确定性,关键系统通常禁止动态内存分配,但是`heap_1`总是确定性的,并且不能分割内存。
64 |
65 | `heap_1` 分配方案将一个简单数组细分为更小的块,因为调用了 `pvPortMalloc()` 。 该数组称为FreeRTOS heap。
66 |
67 | 数组的总大小(以字节为单位)由 `FreeRTOSConfig.h` 中的 `configTOTAL_HEAP_SIZE` 定义设置。 以这种方式定义大型数组可能会使应用程序看起来消耗大量 RAM ,甚至在从数组分配任何内存之前。
68 |
69 | 每个创建的任务都需要一个任务控制块(TCB)和一个从堆中分配的栈。 图 5 演示了在创建任务时 `heap_1` 如何细分简单数组。
70 |
71 | 参考图 5:
72 |
73 | * A 显示了在创建任何任务之前的数组:整个数组是空闲的。
74 | * B 显示了在创建一个任务之后的数组。
75 | * C 显示了子啊船舰了三个任务之后的数组。
76 |
77 | 
78 |
79 | ### Heap\_2
80 |
81 | `heap_2` 保留在 FreeRTOS 发行版中以实现向后兼容,但不建议将其用于新设计。 请优先考虑使用 `heap_4` 而不是`heap_2`,因为 `heap_4` 提供了增强的功能。
82 |
83 | `heap_2.c` 也可以通过细分由 `configTOTAL_HEAP_SIZE` 标注的数组来工作。 它使用最合适的算法来分配内存,与 `heap_1` 不同,它允许释放内存。 同样,数组是静态声明的,因此即使在分配了阵列中的任何内存之前,也会使应用程序看起来消耗大量 RAM。
84 |
85 | 最合适的算法确保 `pvPortMalloc()` 使用与所请求的字节数最接近的空闲内存块。 例如,考虑场景:
86 |
87 | * 堆包含三个可用内存块,分别为 5 个字节,25 个字节和 100 个字节。
88 | * 调用 `pvPortMalloc()` 来请求 20 个字节的 RAM。
89 |
90 | 所请求的字节数适合的最小空闲 RAM 块是 25 字节块,因此 `pvPortMalloc()` 将 25 字节块拆分为一个 20 字节的块和一个 5 字节的块,然后返回指针 20 字节的块。 新的 5 字节块仍可用于将来调用 `pvPortMalloc()`。
91 |
92 | 与 `heap_4` 不同,`heap_2` 不会将相邻的空闲块组合成一个更大的块,因此更容易出现碎片。 但是,如果分配并随后释放的块总是大小相同,则碎片不是问题。 `heap_2` 适用于重复创建和删除任务的应用程序,前提是分配给创建的任务的堆栈大小不会更改。
93 |
94 | 
95 |
96 | 图 6 展示了创建,删除然后再创建任务时最佳拟合算法的工作原理。 参考图6:
97 |
98 | 1. A 显示了创建了三个任务之后的数组。一个大的自由块仍在数组顶部。
99 | 2. B 显示了一个任务被删除之后的数组。数组顶部的大型自由块仍然存在。 现在还有两个较小的空闲块
100 |
101 | ,这是先前已分配给 TCB 和栈的任务,而现在已被删除。
102 | 3. C 显示了在创建另一个任务后的情况。 创建任务导致两次调用 `pvPortMalloc()`,一次分配新 TCB,另一次分配任务堆栈。使用 `xTaskCreate()` API 函数创建任务,如 3.4 节所述。 对 `pvPortMalloc()` 的调用在 `xTaskCreate()`内部发生。每个 TCB 的大小完全相同,因此最合适的算法确保先前分配给已删除任务的 TCB 的 RAM 块被重用以分配新任务的 TCB。分配给新创建的任务的堆栈大小与分配给先前删除的相同,因此最合适的算法确保先前分配给已删除任务堆栈的 RAM 块被重用以分配新任务的堆栈。数组顶部较大的未分配块保持不变。
103 |
104 | `heap_2` 不是确定性的,但比 `malloc()` 和 `free()` 的大多数标准库实现更快。
105 |
106 | ### Heap\_3
107 |
108 | `heap_3.c` 使用标准库 `malloc()` 和 `free()` 函数,因此堆的大小由链接器配置定义,而 `configTOTAL_HEAP_SIZE` 设置没有影响。`heap_3` 使 `malloc()` 和 `free()` 线程安全暂时挂起 FreeRTOS 调度程序。 线程安全和调度程序暂停都是第 7 章资源管理中介绍的主题。
109 |
110 | ### Heap\_4
111 |
112 | 与 `heap_1` 和 `heap_2` 类似,通过将数组细分为较小的块来实现 `heap_4`。 和以前一样,数组是静态声明的,并且由 `configTOTAL_HEAP_SIZE` 标注,因此即使在实际从数组中分配任何内存之前,也会使应用程序看起来消耗大量 RAM。
113 |
114 | `heap_4` 使用首次拟合算法来分配内存。 与 `heap_2` 不同,`heap_4` 将相邻的空闲内存块(合并)组合成一个更大的块,从而最大限度地降低了内存碎片的风险。
115 |
116 | 第一适合算法确保 `pvPortMalloc()`使用足够大的第一个空闲内存块来保存请求的字节数。 例如,考虑以下情况:
117 |
118 | * 堆包含三个可用内存块,它们按照它们在数组中出现的顺序分别为 5 个字节,200 个字节和 100 个字节。
119 | * 调用 `pvPortMalloc()` 来请求 20 个字节的 RAM。
120 |
121 | 请求的字节数适合的第一个RAM块是 200 字节块,因此 `pvPortMalloc()` 将 200 字节块拆分为一个 20 字节的块,以及一个 180 字节的块,然后返回指向 20 的字节块。 新的 180 字节块仍可用于将来调用 `pvPortMalloc()`。
122 |
123 | `heap_4` 将相邻空闲块的(合并)组合成一个更大的块,最大限度地降低了碎片的风险,并使其适用于重复分配和释放不同大小的 RAM 块的应用程序。
124 |
125 | 
126 |
127 | 图 7 演示了如何分配和释放具有内存合并工作的 heap\_4 首次拟合算法。 参考图 7:
128 |
129 | 1. A 显示了创建三个任务后的数组。 一个大的自由块保留在数组的顶部。
130 | 2. B 显示了删除一个任务后的数组。巨大的自由块仍在数组顶部。还有一个空闲块,是先前已分配任务 TCB 和堆栈,现在已经删除。 请注意,与演示 heap\_2 时不同,删除 TCB 时释放的内存以及删除堆栈时释放的内存不会保留为两个单独的空闲块,而是组合起来创建一个更大的单个空闲块。
131 | 3. C 显示创建 FreeRTOS 队列后的情况。 队列是使用 `xQueueCreate()` API 函数创建的,该函数在 4.3 节中描述。 `xQueueCreate()` 调用 `pvPortMalloc()` 来分配队列使用的 RAM。 由于 `heap_4` 使用第一适合算法,`pvPortMalloc()` 将从第一个空闲 RAM 块中分配 RAM,该块足够大以容纳队列,如图 7 所示,当任务被删除时,RAM 被释放。队列不会全部消耗然而,空闲块中的 RAM,因此块被分成两部分,未使用的部分仍然可用于将来调用 `pvPortMalloc()`。
132 | 4. D 显示了直接从应用程序代码调用 `pvPortMalloc()` 之后的情况,而不是通过 FreeRTOS API 函数的间接调用。 用户分配的块足够小,可以放入第一个空闲块,即分配给队列的内存和分配给后续 TCB 的内存之间的块。 删除任务时释放的内存现在已分成三个独立的块; 第一个块保存队列,第二个块保存用户分配的内存,第三个块保持空闲。
133 | 5. E 显示队列被删除后的情况,这会自动释放已分配给已删除队列的内存。现在,用户分配块的任一侧都有可用内存。
134 | 6. F 显示用户分配的内存已被释放后的情况。用户分配的块使用的内存已与任意一侧的空闲内存组合,创建一个更大的单个空闲块。
135 |
136 | `heap_4` 不是具有确定性的,但比 `malloc()` 和 `free()` 的大多数标准库实现更快。
137 |
138 | ### 设置 Heap\_4 使用的数组的起始地址
139 |
140 | 本节包含高级信息。若只是为了使用 `heap_4`,没有必要阅读或理解本节。
141 |
142 | 有时,应用程序编写者需要将 `heap_4` 使用的数组放在特定的内存地址。例如,FreeRTOS 任务使用的堆栈是从堆中分配的,因此可能需要确保堆位于快速的内部存储器中, 而不是缓慢的外部存储器。
143 |
144 | 默认情况下,`heap_4` 使用的数组在 `heap_4.c` 源文件中声明,其起始地址由链接器自动设置。 但是,如果在 `FreeRTOSConfig.h` 中将 `configAPPLICATION_ALLOCATED_HEAP` 编译时配置常量设置为 1,则必须由使用 FreeRTOS 的应用程序声明该数组。 如果数组声明为应用程序的一部分,则应用程序的编写者可以设置其起始地址。
145 |
146 | 如果在 `FreeRTOSConfig.h` 中将 `configAPPLICATION_ALLOCATED_HEAP` 设置为 1,则必须在其中一个应用程序的源文件中声明一个名为 `ucHeap` 的 `uint8_t` 数组,其大小为 `configTOTAL_HEAP_SIZE` 设置。
147 |
148 | 将变量放在特定内存地址所需的语法取决于所使用的编译器,因此请参阅编译器的文档。 两个编译器的示例如下:
149 |
150 | * 清单 2 显示 GCC 编译器声明数组所需的语法,并将数组放在名为 `.my_heap` 的内存部分中。
151 | * 清单 3 显示 IAR 编译器声明数组所需的语法,并将数组放在绝对地址 `0x20000000` 上 。
152 |
153 | ```c
154 | uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ( ( section( ".my_heap" ) ) );
155 | ```
156 |
157 | 清单 2. 使用 GCC 语法声明 `heap_4` 使用的数组,并将数组放在名为 `.my_heap` 的内存段中
158 |
159 | ```c
160 | uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] @ 0x20000000;
161 | ```
162 |
163 | 清单 3. 使用 IAR 语法声明 `heap_4` 将使用的数组,并将数组放在绝对地址 0x20000000。
164 |
165 | ### Heap\_5
166 |
167 | `heap_5` 用于分配和释放内存的算法与 `heap_4` 使用的算法相同。 与 `heap_4` 不同的是,`heap_5` 不限于从单个静态声明的数组中分配内存; `heap_5` 可以从多个独立的内存空间分配内存。 当运行 FreeRTOS 的系统提供的 RAM 在系统的内存映射中不显示为单个连续(无空间)块时,`heap_5` 非常有用。
168 |
169 | 在编写本文时,`heap_5` 是唯一提供的内存分配方案,必须在调用 `pvPortMalloc()` 之前显式初始化。 使用 `vPortDefineHeapRegions()` API函数初始化 `Heap_5`。 当使用 `heap_5` 时,必须先调用 `vPortDefineHeapRegions()`,然后才能创建任何内核对象(任务,队列,信号量等)。
170 |
171 | ### vPortDefineHeapRegions() API函数
172 |
173 | `vPortDefineHeapRegions()` 用于指定每个单独的内存区域的起始地址和大小,它们共同构成 `heap_5` 使用的总内存。
174 |
175 | ```c
176 | void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
177 | ```
178 |
179 | 清单 4. `vPortDefineHeapRegions()` API 函数原型
180 |
181 | 每个单独的存储区域由 `HeapRegion_t` 类型的结构描述。 所有可用内存区域的描述都作为 `HeapRegion_t` 结构数组传递给 `vPortDefineHeapRegions()`。
182 |
183 | ```c
184 | typedef struct HeapRegion
185 | {
186 | /* 将成为堆一部分的内存块的起始地址。 */
187 | uint8_t *pucStartAddress;
188 |
189 | /* 内存块的大小(字节)。 */
190 | size_t xSizeInBytes;
191 | } HeapRegion_t;
192 | ```
193 |
194 | 清单 5. `HeapRegion_t` 结构
195 |
196 | 表 5. `vPortDefineHeapRegions()` 参数
197 |
198 | | 参数名称/返回值 | 描述 |
199 | | -------- | -- |
200 |
201 | | pxHeapRegions | 指向 HeapRegion_t 结构数组开头的指针。 数组中的每个结构都描述了使用 heap_5 时将成为堆的一部分的内存区域的起始地址和长度。
数组中的 HeapRegion_t 结构必须按起始地址排序; 描述具有最低起始地址的存储区域的 HeapRegion_t 结构必须是数组中的第一个结构,并且描述具有最高起始地址的存储区域的 HeapRegion_t 结构必须是数组中的最后一个结构。
数组的末尾由 HeapRegion_t 结构标记,该结构的 pucStartAddress 成员设置为 NULL。
|
202 | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
203 |
204 | 
205 |
206 | 清单 6 显示了一个 `HeapRegion_t` 结构数组,它们共同描述了三个 RAM 块。
207 |
208 | ```c
209 | /* 定义三个RAM区域的起始地址和大小。 */
210 | #define RAM1_START_ADDRESS( ( uint8_t * ) 0x00010000 )
211 | #define RAM1_SIZE( 65 * 1024 )
212 |
213 | #define RAM2_START_ADDRESS( ( uint8_t * ) 0x00020000 )
214 | #define RAM2_SIZE( 32 * 1024 )
215 |
216 | #define RAM3_START_ADDRESS( ( uint8_t * ) 0x00030000 )
217 | #define RAM3_SIZE( 32 * 1024 )
218 |
219 | /* 创建一个 HeapRegion_t 定义数组,其中包含三个 RAM 区域的每个索引,并用空地址结束数组。
220 | HeapRegion_t 结构必须按开始地址顺序出现,包含最低开始地址的结构首先出现。*/
221 | const HeapRegion_t xHeapRegions[] =
222 | {
223 | { RAM1_START_ADDRESS, RAM1_SIZE },
224 | { RAM2_START_ADDRESS, RAM2_SIZE },
225 | { RAM3_START_ADDRESS, RAM3_SIZE },
226 | { NULL, 0 } /* 标记数组的结尾。 */
227 | };
228 |
229 | int main( void )
230 | {
231 | /* 初始化 heap_5。 */
232 | vPortDefineHeapRegions( xHeapRegions );
233 |
234 | /* 在这里添加应用代码。 */
235 | }
236 | ```
237 |
238 | 清单 6. 一个 `HeapRegion_t` 结构数组,它们共同描述了 RAM 的 3 个区域
239 |
240 | 虽然清单 6 正确描述了 RAM,但它没有演示一个可用的示例,因为它将所有 RAM 分配给堆,而没有 RAM 可供其他变量使用。
241 |
242 | 构建项目时,构建过程的链接阶段会为每个变量分配一个 RAM 地址。 可供链接器使用的 RAM 通常由链接器配置文件(如链接描述文件)描述。 在图 8 B 中,假设链接器脚本包含有关 RAM1 的信息,但不包括有关 RAM2 或 RAM3 的信息。 因此,链接器在 RAM1 中放置了变量,只留下了地址为 `0x0001nnnn` 的 RAM1 部分可供 heap\_5 使用。 `0x0001nnnn` 的实际值将取决于所链接的应用程序中包含的所有变量的总大小。 链接器使所有 RAM2 和所有 RAM3 都未使用,使得整个 RAM2 和整个 RAM3 可供 `heap_5` 使用。
243 |
244 | 如果使用清单 6 中所示的代码,则分配给地址 `0x0001nnnn`下面的 `heap_5` 的 RAM 将与用于保存变量的 RAM 重叠。 为避免这种情况,`xHeapRegions[]` 数组中的第一个 `HeapRegion_t` 结构可以使用起始地址`0x0001nnnn`,而不是起始地址 `0x00010000`。 但是,这不是推荐的解决方案,因为:
245 |
246 | 1. 起始地址可能不容易确定。
247 | 2. 链接器使用的 RAM 量可能会在将来的版本中发生变化,需要更新 `HeapRegion_t`结构中使用的起始地址。
248 | 3. 构建工具未知, 因此,如果链接器使用的 RAM 和 `heap_5` 使用的 RAM 重叠,则无法警告应用程序编写者。
249 |
250 | 清单 7 演示了一个更方便和可维护的示例。 它声明了一个名为 `ucHeap` 的数组。 `ucHeap` 是一个普通变量,因此它成为链接器分配给 RAM1 的数据的一部分。 `xHeapRegions` 数组中的第一个 `HeapRegion_t` 结构描述了 `ucHeap` 的起始地址和大小,因此 `ucHeap` 成为 `heap_5` 管理的内存的一部分。 可以增加`ucHeap` 的大小,直到链接器使用的 RAM 消耗所有 RAM1,如图 8 C 所示。
251 |
252 | ```c
253 | /* 定义链接器未使用的两个RAM区域的起始地址和大小。 */
254 | #define RAM2_START_ADDRESS( ( uint8_t * ) 0x00020000 )
255 | #define RAM2_SIZE( 32 * 1024 )
256 |
257 | #define RAM3_START_ADDRESS( ( uint8_t * ) 0x00030000 )
258 | #define RAM3_SIZE( 32 * 1024 )
259 |
260 | /* 声明一个数组,该数组将成为 heap_5 使用的堆的一部分。 该数组将由链接器放置在 RAM1 中。 */
261 | #define RAM1_HEAP_SIZE ( 30 * 1024 )
262 | static uint8_t ucHeap[ RAM1_HEAP_SIZE ];
263 |
264 | /* 创建一个 HeapRegion_t 数组。 而在清单 6 中,第一个条目描述了所有 RAM1,因此 heap_5
265 | 将使用所有 RAM1,这次第一个条目仅描述 ucHeap 数组,因此 heap_5 将仅使用包含 ucHeap
266 | 数组的 RAM1 部分。 HeapRegion_t 结构仍必须以起始地址顺序出现,其结构包含最先出现的最低起
267 | 始地址。*/
268 | const HeapRegion_t xHeapRegions[] =
269 | {
270 | { ucHeap, RAM1_HEAP_SIZE },
271 | { RAM2_START_ADDRESS, RAM2_SIZE },
272 | { RAM3_START_ADDRESS, RAM3_SIZE },
273 | { NULL, 0 } /* 标记数组的结尾。 */
274 | };
275 | ```
276 |
277 | 清单 7. 一个 `HeapRegion_t` 结构数组,描述所有 RAM2,所有 RAM3,但只是 RAM1 的一部分
278 |
279 | 清单7中演示的技术的优点包括:
280 |
281 | 1. 没有必要使用硬编码的起始地址。
282 | 2. `HeapRegion_t` 结构中使用的地址将由链接器自动设置,因此即使链接器使用的 RAM 量在将来的构建中发生更改,也始终是正确的。
283 | 3. 分配给 `heap_5` 的 RAM 不可能与链接器放入 RAM1 的数据重叠。
284 | 4. 如果 `ucHeap` 太大,应用程序将不会链接。
285 |
286 | ## 堆相关的实用函数
287 |
288 | ### xPortGetFreeHeapSize() API函数
289 |
290 | `xPortGetFreeHeapSize()` API 函数在调用函数时返回堆中的空闲字节数。它可用于优化堆大小。 例如,如果 `xPortGetFreeHeapSize()` 在创建了所有内核对象后返回 2000,那么 `configTOTAL_HEAP_SIZE` 的值可以减少 2000。
291 |
292 | 使用 `heap_3` 时,`xPortGetFreeHeapSize()`不可用。
293 |
294 | ```c
295 | size_t xPortGetFreeHeapSize( void );
296 | ```
297 |
298 | 清单 8. `xPortGetFreeHeapSize()`API 函数原型
299 |
300 | 表 6. `xPortGetFreeHeapSize()` 返回值
301 |
302 | | 参数名称/返回值 | 描述 |
303 | | -------- | ------------------------------------------ |
304 | | 返回值 | 调用 `xPortGetFreeHeapSize()` 时在堆中保持未分配的字节数。 |
305 |
306 | ### xPortGetMinimumEverFreeHeapSize() API函数
307 |
308 | `xPortGetMinimumEverFreeHeapSize()` API 函数返回自 FreeRTOS 应用程序开始执行以来堆中曾存在的最小未分配字节数。
309 |
310 | `xPortGetMinimumEverFreeHeapSize()` 返回的值表明应用程序已经接近耗尽堆空间。例如,如果 `xPortGetMinimumEverFreeHeapSize()` 返回200,那么,在应用程序开始执行后的某个时候,它会在耗尽堆空间的 200 字节之内。
311 |
312 | `xPortGetMinimumEverFreeHeapSize()` 仅在使用 `heap_4` 或 `heap_5` 时可用。
313 |
314 | ```c
315 | size_t xPortGetMinimumEverFreeHeapSize( void );
316 | ```
317 |
318 | 清单 9. `xPortGetMinimumEverFreeHeapSize()`API 函数原型
319 |
320 | 表 7. `xPortGetMinimumEverFreeHeapSize()`返回值
321 |
322 | | 参数名称/返回值 | 描述 |
323 | | -------- | ------------------------------------ |
324 | | 返回值 | 自 FreeRTOS 应用程序开始执行以来堆中已存在的最小未分配字节数。 |
325 |
326 | ### malloc 失败的钩子函数
327 |
328 | 可以直接从应用程序代码调用 `pvPortMalloc()`。 每次创建内核对象时,它也会在 FreeRTOS 源文件中调用。内核对象的示例包括任务,队列,信号量和事件组,所有这些都将在本书后面的章节中介绍。
329 |
330 | 就像标准库 `malloc()` 函数一样,如果 `pvPortMalloc()` 由于所请求大小的块不存在而无法返回 RAM 块,那么它将返回 `NULL`。 如果由于应用程序编写器正在创建一个内核对象而执行 `pvPortMalloc()`,并且对 `pvPortMalloc()` 的调用返回 `NULL`,则内核对象不会被创建。
331 |
332 | 如果对 `pvPortMalloc()` 的调用返回 `NULL`,则可以将所有示例堆配置方案配置为调用钩子(或回调)函数。
333 |
334 | 如果在 `FreeRTOSConfig.h` 中将 `configUSE_MALLOC_FAILED_HOOK` 设置为 1,则应用程序必须提供 malloc 失败的钩子函数,该函数具有由清单 10 显示的名称和原型。该函数可以以适合应用程序的任何方式实现。
335 |
336 | ```c
337 | void vApplicationMallocFailedHook( void );
338 | ```
339 |
340 | 清单 10. malloc 失败的钩子函数名和原型。
341 |
--------------------------------------------------------------------------------
/_book/gitbook/gitbook-plugin-lunr/lunr.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.12
3 | * Copyright (C) 2015 Oliver Nightingale
4 | * MIT Licensed
5 | * @license
6 | */
7 | !function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.5.12",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(t){return arguments.length&&null!=t&&void 0!=t?Array.isArray(t)?t.map(function(t){return t.toLowerCase()}):t.toString().trim().toLowerCase().split(/[\s\-]+/):[]},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,o=0;n>o;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;no;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;n FreeRTOSConfig.h:configUSE_16_BIT_TICKS配置用于保存RTOS滴答数的类型,因此似乎与事件组特性无关。它对EventBits_t类型的影响是FreeRTOS内部实现的结果,当FreeRTOS在一个能够比32位tvpes更有效地处理16位类型的架构上执行时,configUSE_16_BIT_TICKS应该只设置为1。
74 |
75 |
76 |
77 | ### 多任务访问
78 |
79 | 事件组是具有自身权限的对象,可以被任何知道其存在的任务或ISR访问。任意数量的任务可以在同一事件组中设置位,任意数量的任务可以从同一事件组中读取位。
80 |
81 |
82 |
83 | ### 一个使用事件组的实例
84 |
85 | FreeRTOS+TCP TCP/IP栈的实现提供了一个实例,说明如何使用事件组同时简化设计,并最小化资源使用。
86 |
87 | 一个TCP套接字必须响应许多不同的事件。事件的例子包括接受事件、绑定事件、读取事件和关闭事件。套接字在任何给定时间所期望的事件取决于套接字的状态。例如,如果套接字已经创建,但还没有绑定到地址,那么它可以预期接收绑定事件,但不会预期接收读事件(如果没有地址,它就无法读取数据)。
88 |
89 | 一个FreeRTOS+TCP套接字的状态保存在一个叫做FreeRTOS_Socket_t的结构中。该结构包含一个事件组,该事件组为套接字必须处理的每个事件定义一个事件位。FreeRTOS+TCP API调用这个阻塞来等待一个事件或一组事件,只是在事件组上阻塞。
90 |
91 | 事件组还包含一个中止位,允许TCP连接被终止,不管套接字当时在等待哪个事件。
92 |
93 |
94 |
95 | ## 使用事件组的事件管理
96 |
97 | ### xEventGroupCreate() API函数
98 |
99 | FreeRTOS V9.0.0还包括`xEventGroupCreateStatic()`函数,该函数在编译时分配静态创建事件组所需的内存:在使用事件组之前,必须显式地创建它。
100 |
101 | 使用EventGroupHandle_t类型的变量引用事件组。API函数`xEventGroupCreate()`用于创建事件组,并返回一个EventGroupHandle_t来引用它创建的事件组。
102 |
103 | ```groovy
104 | EventGroupHandle_t xEventGroupCreate( void );
105 | ```
106 |
107 | 清单 132. `xEventGroupCreate()` API函数原型
108 |
109 | 表 42. `xEventGroupCreate() `的返回值
110 |
111 | | 参数名 | 描述 |
112 | | :----- | ------------------------------------------------------------ |
113 | | 返回值 | 如果返回`NULL`,则不能创建事件组,因为没有足够的堆内存供FreeRTOS分配事件组数据结构。第2章提供了堆内存管理的更多信息。如果返回非null值,则表示事件组创建成功。返回值应该存储为创建的事件组的句柄。 |
114 |
115 |
116 |
117 | ### xEventGroupSetBits() API函数
118 |
119 | `xEventGroupSetBits()` API函数在事件组中设置一个或多个位,通常用于通知任务,由被设置的位表示的事件已经发生。
120 |
121 | > 注意:永远不要在中断服务程序中调用`xEventGroupSetBits()`。应该使用中断安全版本`xEventGroupSetBitsFromISR()`来代替它。
122 |
123 | ```groovy
124 | EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
125 | const EventBits_t uxBitsToSet );
126 | ```
127 |
128 | 清单133. `xEventGroupSetBits()` API函数原型
129 |
130 | 表 43. `xEventGroupSetBits()`参数和返回值
131 |
132 | | 参数名 | 描述 |
133 | | ----------- | ------------------------------------------------------------ |
134 | | `xEventGroup` | 要在其中设置位的事件组的句柄。事件组句柄已经从用于创建事件组的`xEventGroupCreate()`调用中返回。 |
135 | | `uxBitsToSet` | 一个位掩码,用于指定事件组中的事件位或事件位设置为1。事件组的值通过用`uxBitsToSet`传递的值按位或事件组的现有值来更新。例如,将`uxBitsToSe`t设置为0x04(二进制0100)将导致事件组中的事件位3被设置(如果它还没有被设置),而事件组中的所有其他事件位保持不变。 |
136 | | 返回值 | 调用`xEventGroupSetBits()`返回时事件组的值。注意,返回的值不一定是`uxBitsToSet`指定的位,因为这些位可能已经被另一个任务再次清除。 |
137 |
138 |
139 |
140 | ### xEventGroupSetBitsFromlSR() API函数
141 |
142 | `xEventGroupSetBitsFromlSR()`是`xEventGroupSetBits()`的中断安全版本。
143 |
144 | 给出信号量是一个确定性操作,因为预先知道给出信号量最多会导致一个任务离开阻塞状态。当在事件组中设置位时,不知道有多少任务将离开阻塞状态,因此在事件组中设置位不是确定性操作。
145 |
146 | FreeRTOS设计和实现标准不允许不确定的操作在一个中断服务程序执行,或者当中断禁用这个原因,`xEventGroupSetBitsFromlSR()`不直接设置事件比特在中断服务程序,而是延缓RTOS守护进程的行动任务。
147 |
148 | ```groovy
149 | BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
150 | const EventBits_t uxBitsToSet,
151 | BaseType_t *pxHigherPriorityTaskWoken );
152 | ```
153 |
154 | 清单 134. `xEventGroupSetBitsFromISR()` API函数原型
155 |
156 | 表 44. `xEventGroupSetBitsFromISR()`参数和返回值
157 |
158 | | 参数名 | 描述 |
159 | | ------------------------- | ------------------------------------------------------------ |
160 | | `xEventGroup` | 要在其中设置位的事件组的句柄。事件组句柄已经从用于创建事件组的`XEventGroupCreate()`调用中返回。 |
161 | | `uxBitsToSet` | 一个位掩码,用于指定事件组中的事件位或事件位设置为1。事件组的值通过用`uxBitsToSet`传递的值按位或事件组的现有值来更新。例如,将`uxBitsToSet`设置为0x05(二进制0101)将导致事件组中的事件位3和事件位0被设置(如果它们还没有被设置),而事件组中的所有其他事件位保持不变。 |
162 | | `pxHigherPriorityTaskWoken` | `xEventGroupSetBitsFromlSR()`不直接在中断服务例程中设置事件位,而是通过在定时器命令队列上发送命令,将操作推迟给RTOS守护进程任务。如果守护任务处于阻塞状态以等待定时器命令队列上的数据可用,那么写入定时器命令队列将导致守护任务离开阻塞状态。如果守护进程任务的优先级高于当前正在执行的任务(被中断的任务)的优先级,那么,`xEventGroupSetBitsFromlSR()`将在内部将`*pxHigherPriorityTaskWoken`设置为pdTRUE。如果`xEventGroupSetBitsFromlSR()`将这个值设置为pdTRUE,那么应该在中断退出之前执行上下文切换。这将确保中断直接返回到守护任务,因为守护任务将是最高优先级的就绪状态任务。 |
163 | | 返回值 | 有两个可能的返回值:
pdPASS
pdPASS只在数据成功发送到定时器命令队列时返回。
pdFALSE
如果设置位命令不能写入定时器命令队列,因为队列已经满了,将返回pdFALSE。|
164 |
165 | ### xEventGroupWaitBits() API函数
166 |
167 | `xEventGroupWaitBits()` APl函数允许任务读取事件组的值,并且可以选择在阻塞状态等待事件组中的一个或多个事件位被设置,如果这些事件位还没有设置。
168 |
169 | ```groovy
170 | EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
171 | const EventBits_t uxBitsToWaitFor,
172 | const BaseType_t xClearOnExit,
173 | const BaseType_t xWaitForAllBits,
174 | TickType_t xTicksToWait );
175 | ```
176 |
177 | 清单135. `xEventGroupWaitBits()` API函数原型
178 |
179 | 调度程序用来确定任务是否进入阻塞状态,以及任务何时离开阻塞状态的条件称为“解封条件”。解封条件由`uxBitsToWaitFor`和`xWaitForAllBits`参数值的组合指定:
180 |
181 | - `uxBitsToWaitFor`指定要测试事件组中的哪个事件位
182 | - `xWaitForAllBits`指定是使用位数OR测试,还是位数AND测试。
183 |
184 | 如果在调用`xEventGroupWaitBits()`时,任务的解锁条件得到满足,那么该任务将不会进入阻塞状态。
185 |
186 | 表45提供了导致任务进入阻塞状态或退出阻塞状态的条件示例。表45只显示了事件组和`uxBitsToWaitFor`值中最不重要的四个二进制位——这两个值的其他位被假定为零。
187 |
188 | 表45 `uxBitsToWaitFor`和`xWaitForAllBits`参数的影响
189 |
190 | | Existing Event Group Value | uxBitsToWaitFor value | xWaitForAllBitsvalue | Resultant Behavior |
191 | | -------------------------- | --------------------- | -------------------- | ------------------------------------------------------------ |
192 | | 0000 | 0101 | pdFALSE | 由于在事件组中没有设置0位或2位,调用任务将进入阻塞状态,并且在事件组中设置0位或2位时将离开阻塞状态。 |
193 | | 0100 | 0101 | pdTRUE | 由于0位和2位没有同时设置在事件组中,调用任务将进入阻塞状态,当0位和2位同时设置在事件组中,调用任务将离开阻塞状态。 |
194 | | 0100 | 0110 | pdFALSE | 调用任务不会进入阻塞状态,因为`xWaitForAllBits`是pdFALSE,并且`uxBitsToWaitFor`指定的两个位中的一个已经在事件组中设置。 |
195 | | 0100 | 0110 | pdTRUE | 由于`xWaitForAllBits`为pdTRUE,并且`uxBitsToWaitFor`指定的两个位中只有一个已经在事件组中设置,因此调用任务将进入阻塞状态。当事件组中的第2位和第3位都被设置时,任务将离开阻塞状态。 |
196 |
197 | 调用任务使用`uxBitsToWaitFor`参数指定要测试的位,调用任务很可能需要在满足解封条件后将这些位清除为零。事件位可以使用`xEventGroupClearBits()` API函数来清除,但如果使用该函数手动清除事件位将导致应用程序代码中的竞争条件:
198 |
199 | - 使用同一事件组的任务不止一个。
200 | - 位由不同的任务或中断服务程序在事件组中设置。
201 |
202 | 提供了`xClearOnExit`参数以避免这些潜在的竞争条件。如果`xClearOnExit`设置为pdTRUE,那么对调用任务来说,事件位的测试和清除似乎是一个原子操作(不能被其他任务或中断中断)。
203 |
204 | 表46 `xEventGroupWaitBits()`参数和返回值
205 |
206 | | 参数名 | 描述 |
207 | | ----------------- | ------------------------------------------------------------ |
208 | | `xEventGroup` | 事件组的句柄,其中包含正在读取的事件位。事件组句柄已经从用于创建事件组的`xEventGroupCreate()`调用中返回。 |
209 | | `uxBitsToWaitFor` | 指定事件组中要测试的事件位或事件位的位掩码。例如,如果调用任务想要等待事件位0和/或事件位2在事件组中被设置,那么将`uxBitsToWaitFor`设置为0x05(二进制0101)。更多的例子请参见表45。 |
210 | | `xClearOnExit` | 如果调用任务的解封条件已经被满足,并且`xClearOnExit`被设置为pdTRUE,那么在调用任务退出`xEventGroupWaitBits()` API函数之前,`uxBitsToWaitFor`指定的事件位将被清除回事件组中的0。如果`xClearOnExit`设置为pdFALSE,则事件组中的事件位的状态不会被`xEventGroupWaitBits()` API函数修改。 |
211 | | `xWaitForAllBits` | `uxBitsToWaitFor`参数指定要在事件组中测试的事件位。`xWaitForAllBits`指定当`uxBitsToWaitFor`参数指定的一个或多个事件位被设置时,或者只有当`uxBitsToWaitFor`参数指定的所有事件位被设置时,调用任务才应该从阻塞状态中删除。如果`xWaitForAllBits`为pdFALSE,那么一个任务进入阻塞状态等待其开启条件满足时将阻塞状态的任何部分规定`uxBitsToWaitFor`成为集(或指定的超时`xTicksToWait`参数到期)。示例请参见表45。 |
212 | | `xTicksToWait` | 任务保持阻塞状态以等待其解除阻塞条件满足的最大时间。如果`xTicksTolWait`为零,或者在调用`xEventGroupWaitBits()`时满足解封条件,则`xEventGroupWaitBits()`将立即返回。块时间以滴答周期指定,因此它所代表的绝对时间依赖于滴答频率。宏`pdMS_TO_TICKS()`可用于将以毫秒为单位指定的时间转换为以ticks为单位指定的时间。将`xTicksToWait`设置为`portMAX_DELAY`将导致任务无限期等待(不会超时),前提是在`FreeRTOSConfig.h`中将`INCLUDE_vTaskSuspend`设置为1。 |
213 | | 返回值 | 如果`xEventGroupWaitBits()`返回是因为调用任务的解封条件被满足,那么返回值是调用任务的解封条件被满足时的事件组的值(在`xClearOnExit`为pdTRUE时自动清除任何位之前)。在这种情况下,返回值也将满足解封条件。如果`xEventGroupWaitBits()`返回是因为`xTicksToWait`参数指定的块时间过期,那么返回的值是块时间过期时事件组的值。在这种情况下,返回值将不满足解封条件。 |
214 |
215 |
216 |
217 | ### 示例22. 实验事件组
218 |
219 | 这个例子演示了如何进行:
220 |
221 | - 创建事件组。
222 | - 从中断服务程序中设置事件组中的位。
223 |
224 | - 从任务中设置事件组中的位。
225 | - 阻塞事件组。
226 |
227 | `xEventGroupWaitBits()` `xWaitForAllBits`参数的效果是通过首先执行`xWaitForAllBits`设置为pdFALSE的示例,然后执行`xWaitForAllBits`设置为pdTRUE的示例来演示的。
228 |
229 | 事件位0和事件位1由一个任务设置。事件位2是由中断服务程序设置的。例程设置。这三个位通过#define语句被赋予描述性的名字,如清单136所示。
230 |
231 | ```groovy
232 | /*"事件组中事件位的定义。 */
233 | #define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* 事件位0,由任务设置。 */
234 | #define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* 事件位1,由任务设置。 */
235 | #define mainISR_BIT ( 1UL << 2UL ) /* 事件位2,由ISR设置。 */
236 | ```
237 |
238 | 清单136. 示例22中使用的事件位定义
239 |
240 | 清单137显示了设置事件位0和事件位1的任务的实现。它位于一个循环中,反复设置一个位,然后再设置另一个位,每次调用`xEventGroupSetBits()`之间有200毫秒的延迟。在设置每个位之前打印一个字符串,以允许在控制台中看到执行序列。
241 |
242 | ```groovy
243 | static void vEventBitSettingTask( void *pvParameters )
244 | {
245 | const TickType_t xDelay200ms = pdMS_TO_TICKS( 200UL ), xDontBlock = 0;
246 | for( ;; )
247 | {
248 | /*在开始下一个循环之前稍作延迟。 */
249 | vTaskDelay( xDelay200ms );
250 |
251 | /*输出一条消息,表示任务即将设置事件位0,然后设置事件位0。*/
252 | vPrintString( "Bit setting task -\t about to set bit 0.\r\n" );
253 | xEventGroupSetBits( xEventGroup, mainFIRST_TASK_BIT );
254 |
255 | /*在设置其他位之前稍作延迟。 */
256 | vTaskDelay( xDelay200ms );
257 |
258 | /*输出一个消息,说事件位1即将被任务设置,然后设置事件位1。*/
259 | vPrintString( "Bit setting task -\t about to set bit 1.\r\n" );
260 | xEventGroupSetBits( xEventGroup, mainSECOND_TASK_BIT );
261 | }
262 | }
263 | ```
264 |
265 | 清单137. 例22中设置事件组中两个比特的任务
266 |
267 | 清单138显示了在事件组中设置第2位的中断服务例程的实现。同样,在设置位之前打印一个字符串,以允许在控制台中看到执行序列。但是,在这种情况下,因为控制台输出不应该直接在中断服务例程中执行,所以使用`xTimerPendFunctionCallFromISR()`在RTOS守护进程任务的上下文中执行输出。
268 |
269 | 与前面的示例一样,中断服务程序是由一个简单的周期性任务触发的,该任务强制软件中断。在本例中,中断每500毫秒产生一次。
270 |
271 | ```groovy
272 | static uint32_t ulEventBitSettingISR( void )
273 | {
274 | /* 该字符串没有在中断服务程序中打印,而是被发送到RTOS守护进程任务中打印。因此,它被声明为static,以确保编译器不会在ISR的堆栈上分配字符串,因为当从守护进程任务打印字符串时,ISR的堆栈帧将不存在。*/
275 | static const char *pcString = "Bit setting ISR -\t about to set bit 2.\r\n";
276 | BaseType_t xHigherPriorityTaskWoken = pdFALSE;
277 | /*输出一个消息说第2位即将被设置。消息不能从ISR打印,因此通过挂起函数调用在RTOS守护进程任务上下文中运行,将实际输出推迟到RTOS守护进程任务。*/
278 | xTimerPendFunctionCallFromISR( vPrintStringFromDaemonTask,
279 | ( void * ) pcString,
280 | 0,
281 | &xHigherPriorityTaskWoken );
282 |
283 | /* 在事件组中设置位2。 */
284 | xEventGroupSetBitsFromISR( xEventGroup, mainISR_BIT, &xHigherPriorityTaskWoken );
285 |
286 | /* xTimerPendFunctionCallFromISR()和xEventGroupsetBitsFromISR()都写定时器命令队列,并使用相同的xHigherPriorityraskwoken变量。如果写入定时器命令队列导致RTOS守护进程任务,并且RTOS守护进程任务的优先级较高,则返回Blocked状态比当前正在执行的任务(此中断的任务)的优先级高然后xhigherprioritytaskkoken将被设置为pdTRUE。
287 |
288 | xHigherPriorityTaskwoken用作portYIELD FROM ISR()的参数。如果xHigherPriorityraskwoken等于pdTRUE,那么从ISR()调用portYIEID将请求一个上下文切换。如果xHigherPriorityraskwoken仍然是pdFALSE,那么从ISR()调用portYIELD将没有效果。
289 |
290 | 由windows端口使用的portYIELD_FROM_ISR()的实现包括一个返回语句,这就是为什么这个函数没有显式返回值。*/
291 | portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
292 | }
293 | ```
294 |
295 | 清单138. 例22中设置事件组中第2位的ISR
296 |
297 | 清单139显示了调用`xEventGroupWaitBits()`来阻塞事件组的任务的实现。任务为事件组中设置的每个位打印一个字符串。
298 |
299 | `xEventGroupWaitBits()` `xClearOnExit`参数被设置为pdTRUE,因此导致调用`xEventGroupWaitBits()`返回的事件位或位将在`xEventGroupWaitBits()`返回之前被自动清除。
300 |
301 | ```groovy
302 | static void vEventBitReadingTask( void *pvParameters )
303 | {
304 | EventBits_t xEventGroupValue;
305 | const EventBits_t xBitsToWaitFor = ( mainFIRST_TASK_BIT |
306 | mainSECOND_TASK_BIT |
307 | mainISR_BIT );
308 | for( ;; )
309 | {
310 | /* 阻塞以等待事件位在事件组中被设置。*/
311 | xEventGroupValue = xEventGroupWaitBits( /* 要读取的事件组。 */
312 | xEventGroup,
313 | /* 位测试。 */
314 | xBitsToWaitFor,
315 |
316 | /*如果满足解封条件,则在退出时清除位。*/
317 | pdTRUE,
318 |
319 | /*不要等待所有的位。对于第二次执行,该参数被设置为pdTRUE。 */
320 | pdFALSE,
321 |
322 | /* 不要超时。 */
323 | portMAX_DELAY );
324 |
325 | /*为设置的每个位打印一条消息。 */
326 | if( ( xEventGroupValue & mainFIRST_TASK_BIT ) != 0 )
327 | {
328 | vPrintString( "Bit reading task -\t Event bit 0 was set\r\n" );
329 | }
330 | if( ( xEventGroupValue & mainSECOND_TASK_BIT ) != 0 )
331 | {
332 | vPrintString( "Bit reading task -\t Event bit 1 was set\r\n" );
333 | }
334 | if( ( xEventGroupValue & mainISR_BIT ) != 0 )
335 | {
336 | vPrintString( "Bit reading task -\t Event bit 2 was set\r\n" );
337 | }
338 | }
339 | }
340 | ```
341 |
342 | 清单139. 例22中等待事件位被设置而阻塞的任务
343 |
344 | `main()`函数在启动调度程序之前创建事件组和任务。有关它的实现,请参见清单140。从事件组中进行读操作的任务优先级高于向事件组中进行写操作的任务优先级,确保每次满足读任务的解阻塞条件时,读任务都会抢占写任务。
345 |
346 | ```groovy
347 | int main(void)
348 | {
349 | /* 在使用事件组之前,必须先创建事件组。 */
350 | xEventGroup = xEventGroupCreate();
351 |
352 | /* 创建在事件组中设置事件位的任务。 */
353 | xTaskCreate(vEventBitSettingTask, "Bit Setter", 1000, NULL, 1, NULL);
354 |
355 | /* 创建等待事件组中事件位设置的任务。*/
356 | xTaskCreate(vEventBitReadingTask, "Bit Reader", 1000, NULL, 2, NULL);
357 |
358 | /* 创建用于周期性产生软件中断的任务。 */
359 | xTaskCreate(vInterruptGenerator, "Int Gen", 1000, NULL, 3, NULL);
360 |
361 | /*安装软件中断的处理程序。完成此操作所需的语法取决于所使用的FreeRTOS端口。这里显示的语法只能在FreeRros Windows端口中使用,在该端口中这种中断只能被模拟。 */
362 | vPortSetInterruptHandler(mainINTERRUPT_NUMBER, ulEventBitSettingISR);
363 |
364 | /* 启动调度程序,使创建的任务开始执行。 */
365 | vTaskStartScheduler();
366 |
367 | /* 启动调度程序,使已创建的任务开始执行。 */
368 | for (;;);
369 | return 0;
370 | }
371 | ```
372 |
373 | 清单140. 创建例22中的事件组和任务
374 |
375 | 在执行示例22时,将`xEventGroupWaitBits()` xWaitForAllBits参数设置为pdFALSE,所产生的输出如图73所示。在图73中,可以看到,由于调用`xEventGroupWaitBits()`中的xWaitForAllBits参数被设置为pdFALSE,从事件组中读取的任务将离开阻塞状态,并在每次设置任何事件位时立即执行。
376 |
377 | 
378 |
379 | 图73 示例22执行`xWaitForAllBits`设置为pdFALSE时产生的输出
380 |
381 | 在执行示例22时,将`xEventGroupWaitBits()` xWaitForAllBits参数设置为pdTRUE,所产生的输出如图74所示。在图74中可以看到,因为`xWaitForAllBits`参数被设置为pdTRUE,从事件组中读取的任务只有在设置了所有三个事件位之后才会离开状态阻塞。
382 |
383 | 
384 |
385 | 图74 将`xWaitForAlBits`设置为pdTRUE执行示例22时产生的输出
386 |
387 |
388 |
389 | ## 使用事件组进行任务同步
390 |
391 | 有时,应用程序的设计需要两个或多个任务来彼此同步。例如,考虑这样一种设计:任务A接收一个事件,然后将该事件所需的一些处理委托给另外三个任务:任务B、任务C和任务D。如果任务A无法接收到另一个事件,直到任务B、C和D都完成了对前一个事件的处理,那么这四个任务就需要彼此同步。每个任务的同步点将在该任务完成其处理之后,并且不能继续进行,直到其他每个任务都完成了同样的处理。只有当所有四个任务都到达它们的同步点时,任务A才能接收到另一个事件。
392 |
393 | 需要这种类型的任务同步的一个不那么抽象的例子可以在一个FreeRTOS+TCP演示项目中找到。演示在两个任务之间共享一个TCP套接字;一个任务向套接字发送数据,另一个任务从同一个套接字1接收数据。在确定其他任务不会再次尝试访问该套接字之前,关闭TCP套接字对任何一个任务来说都是不安全的。如果两个任务中有一个希望关闭套接字,那么它必须通知另一个任务它的意图,然后等待另一个任务停止使用该套接字,然后再继续。清单140所示的伪代码演示了将数据发送到希望关闭套接字的任务的场景。
394 |
395 | 清单140所展示的情景是微不足道的,因为只有两个任务需要互相同步,但很容易看出,如果有其他任务在执行同步,该方案会变得更复杂,需要更多的任务加入同步,如果其他的任务在执行依赖于套接字的处理的话。处理依赖于套接字被打开。
396 |
397 | > 套接字:在编写本文的时候,这是在任务之间共享单个FreeRTOS+TCP套接字的唯一方法。
398 |
399 | ```groovy
400 | void SocketTxTask(void *pvParameters)
401 | {
402 | xSocket_t xSocket;
403 | uint32_t ulTxCount = 0UL;
404 |
405 | for (;;)
406 | {
407 | /*创建一个新的socket。这个任务将发送到这个套接字,而另一个任务将从这个套接字接收。*/
408 | xSocket = FreeRTOS_socket(...);
409 |
410 | /* 连接套接字。 */
411 | FreeRTOS_connect(xSocket, ...);
412 |
413 | /* 使用队列将套接字发送到接收数据的任务。 */
414 | xQueueSend(xSocketPassingQueue, &xSocket, portMAX_DELAY);
415 |
416 | /* 在关闭套接字之前向套接字发送1000条消息。*/
417 | for (ulTxCount = 0; ulTxCount < 1000; ulTxCount++)
418 | {
419 | if (FreeRTOS_send(xSocket, ...) < 0)
420 | {
421 | /* 意外错误-退出循环,之后套接字将被关闭。 */
422 | break;
423 | }
424 | }
425 |
426 | /* 让Rx任务知道Tx任务想要关闭套接字。 */
427 | TxTaskWantsToCloseSocket();
428 |
429 | /* 这是Tx任务的同步点。Tx任务在这里等待Rx任务到达它的同步点。Rx任务只有在不再使用套接字时才会到达它的同步点,并且可以安全地关闭套接字。*/
430 | xEventGroupSync(...);
431 |
432 | /* 这两个任务都没有使用套接字。关闭连接,然后关闭套接字。*/
433 | FreeRTOS_shutdown(xSocket, ...);
434 | WaitForSocketToDisconnect();
435 | FreeRTOS_closesocket(xSocket);
436 | }
437 | }
438 | /*-----------------------------------------------------------*/
439 |
440 | void SocketRxTask(void *pvParameters)
441 | {
442 | xSocket_t xSocket;
443 |
444 | for (;;)
445 | {
446 | /* 等待接收由Tx任务创建并连接的套接字。 */
447 | xQueueReceive(xSocketPassingQueue, &xSocket, portMAX_DELAY);
448 |
449 | /* 继续接收套接字,直到Tx任务想要关闭套接字。 */
450 | while (TxTaskWantsToCloseSocket() == pdFALSE)
451 | {
452 | /* 接收然后处理数据。 */
453 | FreeRTOS_recv(xSocket, ...);
454 | ProcessReceivedData();
455 | }
456 |
457 | /*这是Rx任务的同步点——它只在不再使用套接字时到达这里,因此Tx任务关闭套接字是安全的。*/
458 | xEventGroupSync(...);
459 | }
460 | }
461 | ```
462 |
463 | 清单141.两个任务的伪代码,它们彼此同步以确保在套接字关闭之前,任何一个任务都不再使用共享的TCP套接字
464 |
465 | 事件组可用于创建同步点:
466 |
467 | - 必须参与同步的每个任务在事件组中被分配一个唯一的事件位。
468 | - 每个任务在到达同步点时设置自己的事件位。
469 |
470 | - 设置了自己的事件位后,每个任务阻塞在事件组上,等待代表所有其他同步任务的事件位也被设置。
471 |
472 | 但是,`xEventGroupSetBits()`和`xEventGroupWaitBits()` API函数不能在此场景中使用。如果使用它们,那么位的设置(指示一个任务已经到达它的同步点)和位的测试(确定其他同步任务是否已经到达它们的同步点)将作为两个独立的操作执行。为了了解为什么这是一个问题,考虑一个场景,任务A任务B和任务C尝试使用事件组进行同步:
473 |
474 | 1. 任务A和任务B已经到达同步点,事件位已经在事件组中设置,处于阻塞状态,等待任务C的事件位也被设置。
475 | 2. 任务C到达同步点后,使用`xEventGroupSetBits()`设置其在事件组中的位。一旦任务C的位被设置,任务A和任务B就会离开阻塞状态,并清除所有三个事件位。
476 |
477 | 3. 任务C然后调用`xEventGroupWaitBits()`等待这三个事件比特成为集,但到那时,所有三个事件已经被清除,任务和任务B离开各自的同步点,所以同步失败了。
478 |
479 | 要成功地使用事件组创建同步点,事件位的设置和后续事件位的测试必须作为一个单独的不可中断操作执行。为此提供了`xEventGroupSync()` API函数。
480 |
481 |
482 |
483 | ### xEventGroupSync() API函数
484 |
485 | 提供`xEventGroupSync()`允许两个或多个任务使用事件组彼此同步。该功能允许任务在一个事件组中设置一个或多个事件位,然后等待在同一事件组中设置多个事件位的组合,作为一个单独的不可中断操作。
486 |
487 | `xEventGroupSync()` uxBitsTolaitFor参数指定调用任务的解除阻塞条件。如果`xEventGroupSync()`返回是因为满足了不阻塞条件,`uxBitsTolaitFor`指定的事件位将在`xEventGroupSync()`返回之前被清除回零。
488 |
489 | ```groovy
490 | EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup,
491 | const EventBits_t uxBitsToSet,
492 | const EventBits_t uxBitsToWaitFor,
493 | TickType_t xTicksToWait);
494 | ```
495 |
496 | 清单142. `xEventGroupSync()` API函数原型
497 |
498 | 表47. `xEventGroupSync()`参数和返回值
499 |
500 | | 参数名 | 描述 |
501 | | --------------- | ------------------------------------------------------------ |
502 | | `xEventGroup` | 要在其中设置事件位然后测试的事件组的句柄。事件组句柄已经从用于创建事件组的`xEventGroupCreate()`调用中返回。 |
503 | | `uxBitsToSet` | 一个位掩码,用于指定事件组中的事件位或事件位设置为1。事件组的值通过用`uxBitsToSet`传递的值按位或事件组的现有值来更新。例如,将`uxBitsToSet`设置为0x04(二进制0100)将导致事件3位被设置(如果它还没有被设置),而事件组中的所有其他事件位保持不变。 |
504 | | `uxBitsToWaitFor` | 指定事件组中要测试的事件位或事件位的位掩码。例如,如果调用任务想要等待事件位0,1和2在事件组中被设置,那么将`uxBitsToWaitFor`设置为0x07(二进制111). |
505 | | `xTicksToWait` | 任务保持阻塞状态以等待其解除阻塞条件满足的最大时间。如果`xTicksToWait`为零,或者在调用`xEventGroupSync()`时满足解除条件,则`xEventGroupSync()`将立即返回。块时间以滴答周期指定,因此它所代表的绝对时间依赖于滴答频率。宏`pdMS_TO_TICKS()`可用于将以毫秒为单位的时间转换为以节拍为单位的时间。将`xTicksToWait`设置为`portMAX_DELAY`将导致任务无限期等待(不会超时),前提是在`FreeRTOSConfig.h`中将`INCLUDE_vTaskSuspend`设置为1。 |
506 | | 返回值 | 如果`xEventGroupSync()`返回是因为调用任务的阻塞条件被满足,那么返回的值是调用任务的阻塞条件被满足时的事件组的值(在任何位被自动清除回零之前)。在这种情况下,返回值也将满足调用任务的阻塞条件。如果`xEventGroupSync()`返回是因为`xTicksToWait`参数指定的块时间过期,那么返回的值是块时间过期时事件组的值。在这种情况下,返回值将不满足调用任务的阻塞条件。 |
507 |
508 |
509 |
510 | ### 示例23. 同步任务
511 |
512 | 示例23使用`xEventGroupSync()`同步一个任务实现的三个实例。任务参数用于将任务调用`xEventGroupSync()`时设置的事件位传递给每个实例。
513 |
514 | 任务在调用`xEventGroupsync()`之前打印一条消息,在调用`xEventGroupsync()`返回之后再次打印一条消息。每条消息都包含一个时间戳。这允许在生成的输出中观察执行顺序。伪随机延迟用于防止所有任务同时到达同步点。
515 |
516 | 有关任务的实现,请参见清单143。
517 |
518 | ```groovy
519 | static void vSyncingTask(void *pvParameters)
520 | {
521 | const TickType_t xMaxDelay = pdMS_TO_TICKS(4000UL);
522 | const TickType_t xMinDelay = pdMS_TO_TICKS(200UL);
523 | TickType_t xDelayTime;
524 | EventBits_t uxThisTasksSyncBit;
525 | const EventBits_t uxAllSyncBits = (mainFIRST_TASK_BIT |
526 | mainSECOND_TASK_BIT |
527 | mainTHIRD_TASK_BIT);
528 |
529 | /*创建该任务的三个实例-每个任务在同步中使用不同的事件位。使用的事件位通过任务参数传递到每个任务实例。将其存储在uxthisaskssyncbit变量中。*/
530 | uxThisTasksSyncBit = (EventBits_t)pvParameters;
531 |
532 | for (;;)
533 | {
534 | /*通过延迟一个伪随机时间来模拟这个任务花费一些时间来执行一个动作。这可以防止该任务的所有三个实例同时到达同步点,因此可以更容易地观察示例的行为。*/
535 | xDelayTime = (rand() % xMaxDelay) + xMinDelay;
536 | vTaskDelay(xDelayTime);
537 |
538 | /*打印一条消息,显示这个任务已经到达它的同步点。pcTaskGetTaskName()是一个API函数,它返回在创建任务时分配给任务的名称。*/
539 | vPrintTwoStrings(pcTaskGetTaskName(NULL), "reached sync point");
540 |
541 | /*等待所有任务到达各自的同步点。*/
542 | xEventGroupSync(/* 用于同步的事件组。 */
543 | xEventGroup,
544 |
545 | /* 该任务设置的位,表示它已到达同步点。 */
546 | uxThisTasksSyncBit,
547 |
548 | /*需要等待的比特,每个参与同步的任务都有一个位*/
549 | uxAllSyncBits,
550 |
551 | /*无限等待所有三个任务到达vnchronization点。*/
552 | portMAX_DELAY);
553 |
554 | /*打印一条消息,显示这个任务已经通过了它的同步点。由于使用了无限期延迟,所以只有在所有任务到达各自的同步点后才会执行下面的行。*/
555 | vPrintTwoStrings(pcTaskGetTaskName(NULL), "exited sync point");
556 | }
557 | }
558 | ```
559 |
560 | 清单143. 示例23中使用的任务的实现
561 |
562 | `main()`函数创建事件组,创建所有三个任务,然后启动调度程序。有关它的实现,请参见清单144。
563 |
564 | ```groovy
565 | /* 事件组中的事件位的定义。 */
566 | #define mainFIRST_TASK_BIT (1UL << 0UL) /* 事件位0,由第一个任务设置。 */
567 | #define mainSECOND_TASK_BIT(1UL << 1UL) /* 事件位1,由第二个任务设置。 */
568 | #define mainTHIRD_TASK_BIT (1UL << 2UL) /* 事件位2,由第三个任务设置。 */
569 |
570 | /* 声明用于同步三个任务的事件组。 */
571 | EventGroupHandle_t xEventGroup;
572 |
573 | int main(void)
574 | {
575 | /* 在使用事件组之前,必须先创建它。 */
576 | xEventGroup = xEventGroupCreate();
577 |
578 | /*创建任务的三个实例。每个任务都有一个不同的名称,稍后将打印出来,以直观地指示正在执行的任务。当任务到达其同步点时要使用的事件位使用任务参数传递给任务。*/
579 | xTaskCreate(vSyncingTask, "Task 1", 1000, mainFIRST_TASK_BIT, 1, NULL);
580 | xTaskCreate(vSyncingTask, "Task 2", 1000, mainSECOND_TASK_BIT, 1, NULL);
581 | xTaskCreate(vSyncingTask, "Task 3", 1000, mainTHIRD_TASK_BIT, 1, NULL);
582 |
583 | /* 启动调度器,使创建的任务开始执行。 */
584 | vTaskStartScheduler();
585 |
586 | /* 像往常一样,永远不要达到下面这行。 */
587 | for (;;);
588 | return 0;
589 | }
590 | ```
591 |
592 | 清单144. 示例23中使用的`main()`函数
593 |
594 | 执行示例23时产生的输出如图75所示。可以看到,即使每个任务在不同的(伪随机)时间到达同步点,每个任务在同一时间退出同步点1(这是最后一个任务到达同步点的时间)。
595 |
596 | > 同步点:图75显示了在FreeRTOS Windows端口中运行的示例,它不提供真正的实时行为(特别是当使用Windows系统调用打印到控制台时),因此会显示一些时间变化。
597 |
598 |
599 |
600 | 
601 |
602 | 图75执行示例23时产生的输出
603 |
604 |
605 |
606 |
--------------------------------------------------------------------------------
/zi-yuan-guan-li.md:
--------------------------------------------------------------------------------
1 | # **资源管理**
2 |
3 |
4 |
5 | ## 引言及范围
6 |
7 | 在多任务系统中,如果一个任务开始访问资源,但在转换出运行状态之前没有完成其访问,则可能发生错误。如果任务使资源处于不一致的状态,那么任何其他任务或中断对相同资源的访问都可能导致数据损坏或其他类似问题。
8 |
9 | 以下是一些例子:
10 |
11 | 1. 访问外设
12 | 考虑以下场景,其中两个任务试图写入一个液晶显示器(LCD)。
13 |
14 | 1. 任务A执行并开始写入字符串 “Hello world”到LCD。
15 | 2. 在输出字符串“Hello w”的开头部分后,任务A被任务B抢占。
16 | 3. 任务B在进入阻塞状态前将“中止, 重试, 失败?”写入LCD。
17 | 4. 任务A从它被抢占的位置继续,并完成其字符串-“orld”的剩余字符的输出。
18 |
19 | 液晶显示器现在显示损坏的字符串“Hello w中止, 重试, 失败? orld”。
20 |
21 | 2. 读、修改、写操作
22 | 清单111显示了一行C代码,以及一个如何将C代码转换为汇编代码的示例。可以看到,`PORTA`的值首 先从内存中读入寄存器,在寄存器中修改,然后写回内存。这被称为读、修改、写操作。
23 |
24 | ```
25 | /* 正在编译的C代码。*/
26 | PORTA |= 0x01;
27 |
28 | /* 编译C代码时生成的汇编代码。*/
29 | LOAD R1, [#PORTA] ; Read a value from PORTA into R1
30 | MOVE R2, #0x01 ; Move the absolute constant 1 into R2
31 | OR R1, R2 ; Bitwise OR R1 (PORTA) with R2 (constant 1)
32 | STORE R1, [#PORTA] ; Store the new value back to PORTA
33 | ```
34 |
35 | 清单 111. 读、修改、写顺序的示例
36 |
37 |
38 |
39 | 这是一个“非原子”操作,因为它需要多个指令才能完成,并且可以被中断。考虑以下场景,其中两个任务试图更新名为`PORTA`的内存映射寄存器。
40 |
41 | 1. 任务A将`PORTA`的值加载到寄存器中——操作的读部分。
42 | 2. 任务A在完成同一操作的修改和写部分之前被任务B抢占。
43 | 3. 任务B更新`PORTA`值。则进入阻塞状态。
44 | 4. 任务A从它被抢占的位置继续执行。在将更新后的值写回`PORTA`之前,它修改已经保存在寄存器中的`PORTA`值的副本。
45 |
46 | 在这个场景中,任务A更新和回写`PORTA`的过期值。任务B在任务A获取`PORTA`值的副本之后修改`PORTA`,并且 在任务A将其修改后的值写回`PORTA`寄存器之前修改`PORTA`。当任务A写入`PORTA`时,它会覆盖任务B已经执行的修改,从而有效地破坏`PORTA`寄存器值。
47 |
48 | 本例使用外设寄存器,但在对变量执行读、修改、写操作时也适用相同的原则。
49 |
50 | 3. 变量的非原子访问
51 |
52 | 更新结构的多个成员,或更新比体系结构的自然字长更大的变量(例如,在16位机器上更新32位变量),这些都是非原子操作的例子。如果它们被打断,可能会导致数据丢失或损坏。
53 |
54 | 4. 函数可重入性
55 |
56 | 如果从多个任务调用函数是安全的,那么该函数就是可重入的。或者来自任务和中断。重入函数被称为线程安全,因为它们可以从多个执行线程访问,而不会有数据或逻辑操作被破坏的风险。
57 |
58 | 每个任务维护自己的堆栈和自己的一组处理器(硬件)寄存器值。如果函数只访问存储在堆栈或寄存器中的数据,那么该函数是可重入的,线程安全的。清单112是一个 重入函数的例子。清单113是一个非可重入函数的例子。
59 |
60 | ```groovy
61 | /* 传递一个参数给函数。这将被传递到堆栈上。或在处理器寄存器中。两种方法都是安全的,因为调用该函数的每个任务或中断都维护自己的堆栈和自己的寄存器值集,所以调用该函数的每个任务或中断都有自己的
62 | 1Var1副本。*/
63 | long lAddOneHundred( long lVar1 )
64 | {
65 | /*该函数的作用域变量也将被分配到堆栈或寄存器,这取决于编译器和优化级别。每个调用该函数的任务或中断都有自己的1Var2副本。*/
66 | long lVar2;
67 |
68 | lVar2 = lVar1 + 100;
69 | return lVar2;
70 | }
71 | ```
72 |
73 | 清单112. 一个可重入函数的例子
74 |
75 | ```groovy
76 | /*在本例中,1Varl是一个全局变量,因此每个调用1NonsenseFunction的任务都会访问该变量的同一个
77 | 副本。*/
78 | long lVar1;
79 |
80 | long lNonsenseFunction( void )
81 | {
82 | /* lstate是静态的,所以没有在堆栈上分配。每个调用该函数的任务都将访问变量的同一副本。*/
83 | static long lState = 0;
84 | long lReturn;
85 |
86 | switch( lState )
87 | {
88 | case 0 : lReturn = lVar1 + 10;
89 | lState = 1;
90 | break;
91 | case 1 : lReturn = lVar1 + 20;
92 | lState = 0;
93 | break;
94 | }
95 | }
96 | ```
97 |
98 | 清单113. 一个非可重入函数的例子
99 |
100 |
101 |
102 | ### 互斥
103 |
104 | 为了确保始终保持数据一致性,可以访问任务之间或任务与中断之间共享的资源。必须使用“互斥”进行管理。目标是确保,一旦一个任务开始访问不可重入且非线程安全的共享资源,在资源返回到一致状态之前,相同的任务对资源具有独占访问权。
105 |
106 | FreeRTOS提供了几个可以用来实现互斥的特性。但是,最好的互斥方法是(只是可能,因为通常不实用)将应用程序设计成不共享资源的方式,并且只从单个任务访问每个资源。
107 |
108 |
109 |
110 | ### 范围
111 |
112 | 本章旨在让读者更好地理解:
113 |
114 | - 何时以及为什么需要资源管理和控制。
115 | - 多么关键的部分啊。
116 |
117 | - 互斥是什么意思。
118 | - 暂停调度器意味着什么。
119 |
120 | - 如何使用互斥锁。
121 | - 如何创建和使用看门人任务。
122 |
123 | - 什么是优先级反转,以及优先级继承如何减少(而不是消除)它的影响。
124 |
125 |
126 |
127 | ## 关键部分和暂停调度器
128 |
129 | ### 基本的关键部分
130 |
131 | 基本临界区是由调用宏`taskENTER_critical()`和`taskEXIT_CRITICAL()`包围的代码区域有个。关键部分也是被称为临界区域。
132 |
133 | `taskENTER_CRITICAL()`和`taskEXIT_CRITICAL()`不接受任何参数,或返回一个值。注释清单114演示了它们的使用。
134 |
135 | > 值:像macro这样的函数并不像实际函数那样“返回一个值”。本书将术语“返回值”应用到宏上,最简单的做法是把宏看成一个函数。
136 |
137 | ```groovy
138 | /*确保对PORTA寄存器的访问不会被中断,将其置于临界区,进入临界区。*/
139 | taskENTER_CRITICAL();
140 |
141 | /*在调用taskENTER_CRITICAL()和调用taskEXIT_CRITICAL()之间不能切换到另一个任务。中断仍然可以在允许中断嵌套的EreeRTos端口上执行,但只允许逻辑优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY常量的值的中断执行,并且这些中断不允许调用FreeRTOS API函数。*/
142 | PORTA |= 0x01;
143 |
144 | /*对PORTA的访问已经完成,因此可以安全退出临界区。*/
145 | taskEXIT_CRITICAL();
146 | ```
147 |
148 | 清单111. 使用临界区来保护对寄存器的访问
149 |
150 | 本书附带的示例项目使用了一个名为`vPrintString()`的函数来将字符串写入标准输出(即使用FreeRTOS Windows端口时的终端窗口)。`vPrintString()`从许多不同的任务调用;因此,理论上,它的实现可以使用临界区保护对标准输出的访问,如清单115所示。
151 |
152 | ```groovy
153 | void vPrintString( const char *pcString )
154 | {
155 | /*将字符串写入标准输出,使用临界区作为一个粗略的互斥方法。 */
156 | taskENTER_CRITICAL();
157 | {
158 | printf( "%s", pcString );
159 | fflush( stdout );
160 | }
161 | taskEXIT_CRITICAL();
162 | }
163 | ```
164 |
165 | 清单115. `vPrintString()`的一个可能实现
166 |
167 | 以这种方式实现的临界区是一种提供互斥的非常粗糙的方法。它们通过禁用中断来工作,要么完全禁用,要么直到`configMAX_SYSCALL_INTERRUPT_PRIORITY`设置的中断优先级—取决于正在使用的FreeRTOS por。抢占式的上下文切换只能发生在一个中断中,因此,只要中断保持禁用,调用`taskENTER_CRITICAL()`的任务就会被保证保持在运行状态,直到临界区段退出。
168 |
169 | 基本临界段必须保持很短,否则它们将对中断响应时间产生不利影响。对`taskENTER_CRITICAL()`的每个调用都必须与对`taskEXIT_CRITICAL()`的调用紧密匹配。因此,不应该使用临界区来保护标准输出(stdout,即计算机写入其输出数据的流)(如清单115所示),因为向终端写入数据可能是一个相对较长的操作。本章的例子探讨了可选的解决方案。
170 |
171 | 关键部分嵌套是安全的,因为内核保留了嵌套深度的计数。只有当嵌套深度返回0时,即每次调用`taskEXIT_CRITICAL()`都执行一次对`taskENTER_CRITICAL()`的调用,临界区才会退出。
172 |
173 | 调用`taskENTER_CRITICAL()`和`taskEXIT_CRITICAL()`是任务改变FreeRTOS运行的处理器的中断启用状态的唯一合法方法。通过任何其他方法改变中断启用状态都会使宏的嵌套计数无效。
174 |
175 | `taskENTER_CRITICAL()`和`taskEXIT_CRITICAL()`不会以“FromlSR”结束,所以一定不能从中断服务程序中调用。`taskENTER_CRITICAL_FROM_ISR()` 是一个中断安全版本的`taskENTER_CRITICAL()`,和`taskEXIT_CRITICAL_FROM_ISR()`是一个允许中断嵌套的FreeRTOS端口,它们将在不允许中断嵌套的端口中被废弃。
176 |
177 | `taskENTER_CRITICAL_FROM_ISR()`返回一个值,这个值必须被传递到匹配的调用`taskEXIT_CRITICAL_FROM_ISR()`。清单116演示了这一点。
178 |
179 | ```groovy
180 | void vAnInterruptServiceRoutine( void )
181 | {
182 | /*声明一个变量,该变量的返回值来自taskENTER_CRITICAL_FROM_ISR()将被保存。*/
183 | UBaseType_t uxSavedInterruptStatus;
184 |
185 | /* ISR的这一部分可以被任何更高优先级的中断中断。*/
186 | /*使用taskENTER_CRITICAL_FROM_ISR()来保护该ISR的一个区域。保存从taskENTER_CRITICAL_FROM_ISR()返回的值,以便它可以被传递到匹配的调用taskEXIT_CRITICAL_FROM_ISR()。*/
187 | uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
188 |
189 | /* ISR的这一部分在调用taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()之间,所以只能被优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY常量设置的优先级。*/
190 |
191 | /*通过调用taskEXIT_CRITICAL_FROM_ISR()再次退出临界区,并传入由匹配的调用返回的值taskENTER_CRITICAL_FROM_ISR()。*/
192 | taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
193 | /* ISR的这一部分可以被任何更高优先级的中断中断。*/
194 | }
195 | ```
196 |
197 | 清单116. 在中断服务程序中使用临界区
198 |
199 | 使用更多的处理时间执行进入并随后退出临界区段的代码,而不是执行实际受临界区段保护的代码,这是一种浪费。基本临界区段进入和退出都非常快,而且总是确定性的,因此当受保护的代码区域非常短时,它们的使用非常理想。
200 |
201 |
202 |
203 | ### 挂起(或锁定)调度器
204 |
205 | 还可以通过挂起调度器来创建临界区。暂停调度器有时也被称为“锁定”调度器。
206 |
207 | 基本临界区保护代码区域不受其他任务和中断的访问。通过暂停调度器实现的临界区仅保护代码区域不被其他任务访问。因为中断仍然处于启用状态。
208 |
209 | 如果临界区段太长,不能简单地通过禁用中断来实现,则可以通过暂停调度程序来实现。然而,在调度器挂起时中断活动会使恢复(或“取消挂起”)调度器成为一个相对较长的操作,因此必须考虑在每种情况下使用哪种方法最好。
210 |
211 |
212 |
213 | ### vTaskSuspendAll() API函数
214 |
215 | ```groovy
216 | void vTaskSuspendAll( void );
217 | ```
218 |
219 | 清单 117. `vTaskSuspendAll()` API函数原型
220 |
221 | 调度程序通过调用`vTaskSuspendAll()`被挂起。挂起调度程序可以防止发生上下文切换,但保留中断。如果在调度器挂起时中断请求上下文切换,则请求将被挂起。并且只在恢复(未挂起)调度程序时执行。
222 |
223 | 当调度器挂起时,不能调用FreeRTOS API函数。
224 |
225 |
226 |
227 | ### xTaskResumeAll()的API函数
228 |
229 | ```groovy
230 | BaseType_t xTaskResumeAll( void );
231 | ```
232 |
233 | 清单118. `xTaskResumeAll()` API函数原型
234 |
235 | 通过调用`xTaskResumeAl()`恢复(未挂起)调度器。
236 |
237 | 表 40. `xTaskResumeAll()`的返回值
238 |
239 | | 返回值 | 描述 |
240 | | ------ | ------------------------------------------------------------ |
241 | | 返回值 | 在调度器挂起时请求的上下文切换将被挂起并仅在恢复调度器时执行。如果在`xTaskResumeAll()`返回之前执行了挂起的上下文切换,则返回pdTRUE。否则返回pdFALSE。 |
242 |
243 | 对`vTaskSuspendAll()`和`xTaskResumeAl()`的调用嵌套是安全的,因为内核保留了嵌套深度的计数。调度器只有在嵌套深度返回0时才会恢复,也就是在每次调用`vTaskSuspendAll()`都执行一次`xTaskResumeAll()`时。
244 |
245 | 清单119显示了`vPrintString()`的实际实现,它挂起调度程序以保护对终端输出的访问。
246 |
247 | ```groovy
248 | void vPrintString( const char *pcString )
249 | {
250 | /*将字符串写入标准输出,以互斥的方式挂起调度程序*/
251 | vTaskSuspendScheduler();
252 | {
253 | printf( "%s", pcString );
254 | fflush( stdout );
255 | }
256 | xTaskResumeScheduler();
257 | }
258 | ```
259 |
260 | 清单119. `vPrintString()`的实现
261 |
262 |
263 |
264 | ### 互斥锁(和二进制信号量)
265 |
266 | 互斥锁是一种特殊类型的二进制信号量,用于控制对两个或多个任务共享的资源的访问。MUTEX这个词来源于“互斥”。`configUSE_MUTEXES`必须在`FreeRTOSConfig.h`中设置为1,互斥才能可用。
267 |
268 | 当在互斥场景中使用互斥时,可以将互斥看作与共享资源相关联的令牌。以便任务合法地访问资源。它必须首先成功地“接受”令牌(作为令牌持有者)。当令牌持有者使用完资源后,它必须“归还”令牌。只有当该令牌被返回时,另一个任务才能成功地获取该令牌,然后安全地访问相同的共享资源。任务不允许访问共享资源,除非它持有令牌。这种机制如图63所示。
269 |
270 | 尽管互斥锁和二进制信号量共享许多特性。图63所示的场景(其中使用互斥锁进行互斥)与图53所示的场景(其中使用二进制信号量进行同步)完全不同。主要的区别在于信号量被获取后发生了什么:
271 |
272 | - 必须始终返回用于互斥的信号量。
273 | - 用于同步的信号量通常被丢弃且不返回。
274 |
275 | 
276 |
277 | 图63 使用互斥对象实现的互斥
278 |
279 | 该机制纯粹通过应用程序编写人员的规程来工作。没有理由一个任务不能在任何时候访问资源,但是每个任务“同意”不这样做。除非它能成为互斥锁持有者。
280 |
281 |
282 |
283 | ### xSemaphoreCreateMutex() API函数
284 |
285 | FreeRTOS V9.0.0还包括`xSemaphoreCreateMutexStatic()`函数,该函数在编译时分配静态创建互斥锁所需的内存:互斥锁是一种信号量类型。所有不同类型的FreeRTOS信号量的句柄都存储在`SemaphoreHandle_t`类型的变量中。
286 |
287 | 在使用互斥锁之前,必须先创建它。要创建互斥量类型的信号量,请使用`xSemaphoreCreateMutex()` APl函数。
288 |
289 | ```groovy
290 | SemaphoreHandle_t xSemaphoreCreateMutex( void );
291 | ```
292 |
293 | 清单120. `xSemaphoreCreateMutex()` API函数原型
294 |
295 | 表 41. `xSemaphoreCreateMutex()`的返回值
296 |
297 | | 参数名称/返回值 | 描述 |
298 | | --------------- | ------------------------------------------------------------ |
299 | | 返回值 | 如果返回`NULL`,则无法创建互斥锁,因为没有足够的堆内存供FreeRTOS分配互斥锁数据结构。第2章提供了堆内存管理的更多信息。非`NULL`返回值表明互斥锁已经成功创建。返回值应该存储为创建的互斥锁的句柄。 |
300 |
301 |
302 |
303 | ### 示例20. 重写vPrintString()以使用信号灯
304 |
305 | 本例创建了`vPrintString()`的新版本,称为`prvNewPrintString()`,然后从多个任务中调用这个新函数。`prvNewPrintString()`在功能上与`vPrintString()`相同,但使用互斥锁来控制对标准输出的访问,而不是通过锁定调度器。清单121显示了`prvNewPrintString()`的实现。
306 |
307 | ```groovy
308 | static void prvNewPrintString( const char *pcString )
309 | {
310 | /*互斥锁是在调度器启动之前创建的,所以在任务执行时已经存在尝试获取互斥锁,如果互斥锁不能立即可用,则无限期阻塞以等待它。对xSemaphoreTake()的调用只有在成功获得互斥锁时才会返回。所以不需要检查函数的返回值。如果使用了任何其他延迟周期,那么代码必须在访问共享资源(在本例中是标准输出)之前检查xSemaphorerake()是否返回pdTRUE。正如本书前面提到的,不建议在生产代码中使用无限期超时。*/
311 | xSemaphoreTake( xMutex, portMAX_DELAY );
312 | {
313 | /*只有成功获得互斥锁后,才会执行下面的行。现在可以自由访问标准输出,因为在任何时候只有一个任务可以拥有互斥锁。*/
314 | printf( "%s", pcString );
315 | fflush( stdout );
316 |
317 | /* 互斥锁必须返回! */
318 | }
319 | xSemaphoreGive( xMutex );
320 | }
321 | ```
322 |
323 | 清单121. `prvNewPrintString()`的实现
324 |
325 | `prvNewPrintString()`由`prvPrintTask()`实现的任务的两个实例反复调用。每次调用之间使用随机延迟时间。task参数用于向任务的每个实例传递唯一的字符串。清单122显示了`prvPrintTask()`的实现。
326 |
327 | ```groovy
328 | static void prvPrintTask( void *pvParameters )
329 | {
330 | char *pcStringToPrint;
331 | const TickType_t xMaxBlockTimeTicks = 0x20;
332 | /*创建该任务的两个实例。任务打印的字符串“使用任务的参数传递给任务”。参数被转换为所需的类型。*/
333 | pcStringToPrint = ( char * ) pvParameters;
334 |
335 | for( ;; )
336 | {
337 | /*使用新定义的函数输出字符串。*/
338 | prvNewPrintString( pcStringToPrint );
339 | /*等待一个伪随机时间。请注意rand()不一定是可重入的,但在这种情况下,它实际上并不重要,因为代码并不关心返回的值是什么。在更安全的应用程序中,应该使用已知可重入的rand()版本——或者应该在临界区段保护rand()的调用。*/
340 | vTaskDelay( ( rand() % xMaxBlockTimeTicks ) );
341 | }
342 | }
343 | ```
344 |
345 | 清单122. 示例20中`prvPrintTask()`的实现
346 |
347 | 正常情况下,`main()`只是创建互斥锁,创建任务,然后启动调度器。实现如清单123所示。
348 |
349 | `prvPrintTask()`的两个实例以不同的优先级创建,因此优先级较低的任务有时会被优先级较高的任务抢占。由于使用互斥锁来确保每个任务对终端的访问是互斥的,所以即使发生了抢占,被删除的字符串也将是正确的,并且不会损坏。通过减少任务处于阻塞状态的最大时间,可以增加抢占的频率,该时间是由`xMaxBlockTimeTicks`常量设置的。
350 |
351 | 使用FreeRTOS Windows端口的示例20的注意事项:
352 |
353 | - 调用`printf()`将生成一个Windows系统调用。Windows系统调用不在FreeRTOS的控制范围之内,并且会带来不稳定性。
354 | - Windows系统调用的执行方式意味着,即使没有使用互斥锁,也很少看到损坏的字符串。
355 |
356 | ```groovy
357 | int main( void )
358 | {
359 | /*在使用信号量之前,必须显式地创建信号量。在这个例子中,创建了一个互斥量类型的信号量。*/
360 | xMutex = xSemaphoreCreateMutex();
361 | /*在创建任务之前检查信号量是否创建成功。*/
362 | if( xMutex != NULL )
363 | {
364 | /*创建两个写入stdout的任务实例。他们写入的字符串作为任务的参数传递给任务。任务按不同的优先级创建,因此会发生一些抢占。*/
365 | xTaskCreate( prvPrintTask, "Print1", 1000,
366 | "Task 1 ***************************************\r\n", 1, NULL );
367 | xTaskCreate( prvPrintTask, "Print2", 1000,
368 | "Task 2 ---------------------------------------\r\n", 2, NULL );
369 |
370 | /*启动调度程序,使创建的任务开始执行。*/
371 | vTaskStartScheduler();
372 | }
373 |
374 | /*如果一切正常,那么main()将永远不会到达这里,因为调度程序现在将运行任务。如果main()确实到达了这里,那么很可能是没有足够的堆内存可用来创建空闲任务。第2章提供了堆内存管理的更多信息。*/
375 | for( ;; );
376 | }
377 | ```
378 |
379 | 清单123. 示例20的·main()·的实现
380 |
381 | 执行示例20时产生的输出如图64所示。图65描述了可能的执行顺序
382 |
383 | 
384 |
385 | 图64. 执行示例20时产生的输出
386 |
387 | 如图64所示,终端上显示的字符串没有损坏。随机排序是任务所使用的随机延迟周期的结果。
388 |
389 | 
390 |
391 | 图65. 例20中可能的执行序列
392 |
393 |
394 |
395 | ### 优先级反转
396 |
397 | 图65演示了使用互斥锁提供互斥的一个潜在缺陷。所描述的执行顺序显示了高优先级任务2必须等待低优先级任务1放弃对互斥锁的控制。高优先级的任务被低优先级的任务以这种方式延迟,我们称之为“优先级反转”。如果一个中等优先级的任务在高优先级任务等待信号量的同时开始执行,这种不受欢迎的行为将进一步扩大——结果将是一个高优先级任务在等待一个低优先级任务——而低优先级任务甚至无法执行。图66显示了这种最糟糕的情况。
398 |
399 | 
400 |
401 | 图66 最坏情况下的优先权反转情况
402 |
403 | 优先级反转可能是一个严重的问题,但在小型嵌入式系统中,通过考虑如何访问资源,通常可以在系统设计时避免这个问题。
404 |
405 |
406 |
407 | ### 优先级继承
408 |
409 | FreeRTOS互斥体和二进制信号量非常相似,不同之处是互斥体包含一个基本的优先级继承机制。而二进制信号量则不然。优先级继承是一种最小化优先级倒置负面影响的方案。它并不修复优先级倒置,只是通过确保倒置总是有时间限制来减少其影响。但是,优先级继承使系统定时分析变得复杂,依赖它来进行正确的系统操作并不是一个好的实践。
410 |
411 | 优先级继承的工作原理是,暂时将互斥锁持有者的优先级提高到试图获取同一个互斥锁的优先级最高的任务的优先级。持有互斥锁的低优先级任务继承了等待互斥锁的任务的优先级。图67演示了这一点。互斥锁持有者的优先级在返回互斥锁时自动重置为初始值。
412 |
413 | 
414 |
415 | 图67. 优先权继承使优先权反转的影响最小化
416 |
417 | 正如刚才看到的,优先级继承功能会影响使用互斥锁的任务的优先级。因此,互斥锁不能在中断服务程序中使用。
418 |
419 |
420 |
421 | ### 死锁(或致命拥抱)
422 |
423 | “死锁”是使用互斥对象进行互斥的另一个潜在陷阱。僵局有时也被称为“致命拥抱”。
424 |
425 | 当两个任务都在等待对方持有的资源而无法继续时,就会发生死锁。考虑以下场景,任务A和任务B都需要获取互斥锁X和互斥锁Y来执行一个操作:
426 |
427 | 1. 任务A执行并成功接受互斥锁X。
428 |
429 | 2. 任务A被任务B抢占。
430 |
431 | 3. 任务B在尝试获取互斥锁X之前成功获取了互斥锁Y,但是互斥锁X被任务A持有,所以对任务B不可用。任务B选择进入阻塞状态,等待互斥锁X被释放。
432 |
433 | 4. 任务A继续执行。它尝试取互斥量Y,但是互斥量Y被任务B持有,所以不能被任务A 使用。任务A选择进入阻塞状态来等待互斥量Y被释放。
434 |
435 | 在这个场景的最后,任务A正在等待任务B持有的互斥锁,任务B也在等待任务A持有的互斥锁。
436 |
437 | 与优先级倒置一样,避免死锁的最佳方法是在设计时考虑它的潜力,并设计系统以确保不会发生死锁。特别是,正如本书前面所述,任务无限期地等待(没有超时)来获得互斥锁通常是不好的做法。相反,使用比期望等待互斥锁的最大时间稍长一点的超时时间——那么在此时间内无法获得互斥锁将是设计错误的症状,可能是死锁。
438 |
439 | 在实践中,死锁在小型嵌入式系统中不是一个大问题,因为系统设计人员可以很好地理解整个应用程序,因此可以识别和消除可能发生死锁的区域。
440 |
441 |
442 |
443 | ### 递归互斥锁
444 |
445 | 任务本身也有可能死锁。如果任务试图执行,就会发生这种情况。多次使用同一个互斥锁,而不首先返回互斥锁。考虑以下场景:
446 |
447 | 1. 任务成功获取互斥锁。
448 |
449 | 2. 在持有互斥锁时,任务调用库函数
450 |
451 | 3. 库函数的实现尝试获取相同的互斥锁,并进入阻塞状态以等待互斥锁可用。
452 |
453 | 在本场景结束时,任务处于阻塞状态,等待返回互斥锁,但任务已经是互斥锁持有者了。出现死锁,因为任务处于阻止状态以等待其自身。
454 |
455 | 这种类型的死锁可以通过使用递归锁来代替标准互斥锁来避免。一个递归锁可以被同一个任务多次使用。并且只有在每次调用“取”递归互斥时执行一次“给”递归后才返回。
456 |
457 | 标准互斥体和递归互斥体的创建和使用方式类似:
458 |
459 | - 标准互斥对象是使用`xSemaphoreCreateMutex()`创建的。递归互斥是使用`xSemaphoreCreateRecursiveMutex()`创建的。这两个API函数具有相同的原型。
460 | - 标准互斥对象使用`xSemaphoreTake()`来“获取”。使用`xSemaphoreTakeRecursive()`“获取”递归互斥。这两个API函数具有相同的原型。
461 |
462 | - 标准互斥对象使用`xsemaphoregve()`来“给定”。递归互斥是使用`xSemaphoreTakeRecursive()`给出的。这两个API函数具有相同的原型。
463 |
464 | 清单124. 演示了如何创建和使用递归锁
465 |
466 | ```groovy
467 | /*递归互斥是SemaphoreHandle_t类型的变量。 */
468 | SemaphoreHandle_t xRecursiveMutex;
469 |
470 | /* 创建和使用递归互斥锁的任务的实现。 */
471 | void vTaskFunction( void *pvParameters )
472 | {
473 | const TickType_t xMaxBlock20ms = pdMS_TO_TICKS( 20 );
474 | /* 在使用递归互斥锁之前,必须显式地创建它。 */
475 | xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
476 |
477 | /*检查信号量是否创建成功。configASSERT()在11.2节中有描述。*/
478 | configASSERT( xRecursiveMutex );
479 |
480 | /*对于大多数任务,这个任务是作为一个无限循环来实现的。*/
481 | for( ;; )
482 | {
483 | /* ... */
484 | /*取递归互斥锁。 */
485 | if( xSemaphoreTakeRecursive( xRecursiveMutex, xMaxBlock20ms ) == pdPASS )
486 | {
487 | /*成功获取递归互斥锁。任务现在可以访问互斥锁保护的资源。此时,递归调用计数(即对xSemaphoreTakeRecursive()的嵌套调用的数量)为1,因为递归互斥对象只被取过一次。* /
488 |
489 | /*当它已经持有递归的互斥对象时,任务再次接受互斥对象。在实际的应用程序中这只可能发生在该任务调用的子函数中,因为没有实际的理由要多次使用同一个互斥锁。调用任务已经是互斥锁的持有者,所以对xSemaphorerakeRecursive()的第二次调用只是将递归调用的声音增加到2。*/
490 | xSemaphoreTakeRecursive( xRecursiveMutex, xMaxBlock20ms );
491 |
492 | /* ... */
493 | /*任务在访问了互斥锁保护的资源后返回互斥锁。此时,递归调用计数为2,因此对xSemaphoreGiveRecursive()的第一次调用不返回互斥锁。相反它只是将递归调用计数减回1。*/
494 | xSemaphoreGiveRecursive( xRecursiveMutex );
495 |
496 | /*下一次调用xSemaphoreGiveRecursive()时,返回的调用计数减少为0。所以这次返回的是递归互斥锁。*/
497 | xSemaphoreGiveRecursive( xRecursiveMutex );
498 |
499 | /*现在每次调用xSemaphoreGiveRecursive()都会执行一个xSemaphoreGiveRecursive(),所以任务不再是互斥锁的持有者。*/
500 | }
501 | }
502 | }
503 | ```
504 |
505 | 清单124. 创建和使用递归互斥锁
506 |
507 |
508 |
509 | ### 互斥体和任务调度
510 |
511 | 如果两个具有不同优先级的任务使用同一个互斥锁,那么FreeRTOS调度策略会明确任务执行的顺序;能够运行的最高优先级任务将被选择为进入运行状态的任务。例如,如果高优先级任务处于阻塞状态,等待低优先级任务持有的互斥锁,那么一旦低优先级任务返回互斥锁,高优先级任务就会抢占低优先级任务。高优先级的任务将成为互斥锁的持有者。在图67中已经看到了这个场景。
512 |
513 | 然而,当任务具有相同的优先级时,通常会对任务的执行顺序做出错误的假设。如果任务1和任务2有相同的优先级。任务1处于阻塞状态,等待由任务2持有的互斥,那么当任务2 "给出 "突变时,任务1不会抢占任务2。相反,任务2将保持在 运行状态,而任务1将简单地从阻塞状态移动到就绪状态。这种情况由图68显示了,其中垂直线标记了勾号中断发生的时间。
514 |
515 | 
516 |
517 | 图68 具有相同优先级的任务使用同一个互斥锁时可能的执行顺序
518 |
519 | 在图68所示的场景中,当互斥锁可用时,FreeRTOS调度器不会让任务一成为运行状态任务,因为:
520 |
521 | 1. 任务1和任务2具有相同的优先级,所以除非任务2进入阻塞状态,否则在下一次tick中断之前不会切换到任务1(假设`configUSE TIME SLICING`在`FreeRTOSConfig.h`中设置为1)。
522 | 2. 如果一个任务在紧循环中使用了一个互斥锁,并且每次任务给出互斥锁时都会发生上下文切换,那么这个任务只会在很短的时间内保持运行状态。如果两个或多个任务在一个紧循环中使用同一个互斥锁,那么在任务之间快速切换会浪费处理时间。
523 |
524 | 如果一个互斥锁在一个紧循环中被多个任务使用,并且使用互斥锁的任务具有相同的优先级,那么必须小心确保任务收到大约相等的处理时间。图69演示了任务可能无法获得相同数量的处理时间的原因。它显示了以相同优先级创建清单125所示任务的两个实例时可能发生的执行序列。
525 |
526 | ```groovy
527 | /*在紧循环中使用互斥锁的任务的实现。该任务在本地缓冲区中创建一个文本字符串,然后将该字符串写入显示。对显示器的访问受互斥锁的保护。*/
528 | void vATask( void *pvParameter )
529 | {
530 | extern SemaphoreHandle_t xMutex;
531 | char cTextBuffer[ 128 ];
532 | for( ;; )
533 | {
534 | /* 生成文本字符串-这是一个快速的操作。 */
535 | vGenerateTextInALocalBuffer( cTextBuffer );
536 |
537 | /*获取保护对显示的访问的互斥锁。*/
538 | xSemaphoreTake( xMutex, portMAX_DELAY );
539 |
540 | /* 将生成的文本写入显示器-这是一个缓慢的操作。*/
541 | vCopyTextToFrameBuffer( cTextBuffer );
542 |
543 | /* 文本已经写入显示器,因此返回互斥锁。*/
544 | xSemaphoreGive( xMutex );
545 | }
546 | }
547 | ```
548 |
549 | 清单125. 一个在紧密循环中使用互斥的任务
550 |
551 | 清单125中的注释注意到,创建字符串是一个快速的操作,而更新显示则是一个缓慢的操作。因此,当显示被更新时,互斥锁被持有,任务将在大部分运行时间内持有互斥锁。
552 |
553 | 在图69中,垂直线标记了记号中断发生的时间。
554 |
555 | 
556 |
557 | 图69 任务的两个实例可能发生的执行序列如清单125所示,以相同的优先级创建
558 |
559 | 图69中的第7步显示任务1重新进入阻塞状态,这发生在 `xSemaphoreTake()` API函数中发生。
560 |
561 | 图69表明,任务1将被阻止获得互斥锁,直到时间片的开始与任务2不是互斥锁持有者的短时间的重合。
562 |
563 | 图69中所示的情况可以通过在调用`taskYIELD()`后添加一个调用来避免。调用`xSemaphoreGive()`后,可以避免这种情况。这在清单126中得到了证明,如果在任务持有互斥锁时`taskYIELD()`被调用,那么它就会被调用。如果在任务持有互斥锁的时候,标记计数发生了变化,就会调用`taskYIELD()`。
564 |
565 | ```groovy
566 | void vFunction( void *pvParameter )
567 | {
568 | extern SemaphoreHandle_t xMutex;
569 | char cTextBuffer[ 128 ];
570 | TickType_t xTimeAtWhichMutexWasTaken;
571 |
572 | for( ;; )
573 | {
574 | /* 生成文本字符串-这是一个快速的操作。 */
575 | vGenerateTextInALocalBuffer( cTextBuffer );
576 |
577 | /*获取对显示器进行protectinq访问的互斥锁。 */
578 | xSemaphoreTake( xMutex, portMAX_DELAY );
579 |
580 | /* 记录使用互斥锁的时间。 */
581 | xTimeAtWhichMutexWasTaken = xTaskGetTickCount();
582 |
583 | /* 将生成的文本写入显示器——这是一个缓慢的操作。 */
584 | vCopyTextToFrameBuffer( cTextBuffer );
585 |
586 | /* 文本已经写入显示器,因此返回互斥锁。*/
587 | xSemaphoreGive( xMutex );
588 |
589 | /*如果每次迭代都调用taskYIELD(),那么该任务只会在很短的一段时间内保持运行状态,而在任务之间快速切换会浪费处理时间。因此,只有当在互斥锁被持有时时钟计数改变时才调用taskYIELD()。* /
590 | if( xTaskGetTickCount() != xTimeAtWhichMutexWasTaken )
591 | {
592 | taskYIELD();
593 | }
594 | }
595 | }
596 | ```
597 |
598 | 清单126. 确保在循环中使用互斥锁的任务可以获得更多的处理时间,同时也确保不会因为在任务之间切换太快而浪费处理时间。
599 |
600 |
601 |
602 | ## 看门人任务
603 |
604 | 看门人任务提供了一种干净的实现互斥的方法,并且没有优先级倒置或死锁的风险。
605 |
606 | 看门人任务是对资源拥有唯一所有权的任务。只有看门人任务被允许直接访问资源,其他需要访问资源的任务只能通过使用看门人的服务间接访问资源。
607 |
608 |
609 |
610 | ### 示例21. 重写vPrintString()以使用看门人任务
611 |
612 | 示例21提供了`vPrintString()`的另一种替代实现。这一次,使用一个看门人任务来管理对标准输出的访问。当任务想要将消息写入标准输出时,它不会直接调用print函数,而是将消息发送给看门人。
613 |
614 | 看门人任务使用FreeRTOS队列将访问序列化到标准输出。任务的内部实现不必考虑互斥,因为它是唯一允许直接访问标准输出的任务。
615 |
616 | 看门人任务大部分时间处于阻塞状态,等待消息到达队列。当消息到达时,看门人只需将消息写入标准输出,然后返回阻塞状态以等待下一条消息。看门人任务的实现如清单128所示。
617 |
618 | 中断可以发送到队列,因此中断服务程序也可以安全地使用看门人的服务向终端写入消息。在本例中,使用了一个钩子函数,每200次写一条消息。
619 |
620 | 滴答钩子(或滴答回调)是内核在每次滴答中断期间调用的函数。使用滴答钩子函数:
621 |
622 | 1. 在`FreeRTOSConfig.h`中设置`configUSE_TICK_HOOK`为1。
623 | 2. 提供钩子函数的实现,使用清单127所示的准确的函数名称和原型如清单127所示。
624 |
625 | ```groovy
626 | void vApplicationTickHook( void );
627 | ```
628 |
629 | 清单127. 钩子函数的名称和原型
630 |
631 | 钩子函数在勾选中断的上下文中执行,因此必须保持非常短的时间。所以必须保持非常短,必须只使用适量的堆栈空间,并且不能调用任何不以“`FromISR()`”结尾的FreeRTOS API 函数,而不是以 “`FromISR() `”结尾。
632 |
633 | 调度器将总是在滴答钩子函数之后立即执行,所以中断安全。从滴答钩子调用的FreeRTOS API函数不需要使用其 `pxHigherPriorityTaskWoken`参数,并且该参数可以被设置为`NULL`。
634 |
635 | ```groovy
636 | static void prvStdioGatekeeperTask( void *pvParameters )
637 | {
638 | char *pcMessageToPrint;
639 |
640 | /*这是唯一允许写入标准输出的任务。任何其他想要将字符串写入输出的任务都不会直接访问标准输出,而是将字符串发送给该任务。因为只有该任务访问标准输出,所以在任务本身的实现中不需要考虑互斥或序列化问题。*/
641 | for( ;; )
642 | {
643 | /*等待消息到达。指定了一个不确定的块时间,因此不需要检查返回值-该函数只在成功接收到消息时返回。*/
644 | xQueueReceive( xPrintQueue, &pcMessageToPrint, portMAX_DELAY );
645 |
646 | /* Output the received string. */
647 | printf( "%s", pcMessageToPrint );
648 | fflush( stdout );
649 |
650 | /*返回循环以等待下一条消息。*/
651 | }
652 | }
653 | ```
654 |
655 | 清单128. 看门人任务
656 |
657 | 写入队列的任务如清单129所示。与前面一样,将创建任务的两个独立实例,并使用任务参数将任务写入队列的字符串传递给任务。
658 |
659 | ```groovy
660 | static void prvPrintTask( void *pvParameters )
661 | {
662 | int iIndexToString;
663 | const TickType_t xMaxBlockTimeTicks = 0x20;
664 |
665 | /*创建该任务的两个实例。任务参数用于将字符串数组的索引传递给任务。将其转换为所需的类型。*/
666 | iIndexToString = ( int ) pvParameters;
667 |
668 | for( ;; )
669 | {
670 | /*输出字符串,不是直接输出,而是通过队列将字符串的指针传递给看门人任务。队列是在调度程序启动之前创建的,因此在该任务第一次执行时已经存在。没有指定块时间,因为队列中总是有空间。*/
671 | xQueueSendToBack( xPrintQueue, &( pcStringsToPrint[ iIndexToString ] ), 0 );
672 |
673 | /*等待一个伪随机时间。请注意rand()不一定是可重入的,但在这种情况下,它实际上并不重要,因为代码并不关心返回的值是什么。在更安全的应用程序中,应该使用已知可重入的rand()版本——或者应该使用临界区来保护对rand()的调用。*/
674 | vTaskDelay( ( rand() % xMaxBlockTimeTicks ) );
675 | }
676 | }
677 | ```
678 |
679 | 清单129. 示例21的打印任务实现
680 |
681 | 滴答钩子函数对自己被调用的次数进行计数,每次计数达到200时,就向看门人任务发送消息。仅出于演示目的,滴答钩子写到队列的前面,而任务写到队列的后面。滴答钩子实现如清单130所示。
682 |
683 | ```groovy
684 | void vApplicationTickHook( void )
685 | {
686 | static int iCount = 0;
687 |
688 | /*每200个节拍打印一条消息。消息不是直接写出来的,而是发送给看门人任务。*/
689 | iCount++;
690 | if( iCount >= 200 )
691 | {
692 | /*由于xQueueSendToFrontFromISR()是从标记钩子调用的,所以不需要使用xHigherPriorityTaskwoken参数(第三个参数),该参数设置为NULL。*/
693 | xQueueSendToFrontFromISR( xPrintQueue,
694 | &( pcStringsToPrint[ 2 ] ),
695 | NULL );
696 |
697 | /* 重置计数,准备在200滴答时间内再次打印字符串。 */
698 | iCount = 0;
699 | }
700 | }
701 | ```
702 |
703 | 清单130. 滴答钩子函数的实现
704 |
705 | 正常情况下,`main()`创建运行示例所需的队列和任务,然后启动调度程序。`main()`的实现如清单131所示。
706 |
707 | ```groovy
708 | /*定义任务和中断将通过看门人打印出来的字符串。 */
709 | static char *pcStringsToPrint[] =
710 | {
711 | "Task 1 ****************************************************\r\n",
712 | "Task 2 ----------------------------------------------------\r\n",
713 | "Message printed from the tick hook interrupt ##############\r\n"
714 | };
715 |
716 | /*-----------------------------------------------------------*/
717 | /*声明一个类型为QueueHandle_t的变量。该队列用于发送消息,从打印任务和标记中断到看门人任务。*/
718 | QueueHandle_t xPrintQueue;
719 | /*-----------------------------------------------------------*/
720 |
721 | int main( void )
722 | {
723 | /*在使用队列之前,必须显式创建队列。队列被创建为最多容纳5个字符的指针。*/
724 | xPrintQueue = xQueueCreate( 5, sizeof( char * ) );
725 | /* 检查队列是否创建成功。 */
726 | if( xPrintQueue != NULL )
727 | {
728 | /*创建任务的两个实例,发送消息给守门人。任务使用的字符串的索引通过任务参数传递给任务(xTaskcreate()的第4个参数)。这些任务按不同的优先级创建,因此高优先级任务偶尔会抢占低优先级任务。*/
729 | xTaskCreate( prvPrintTask, "Print1", 1000, ( void * ) 0, 1, NULL );
730 | xTaskCreate( prvPrintTask, "Print2", 1000, ( void * ) 1, 2, NULL );
731 |
732 | /*创建看门人任务。这是唯一允许直接访问标准输出的任务。*/
733 | xTaskCreate( prvStdioGatekeeperTask, "Gatekeeper", 1000, NULL, 0, NULL );
734 |
735 | /* 启动调度程序,使创建的任务开始执行。 */
736 | vTaskStartScheduler();
737 | }
738 |
739 | /*如果一切正常,那么main()将永远不会到达这里,因为调度程序现在将运行任务。如果main()确实到达了这里,那么很可能是没有足够的堆内存可用来创建空闲任务。第2章提供了堆内存管理的更多信息。*/
740 | for( ;; );
741 | }
742 | ```
743 |
744 | 清单131. 示例21的`main()`的实现
745 |
746 | 执行示例21时产生的输出如图70所示。可以看到,来自任务的字符串和来自中断的字符串都正确输出,没有损坏。
747 |
748 | 
749 |
750 | 图70 执行例21时产生的输出
751 |
752 | 看门人任务的优先级比打印任务低,因此发送给看门人的消息会一直留在队列中,直到两个打印任务都处于阻塞状态。在某些情况下,为看门人分配更高的优先级是合适的,这样消息就可以立即得到处理——但是这样做的代价是,看门人会延迟较低优先级的任务,直到完成对受保护资源的访问。
--------------------------------------------------------------------------------