ಈ ಲೇಖನವು ಯುನಿಕ್ಸ್ ಕರ್ನಲ್ನಲ್ಲಿ ಪೈಪ್ಲೈನ್ಗಳ ಅನುಷ್ಠಾನವನ್ನು ವಿವರಿಸುತ್ತದೆ. " ಎಂಬ ಶೀರ್ಷಿಕೆಯ ಇತ್ತೀಚಿನ ಲೇಖನದಿಂದ ನಾನು ಸ್ವಲ್ಪ ನಿರಾಶೆಗೊಂಡಿದ್ದೇನೆ.
ನಾವು ಏನು ಮಾತನಾಡುತ್ತಿದ್ದೇವೆ?
ಪೈಪ್ಲೈನ್ಗಳು, "ಬಹುಶಃ ಯುನಿಕ್ಸ್ನಲ್ಲಿನ ಅತ್ಯಂತ ಪ್ರಮುಖ ಆವಿಷ್ಕಾರವಾಗಿದೆ," ಸಣ್ಣ ಪ್ರೋಗ್ರಾಂಗಳನ್ನು ಒಟ್ಟಿಗೆ ಜೋಡಿಸುವ ಆಧಾರವಾಗಿರುವ ಯುನಿಕ್ಸ್ ತತ್ವಶಾಸ್ತ್ರದ ವಿಶಿಷ್ಟ ಲಕ್ಷಣವಾಗಿದೆ, ಜೊತೆಗೆ ಆಜ್ಞಾ ಸಾಲಿನಲ್ಲಿ ಪರಿಚಿತ ಚಿಹ್ನೆ:
$ echo hello | wc -c
6
ಈ ಕಾರ್ಯವು ಕರ್ನಲ್ ಒದಗಿಸಿದ ಸಿಸ್ಟಮ್ ಕರೆಯನ್ನು ಅವಲಂಬಿಸಿರುತ್ತದೆ pipe
, ಇದು ದಸ್ತಾವೇಜನ್ನು ಪುಟಗಳಲ್ಲಿ ವಿವರಿಸಲಾಗಿದೆ
ಇಂಟರ್ಪ್ರೊಸೆಸ್ ಸಂವಹನಕ್ಕಾಗಿ ಪೈಪ್ಲೈನ್ಗಳು ಏಕಮುಖ ಚಾನಲ್ ಅನ್ನು ಒದಗಿಸುತ್ತವೆ. ಪೈಪ್ಲೈನ್ ಇನ್ಪುಟ್ (ಬರೆಯುವ ಅಂತ್ಯ) ಮತ್ತು ಔಟ್ಪುಟ್ (ಓದಲು ಅಂತ್ಯ) ಹೊಂದಿದೆ. ಪೈಪ್ಲೈನ್ನ ಇನ್ಪುಟ್ಗೆ ಬರೆಯಲಾದ ಡೇಟಾವನ್ನು ಔಟ್ಪುಟ್ನಲ್ಲಿ ಓದಬಹುದು.
ಕರೆಯನ್ನು ಬಳಸಿಕೊಂಡು ಪೈಪ್ಲೈನ್ ಅನ್ನು ರಚಿಸಲಾಗಿದೆ
pipe(2)
, ಇದು ಎರಡು ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ಗಳನ್ನು ಹಿಂದಿರುಗಿಸುತ್ತದೆ: ಒಂದು ಪೈಪ್ಲೈನ್ನ ಇನ್ಪುಟ್ ಅನ್ನು ಉಲ್ಲೇಖಿಸುತ್ತದೆ, ಎರಡನೆಯದು ಔಟ್ಪುಟ್ಗೆ.
ಮೇಲಿನ ಆಜ್ಞೆಯಿಂದ ಟ್ರೇಸ್ ಔಟ್ಪುಟ್ ಪೈಪ್ಲೈನ್ನ ರಚನೆ ಮತ್ತು ಅದರ ಮೂಲಕ ಒಂದು ಪ್ರಕ್ರಿಯೆಯಿಂದ ಇನ್ನೊಂದಕ್ಕೆ ಡೇಟಾದ ಹರಿವನ್ನು ತೋರಿಸುತ್ತದೆ:
$ strace -qf -e execve,pipe,dup2,read,write
sh -c 'echo hello | wc -c'
execve("/bin/sh", ["sh", "-c", "echo hello | wc -c"], …)
pipe([3, 4]) = 0
[pid 2604795] dup2(4, 1) = 1
[pid 2604795] write(1, "hellon", 6) = 6
[pid 2604796] dup2(3, 0) = 0
[pid 2604796] execve("/usr/bin/wc", ["wc", "-c"], …)
[pid 2604796] read(0, "hellon", 16384) = 6
[pid 2604796] write(1, "6n", 2) = 2
ಪೋಷಕ ಪ್ರಕ್ರಿಯೆಯು ಕರೆ ಮಾಡುತ್ತದೆ pipe()
ಮೌಂಟೆಡ್ ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ಗಳನ್ನು ಪಡೆಯಲು. ಒಂದು ಮಗುವಿನ ಪ್ರಕ್ರಿಯೆಯು ಒಂದು ಹ್ಯಾಂಡಲ್ಗೆ ಬರೆಯುತ್ತದೆ ಮತ್ತು ಇನ್ನೊಂದು ಪ್ರಕ್ರಿಯೆಯು ಮತ್ತೊಂದು ಹ್ಯಾಂಡಲ್ನಿಂದ ಅದೇ ಡೇಟಾವನ್ನು ಓದುತ್ತದೆ. stdin ಮತ್ತು stdout ಅನ್ನು ಹೊಂದಿಸಲು 2 ಮತ್ತು 3 ವಿವರಣೆಗಳನ್ನು "ಮರುಹೆಸರಿಸಲು" ಶೆಲ್ dup4 ಅನ್ನು ಬಳಸುತ್ತದೆ.
ಪೈಪ್ಗಳಿಲ್ಲದೆಯೇ, ಶೆಲ್ ಒಂದು ಪ್ರಕ್ರಿಯೆಯ ಔಟ್ಪುಟ್ ಅನ್ನು ಫೈಲ್ಗೆ ಬರೆಯಬೇಕು ಮತ್ತು ಫೈಲ್ನಿಂದ ಡೇಟಾವನ್ನು ಓದಲು ಅದನ್ನು ಇನ್ನೊಂದು ಪ್ರಕ್ರಿಯೆಗೆ ರವಾನಿಸಬೇಕು. ಪರಿಣಾಮವಾಗಿ, ನಾವು ಹೆಚ್ಚಿನ ಸಂಪನ್ಮೂಲಗಳನ್ನು ಮತ್ತು ಡಿಸ್ಕ್ ಜಾಗವನ್ನು ವ್ಯರ್ಥ ಮಾಡುತ್ತೇವೆ. ಆದಾಗ್ಯೂ, ಪೈಪ್ಲೈನ್ಗಳು ಒಳ್ಳೆಯದು ಏಕೆಂದರೆ ಅವು ತಾತ್ಕಾಲಿಕ ಫೈಲ್ಗಳ ಬಳಕೆಯನ್ನು ತಪ್ಪಿಸಲು ನಿಮಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತವೆ:
ಒಂದು ಪ್ರಕ್ರಿಯೆಯು ಖಾಲಿ ಪೈಪ್ಲೈನ್ನಿಂದ ಓದಲು ಪ್ರಯತ್ನಿಸುತ್ತಿದ್ದರೆ
read(2)
ಡೇಟಾ ಲಭ್ಯವಾಗುವವರೆಗೆ ನಿರ್ಬಂಧಿಸುತ್ತದೆ. ಒಂದು ಪ್ರಕ್ರಿಯೆಯು ಪೂರ್ಣ ಪೈಪ್ಲೈನ್ಗೆ ಬರೆಯಲು ಪ್ರಯತ್ನಿಸಿದರೆ, ನಂತರwrite(2)
ಬರಹವನ್ನು ನಿರ್ವಹಿಸಲು ಪೈಪ್ಲೈನ್ನಿಂದ ಸಾಕಷ್ಟು ಡೇಟಾವನ್ನು ಓದುವವರೆಗೆ ನಿರ್ಬಂಧಿಸುತ್ತದೆ.
POSIX ಅವಶ್ಯಕತೆಯಂತೆ, ಇದು ಒಂದು ಪ್ರಮುಖ ಆಸ್ತಿಯಾಗಿದೆ: ಪೈಪ್ಲೈನ್ಗೆ ಬರೆಯುವುದು PIPE_BUF
ಬೈಟ್ಗಳು (ಕನಿಷ್ಠ 512) ಪರಮಾಣುವಾಗಿರಬೇಕು ಆದ್ದರಿಂದ ಪ್ರಕ್ರಿಯೆಗಳು ಪೈಪ್ಲೈನ್ ಮೂಲಕ ಪರಸ್ಪರ ಸಂವಹನ ನಡೆಸಬಹುದು, ಸಾಮಾನ್ಯ ಫೈಲ್ಗಳು (ಅಂತಹ ಖಾತರಿಗಳನ್ನು ಒದಗಿಸುವುದಿಲ್ಲ).
ನಿಯಮಿತ ಫೈಲ್ ಅನ್ನು ಬಳಸುವಾಗ, ಪ್ರಕ್ರಿಯೆಯು ಅದರ ಎಲ್ಲಾ ಔಟ್ಪುಟ್ ಅನ್ನು ಅದಕ್ಕೆ ಬರೆಯಬಹುದು ಮತ್ತು ಅದನ್ನು ಇನ್ನೊಂದು ಪ್ರಕ್ರಿಯೆಗೆ ರವಾನಿಸಬಹುದು. ಅಥವಾ ಪ್ರಕ್ರಿಯೆಗಳು ಹೆಚ್ಚು ಸಮಾನಾಂತರ ಮೋಡ್ನಲ್ಲಿ ಕಾರ್ಯನಿರ್ವಹಿಸಬಹುದು, ಬಾಹ್ಯ ಸಿಗ್ನಲಿಂಗ್ ಕಾರ್ಯವಿಧಾನವನ್ನು (ಸೆಮಾಫೋರ್ನಂತೆ) ಬಳಸಿಕೊಂಡು ಬರೆಯುವುದು ಅಥವಾ ಓದುವುದು ಪೂರ್ಣಗೊಂಡಾಗ ಪರಸ್ಪರ ತಿಳಿಸುತ್ತದೆ. ಕನ್ವೇಯರ್ಗಳು ಈ ಎಲ್ಲಾ ತೊಂದರೆಗಳಿಂದ ನಮ್ಮನ್ನು ರಕ್ಷಿಸುತ್ತವೆ.
ನಾವು ಏನು ಹುಡುಕುತ್ತಿದ್ದೇವೆ?
ನಾನು ಅದನ್ನು ಸರಳ ಪದಗಳಲ್ಲಿ ವಿವರಿಸುತ್ತೇನೆ ಇದರಿಂದ ಕನ್ವೇಯರ್ ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಊಹಿಸಲು ನಿಮಗೆ ಸುಲಭವಾಗುತ್ತದೆ. ನೀವು ಬಫರ್ ಮತ್ತು ಮೆಮೊರಿಯಲ್ಲಿ ಕೆಲವು ಸ್ಥಿತಿಯನ್ನು ನಿಯೋಜಿಸಬೇಕಾಗುತ್ತದೆ. ಬಫರ್ನಿಂದ ಡೇಟಾವನ್ನು ಸೇರಿಸಲು ಮತ್ತು ತೆಗೆದುಹಾಕಲು ನಿಮಗೆ ಕಾರ್ಯಗಳು ಬೇಕಾಗುತ್ತವೆ. ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ಗಳಲ್ಲಿ ಓದುವ ಮತ್ತು ಬರೆಯುವ ಕಾರ್ಯಾಚರಣೆಗಳ ಸಮಯದಲ್ಲಿ ಕಾರ್ಯಗಳನ್ನು ಕರೆಯಲು ನಿಮಗೆ ಕೆಲವು ವಿಧಾನಗಳ ಅಗತ್ಯವಿದೆ. ಮತ್ತು ಮೇಲೆ ವಿವರಿಸಿದ ವಿಶೇಷ ನಡವಳಿಕೆಯನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲು ನಿಮಗೆ ಬೀಗಗಳ ಅಗತ್ಯವಿದೆ.
ಈಗ ನಾವು ನಮ್ಮ ಅಸ್ಪಷ್ಟ ಮಾನಸಿಕ ಮಾದರಿಯನ್ನು ದೃಢೀಕರಿಸಲು ಅಥವಾ ನಿರಾಕರಿಸಲು ಪ್ರಕಾಶಮಾನವಾದ ದೀಪದ ಬೆಳಕಿನಲ್ಲಿ ಕರ್ನಲ್ ಮೂಲ ಕೋಡ್ ಅನ್ನು ವಿಚಾರಣೆ ಮಾಡಲು ಸಿದ್ಧರಿದ್ದೇವೆ. ಆದರೆ ಯಾವಾಗಲೂ ಅನಿರೀಕ್ಷಿತವಾಗಿ ಸಿದ್ಧರಾಗಿರಿ.
ನಾವು ಎಲ್ಲಿ ನೋಡುತ್ತಿದ್ದೇವೆ?
ನನ್ನ ಪ್ರಸಿದ್ಧ ಪುಸ್ತಕದ ಪ್ರತಿ ಎಲ್ಲಿದೆ ಎಂದು ನನಗೆ ಗೊತ್ತಿಲ್ಲ "
TUHS ಆರ್ಕೈವ್ಗಳ ಮೂಲಕ ಅಲೆದಾಡುವುದು ಮ್ಯೂಸಿಯಂಗೆ ಭೇಟಿ ನೀಡಿದಂತಿದೆ. ನಾವು ನಮ್ಮ ಹಂಚಿದ ಇತಿಹಾಸವನ್ನು ನೋಡಬಹುದು ಮತ್ತು ಹಳೆಯ ಟೇಪ್ಗಳು ಮತ್ತು ಪ್ರಿಂಟ್ಗಳಿಂದ ಈ ಎಲ್ಲಾ ವಸ್ತುಗಳನ್ನು ಸ್ವಲ್ಪಮಟ್ಟಿಗೆ ಮರುಪಡೆಯಲು ಹಲವು ವರ್ಷಗಳ ಪ್ರಯತ್ನಕ್ಕೆ ನನಗೆ ಗೌರವವಿದೆ. ಮತ್ತು ಇನ್ನೂ ಕಾಣೆಯಾಗಿರುವ ಆ ತುಣುಕುಗಳ ಬಗ್ಗೆ ನನಗೆ ತೀವ್ರ ಅರಿವಿದೆ.
ಕನ್ವೇಯರ್ಗಳ ಪ್ರಾಚೀನ ಇತಿಹಾಸದ ಬಗ್ಗೆ ನಮ್ಮ ಕುತೂಹಲವನ್ನು ತೃಪ್ತಿಪಡಿಸಿದ ನಂತರ, ಹೋಲಿಕೆಗಾಗಿ ನಾವು ಆಧುನಿಕ ಕರ್ನಲ್ಗಳನ್ನು ನೋಡಬಹುದು.
ಮೂಲಕ, pipe
ಟೇಬಲ್ನಲ್ಲಿ ಸಿಸ್ಟಮ್ ಕರೆ ಸಂಖ್ಯೆ 42 ಆಗಿದೆ sysent[]
. ಕಾಕತಾಳೀಯ?
ಸಾಂಪ್ರದಾಯಿಕ ಯುನಿಕ್ಸ್ ಕರ್ನಲ್ಗಳು (1970–1974)
ನನಗೆ ಯಾವುದೇ ಕುರುಹುಗಳು ಸಿಗಲಿಲ್ಲ pipe(2)
ಎರಡೂ ಅಲ್ಲ
TUHS ಹೇಳುತ್ತದೆ
Unix 1973 ನೇ ಆವೃತ್ತಿಯು ಅಸೆಂಬ್ಲಿ ಭಾಷೆಯಲ್ಲಿ ಬರೆದ ಕರ್ನಲ್ನೊಂದಿಗೆ ಕೊನೆಯ ಆವೃತ್ತಿಯಾಗಿದೆ, ಆದರೆ ಪೈಪ್ಲೈನ್ಗಳೊಂದಿಗೆ ಮೊದಲ ಆವೃತ್ತಿಯಾಗಿದೆ. XNUMX ರ ಸಮಯದಲ್ಲಿ, ಮೂರನೇ ಆವೃತ್ತಿಯನ್ನು ಸುಧಾರಿಸಲು ಕೆಲಸವನ್ನು ಕೈಗೊಳ್ಳಲಾಯಿತು, ಕರ್ನಲ್ ಅನ್ನು C ನಲ್ಲಿ ಪುನಃ ಬರೆಯಲಾಯಿತು ಮತ್ತು ಆದ್ದರಿಂದ Unix ನ ನಾಲ್ಕನೇ ಆವೃತ್ತಿಯು ಕಾಣಿಸಿಕೊಂಡಿತು.
ಒಬ್ಬ ಓದುಗರು ಡಾಕ್ಯುಮೆಂಟ್ನ ಸ್ಕ್ಯಾನ್ ಅನ್ನು ಕಂಡುಕೊಂಡರು, ಅದರಲ್ಲಿ ಡೌಗ್ ಮ್ಯಾಕ್ಲ್ರಾಯ್ "ಗಾರ್ಡನ್ ಮೆದುಗೊಳವೆನಂತಹ ಕಾರ್ಯಕ್ರಮಗಳನ್ನು ಸಂಪರ್ಕಿಸುವ" ಕಲ್ಪನೆಯನ್ನು ಪ್ರಸ್ತಾಪಿಸಿದರು.
ಬ್ರಿಯಾನ್ ಕೆರ್ನಿಘನ್ ಅವರ ಪುಸ್ತಕದಲ್ಲಿ
Unix ಹೊರಬಂದಾಗ, ಕೊರೊಟೀನ್ಗಳೊಂದಿಗಿನ ನನ್ನ ಮೋಹವು OS ನ ಲೇಖಕ ಕೆನ್ ಥಾಂಪ್ಸನ್ ಅವರನ್ನು ಕೇಳಲು ಕಾರಣವಾಯಿತು, ಪ್ರಕ್ರಿಯೆಗೆ ಬರೆದ ಡೇಟಾವನ್ನು ಸಾಧನಕ್ಕೆ ಮಾತ್ರವಲ್ಲದೆ ಮತ್ತೊಂದು ಪ್ರಕ್ರಿಯೆಗೆ ಔಟ್ಪುಟ್ ಮಾಡಲು ಅನುಮತಿಸುವಂತೆ. ಕೆನ್ ಇದು ಸಾಧ್ಯ ಎಂದು ನಿರ್ಧರಿಸಿದರು. ಆದಾಗ್ಯೂ, ಕನಿಷ್ಠೀಯತಾವಾದಿಯಾಗಿ, ಪ್ರತಿಯೊಂದು ಸಿಸ್ಟಮ್ ಕಾರ್ಯವು ಮಹತ್ವದ ಪಾತ್ರವನ್ನು ವಹಿಸಬೇಕೆಂದು ಅವರು ಬಯಸಿದ್ದರು. ಮಧ್ಯಂತರ ಫೈಲ್ಗೆ ಬರೆಯುವುದಕ್ಕಿಂತ ಪ್ರಕ್ರಿಯೆಗಳ ನಡುವೆ ನೇರವಾಗಿ ಬರೆಯುವುದು ನಿಜವಾಗಿಯೂ ದೊಡ್ಡ ಪ್ರಯೋಜನವೇ? "ಪೈಪ್ಲೈನ್" ಎಂಬ ಆಕರ್ಷಕ ಹೆಸರು ಮತ್ತು ಪ್ರಕ್ರಿಯೆಗಳ ನಡುವಿನ ಪರಸ್ಪರ ಕ್ರಿಯೆಯ ಸಿಂಟ್ಯಾಕ್ಸ್ನ ವಿವರಣೆಯೊಂದಿಗೆ ನಾನು ನಿರ್ದಿಷ್ಟ ಪ್ರಸ್ತಾಪವನ್ನು ಮಾಡಿದಾಗ ಮಾತ್ರ ಕೆನ್ ಅಂತಿಮವಾಗಿ ಉದ್ಗರಿಸಿದನು: "ನಾನು ಅದನ್ನು ಮಾಡುತ್ತೇನೆ!"
ಮತ್ತು ಮಾಡಿದರು. ಒಂದು ಅದೃಷ್ಟದ ಸಂಜೆ, ಕೆನ್ ಕರ್ನಲ್ ಮತ್ತು ಶೆಲ್ ಅನ್ನು ಬದಲಾಯಿಸಿದರು, ಅವರು ಇನ್ಪುಟ್ ಅನ್ನು ಹೇಗೆ ಸ್ವೀಕರಿಸುತ್ತಾರೆ ಎಂಬುದನ್ನು ಪ್ರಮಾಣೀಕರಿಸಲು ಹಲವಾರು ಪ್ರಮಾಣಿತ ಕಾರ್ಯಕ್ರಮಗಳನ್ನು ಸರಿಪಡಿಸಿದರು (ಇದು ಪೈಪ್ಲೈನ್ನಿಂದ ಬರಬಹುದು), ಮತ್ತು ಫೈಲ್ ಹೆಸರುಗಳನ್ನು ಸಹ ಬದಲಾಯಿಸಿದರು. ಮರುದಿನ, ಪೈಪ್ಲೈನ್ಗಳನ್ನು ಅಪ್ಲಿಕೇಶನ್ಗಳಲ್ಲಿ ವ್ಯಾಪಕವಾಗಿ ಬಳಸಲಾರಂಭಿಸಿತು. ವಾರದ ಅಂತ್ಯದ ವೇಳೆಗೆ, ವರ್ಡ್ ಪ್ರೊಸೆಸರ್ಗಳಿಂದ ಪ್ರಿಂಟರ್ಗೆ ದಾಖಲೆಗಳನ್ನು ಕಳುಹಿಸಲು ಕಾರ್ಯದರ್ಶಿಗಳು ಅವುಗಳನ್ನು ಬಳಸುತ್ತಿದ್ದರು. ಸ್ವಲ್ಪ ಸಮಯದ ನಂತರ, ಕೆನ್ ಮೂಲ API ಮತ್ತು ಸಿಂಟ್ಯಾಕ್ಸ್ ಅನ್ನು ಪೈಪ್ಲೈನ್ಗಳ ಬಳಕೆಯನ್ನು ಕ್ಲೀನರ್ ಕನ್ವೆನ್ಶನ್ಗಳೊಂದಿಗೆ ಸುತ್ತಲು ಬದಲಾಯಿಸಿದರು, ಇದನ್ನು ಅಂದಿನಿಂದಲೂ ಬಳಸಲಾಗುತ್ತಿದೆ.
ದುರದೃಷ್ಟವಶಾತ್, ಮೂರನೇ ಆವೃತ್ತಿಯ ಯುನಿಕ್ಸ್ ಕರ್ನಲ್ನ ಮೂಲ ಕೋಡ್ ಕಳೆದುಹೋಗಿದೆ. ಮತ್ತು ನಾವು C ಯಲ್ಲಿ ಬರೆದ ಕರ್ನಲ್ ಮೂಲ ಕೋಡ್ ಅನ್ನು ಹೊಂದಿದ್ದರೂ ಸಹ
ನಾವು ಪಠ್ಯ ದಸ್ತಾವೇಜನ್ನು ಹೊಂದಿದ್ದೇವೆ pipe(2)
ಎರಡೂ ಬಿಡುಗಡೆಗಳಿಂದ, ಆದ್ದರಿಂದ ನೀವು ದಸ್ತಾವೇಜನ್ನು ಹುಡುಕುವ ಮೂಲಕ ಪ್ರಾರಂಭಿಸಬಹುದು pipe(2)
ಅಸೆಂಬ್ಲಿ ಭಾಷೆಯಲ್ಲಿ ಬರೆಯಲಾಗಿದೆ ಮತ್ತು ಕೇವಲ ಒಂದು ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ, ಆದರೆ ಈಗಾಗಲೇ ನಿರೀಕ್ಷಿತ ಕೋರ್ ಕಾರ್ಯವನ್ನು ಒದಗಿಸುತ್ತದೆ:
ಸಿಸ್ಟಮ್ ಕರೆ ಪೈಪ್ ಪೈಪ್ಲೈನ್ ಎಂಬ ಇನ್ಪುಟ್/ಔಟ್ಪುಟ್ ಕಾರ್ಯವಿಧಾನವನ್ನು ರಚಿಸುತ್ತದೆ. ಹಿಂತಿರುಗಿದ ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ ಅನ್ನು ಓದಲು ಮತ್ತು ಬರೆಯಲು ಕಾರ್ಯಾಚರಣೆಗಳಿಗೆ ಬಳಸಬಹುದು. ಪೈಪ್ಲೈನ್ಗೆ ಏನನ್ನಾದರೂ ಬರೆಯುವಾಗ, 504 ಬೈಟ್ಗಳವರೆಗೆ ಡೇಟಾವನ್ನು ಬಫರ್ ಮಾಡಲಾಗುತ್ತದೆ, ಅದರ ನಂತರ ಬರವಣಿಗೆ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಸ್ಥಗಿತಗೊಳಿಸಲಾಗುತ್ತದೆ. ಪೈಪ್ಲೈನ್ನಿಂದ ಓದುವಾಗ, ಬಫರ್ ಮಾಡಿದ ಡೇಟಾವನ್ನು ತೆಗೆದುಕೊಳ್ಳಲಾಗುತ್ತದೆ.
ಮುಂದಿನ ವರ್ಷದ ಹೊತ್ತಿಗೆ ಕರ್ನಲ್ ಅನ್ನು C ನಲ್ಲಿ ಪುನಃ ಬರೆಯಲಾಯಿತು, ಮತ್ತು pipe(fildes)
»:
ಸಿಸ್ಟಮ್ ಕರೆ ಪೈಪ್ ಪೈಪ್ಲೈನ್ ಎಂಬ ಇನ್ಪುಟ್/ಔಟ್ಪುಟ್ ಕಾರ್ಯವಿಧಾನವನ್ನು ರಚಿಸುತ್ತದೆ. ಹಿಂತಿರುಗಿದ ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ಗಳನ್ನು ಓದಲು ಮತ್ತು ಬರೆಯಲು ಕಾರ್ಯಾಚರಣೆಗಳಲ್ಲಿ ಬಳಸಬಹುದು. ಪೈಪ್ಲೈನ್ಗೆ ಏನನ್ನಾದರೂ ಬರೆದಾಗ, r1 (resp. fildes[1]) ನಲ್ಲಿ ಹಿಂತಿರುಗಿದ ಹ್ಯಾಂಡಲ್ ಅನ್ನು ಬಳಸಲಾಗುತ್ತದೆ, 4096 ಬೈಟ್ಗಳ ಡೇಟಾಗೆ ಬಫರ್ ಮಾಡಲಾಗುತ್ತದೆ, ಅದರ ನಂತರ ಬರೆಯುವ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಸ್ಥಗಿತಗೊಳಿಸಲಾಗುತ್ತದೆ. ಪೈಪ್ಲೈನ್ನಿಂದ ಓದುವಾಗ, ಹ್ಯಾಂಡಲ್ r0 (resp. fildes[0]) ಗೆ ಹಿಂತಿರುಗಿ ಡೇಟಾವನ್ನು ತೆಗೆದುಕೊಳ್ಳುತ್ತದೆ.
ಪೈಪ್ಲೈನ್ ಅನ್ನು ಒಮ್ಮೆ ವ್ಯಾಖ್ಯಾನಿಸಿದರೆ, ಎರಡು (ಅಥವಾ ಹೆಚ್ಚು) ಸಂವಹನ ಪ್ರಕ್ರಿಯೆಗಳು (ನಂತರದ ಕರೆಗಳಿಂದ ರಚಿಸಲಾಗಿದೆ ಫೋರ್ಕ್) ಕರೆಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಪೈಪ್ಲೈನ್ನಿಂದ ಡೇಟಾವನ್ನು ವರ್ಗಾಯಿಸುತ್ತದೆ ಓದಲು и ಬರೆಯಲು.
ಪೈಪ್ಲೈನ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾದ ಪ್ರಕ್ರಿಯೆಗಳ ರೇಖೀಯ ಶ್ರೇಣಿಯನ್ನು ವ್ಯಾಖ್ಯಾನಿಸಲು ಶೆಲ್ ಸಿಂಟ್ಯಾಕ್ಸ್ ಅನ್ನು ಹೊಂದಿದೆ.
ಖಾಲಿ ಪೈಪ್ಲೈನ್ನಿಂದ ಓದಲು ಕರೆಗಳು (ಯಾವುದೇ ಬಫರ್ ಮಾಡಲಾದ ಡೇಟಾವನ್ನು ಒಳಗೊಂಡಿಲ್ಲ) ಅದು ಕೇವಲ ಒಂದು ತುದಿಯನ್ನು ಹೊಂದಿದೆ (ಎಲ್ಲಾ ಬರೆಯುವ ಫೈಲ್ ವಿವರಣೆಗಳನ್ನು ಮುಚ್ಚಲಾಗಿದೆ) "ಫೈಲ್ನ ಅಂತ್ಯ" ಹಿಂತಿರುಗಿಸುತ್ತದೆ. ಇದೇ ರೀತಿಯ ಪರಿಸ್ಥಿತಿಯಲ್ಲಿ ಬರೆಯಲು ಕರೆಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸಲಾಗುತ್ತದೆ.
ಮುಂಚಿನ
ಯುನಿಕ್ಸ್ನ ಆರನೇ ಆವೃತ್ತಿ (1975)
Unix ಮೂಲ ಕೋಡ್ ಅನ್ನು ಓದಲು ಪ್ರಾರಂಭಿಸೋಣ
ಹಲವು ವರ್ಷಗಳಿಂದ ಪುಸ್ತಕ ಲಯನ್ಸ್ ಬೆಲ್ ಲ್ಯಾಬ್ಸ್ನ ಹೊರಗೆ ಲಭ್ಯವಿರುವ ಯುನಿಕ್ಸ್ ಕರ್ನಲ್ನಲ್ಲಿನ ಏಕೈಕ ದಾಖಲೆಯಾಗಿದೆ. ಆರನೇ ಆವೃತ್ತಿಯ ಪರವಾನಗಿಯು ಶಿಕ್ಷಕರಿಗೆ ಅದರ ಮೂಲ ಕೋಡ್ ಅನ್ನು ಬಳಸಲು ಅವಕಾಶ ಮಾಡಿಕೊಟ್ಟರೂ, ಏಳನೇ ಆವೃತ್ತಿಯ ಪರವಾನಗಿಯು ಈ ಸಾಧ್ಯತೆಯನ್ನು ಹೊರತುಪಡಿಸಿದೆ, ಆದ್ದರಿಂದ ಪುಸ್ತಕವನ್ನು ಅಕ್ರಮ ಟೈಪ್ರೈಟನ್ ಪ್ರತಿಗಳ ರೂಪದಲ್ಲಿ ವಿತರಿಸಲಾಯಿತು.
ಇಂದು ನೀವು ಪುಸ್ತಕದ ಮರುಮುದ್ರಣವನ್ನು ಖರೀದಿಸಬಹುದು, ಅದರ ಕವರ್ ನಕಲು ಯಂತ್ರದಲ್ಲಿ ವಿದ್ಯಾರ್ಥಿಗಳನ್ನು ತೋರಿಸುತ್ತದೆ. ಮತ್ತು ವಾರೆನ್ ಟೂಮಿಗೆ ಧನ್ಯವಾದಗಳು (TUHS ಯೋಜನೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿದವರು) ನೀವು ಡೌನ್ಲೋಡ್ ಮಾಡಬಹುದು
15 ವರ್ಷಗಳ ಹಿಂದೆ, ನಾನು ನೀಡಲಾದ ಮೂಲ ಕೋಡ್ನ ನಕಲನ್ನು ಟೈಪ್ ಮಾಡಿದ್ದೇನೆ ಲಯನ್ಸ್, ಏಕೆಂದರೆ ಅಜ್ಞಾತ ಸಂಖ್ಯೆಯ ಇತರ ಪ್ರತಿಗಳಿಂದ ನನ್ನ ಪ್ರತಿಯ ಗುಣಮಟ್ಟ ನನಗೆ ಇಷ್ಟವಾಗಲಿಲ್ಲ. TUHS ಇನ್ನೂ ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲ ಮತ್ತು ನಾನು ಹಳೆಯ ಮೂಲಗಳಿಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ. ಆದರೆ 1988 ರಲ್ಲಿ, ನಾನು ಹಳೆಯ 9-ಟ್ರ್ಯಾಕ್ ಟೇಪ್ ಅನ್ನು ಕಂಡುಕೊಂಡೆ, ಅದು PDP11 ಕಂಪ್ಯೂಟರ್ನಿಂದ ಬ್ಯಾಕಪ್ ಅನ್ನು ಒಳಗೊಂಡಿದೆ. ಇದು ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿದೆಯೇ ಎಂದು ಹೇಳಲು ಕಷ್ಟವಾಯಿತು, ಆದರೆ ಒಂದು ಅಖಂಡ /usr/src/ ಮರವಿತ್ತು, ಅದರಲ್ಲಿ ಹೆಚ್ಚಿನ ಫೈಲ್ಗಳನ್ನು 1979 ವರ್ಷ ಎಂದು ಲೇಬಲ್ ಮಾಡಲಾಗಿದೆ, ಅದು ಪ್ರಾಚೀನವಾಗಿಯೂ ಕಾಣುತ್ತದೆ. ನಾನು ನಂಬಿದಂತೆ ಇದು ಏಳನೇ ಆವೃತ್ತಿ ಅಥವಾ ಅದರ ಉತ್ಪನ್ನ PWB ಆಗಿತ್ತು.
ನಾನು ಹುಡುಕಾಟವನ್ನು ಆಧಾರವಾಗಿ ತೆಗೆದುಕೊಂಡೆ ಮತ್ತು ಆರನೇ ಆವೃತ್ತಿಗೆ ಮೂಲಗಳನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಸಂಪಾದಿಸಿದೆ. ಕೆಲವು ಕೋಡ್ ಒಂದೇ ಆಗಿರುತ್ತದೆ, ಆದರೆ ಕೆಲವನ್ನು ಸ್ವಲ್ಪ ಸಂಪಾದಿಸಬೇಕಾಗಿತ್ತು, ಆಧುನಿಕ += ಟೋಕನ್ ಅನ್ನು ಹಳತಾದ =+ ಗೆ ಬದಲಾಯಿಸಬೇಕಾಗಿತ್ತು. ಕೆಲವು ವಿಷಯಗಳನ್ನು ಸರಳವಾಗಿ ಅಳಿಸಲಾಗಿದೆ, ಮತ್ತು ಕೆಲವು ಸಂಪೂರ್ಣವಾಗಿ ಪುನಃ ಬರೆಯಬೇಕಾಗಿತ್ತು, ಆದರೆ ಹೆಚ್ಚು ಅಲ್ಲ.
ಮತ್ತು ಇಂದು ನಾವು TUHS ನಲ್ಲಿ ಆರನೇ ಆವೃತ್ತಿಯ ಮೂಲ ಕೋಡ್ ಅನ್ನು ಆನ್ಲೈನ್ನಲ್ಲಿ ಓದಬಹುದು
ಅಂದಹಾಗೆ, ಮೊದಲ ನೋಟದಲ್ಲಿ, ಕೆರ್ನಿಘನ್ ಮತ್ತು ರಿಚ್ಚಿ ಅವಧಿಯ ಮೊದಲು ಸಿ-ಕೋಡ್ನ ಮುಖ್ಯ ಲಕ್ಷಣವಾಗಿದೆ ಸಂಕ್ಷಿಪ್ತತೆ. ನನ್ನ ಸೈಟ್ನಲ್ಲಿ ತುಲನಾತ್ಮಕವಾಗಿ ಕಿರಿದಾದ ಪ್ರದರ್ಶನ ಪ್ರದೇಶಕ್ಕೆ ಸರಿಹೊಂದುವಂತೆ ವ್ಯಾಪಕವಾದ ಸಂಪಾದನೆ ಇಲ್ಲದೆಯೇ ಕೋಡ್ನ ತುಣುಕುಗಳನ್ನು ಸೇರಿಸಲು ನನಗೆ ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ.
ಆರಂಭದಲ್ಲಿ
/*
* Max allowable buffering per pipe.
* This is also the max size of the
* file created to implement the pipe.
* If this size is bigger than 4096,
* pipes will be implemented in LARG
* files, which is probably not good.
*/
#define PIPSIZ 4096
ನಾಲ್ಕನೇ ಆವೃತ್ತಿಯ ನಂತರ ಬಫರ್ ಗಾತ್ರವು ಬದಲಾಗಿಲ್ಲ. ಆದರೆ ಇಲ್ಲಿ ನಾವು ನೋಡುತ್ತೇವೆ, ಯಾವುದೇ ಸಾರ್ವಜನಿಕ ದಾಖಲಾತಿಗಳಿಲ್ಲದೆ, ಪೈಪ್ಲೈನ್ಗಳು ಒಮ್ಮೆ ಫೈಲ್ಗಳನ್ನು ಬ್ಯಾಕಪ್ ಸಂಗ್ರಹಣೆಯಾಗಿ ಬಳಸಿದವು!
LARG ಫೈಲ್ಗಳಿಗೆ ಸಂಬಂಧಿಸಿದಂತೆ, ಅವುಗಳು ಸಂಬಂಧಿಸಿವೆ
ನಿಜವಾದ ಸಿಸ್ಟಮ್ ಕರೆ ಇಲ್ಲಿದೆ pipe
:
/*
* The sys-pipe entry.
* Allocate an inode on the root device.
* Allocate 2 file structures.
* Put it all together with flags.
*/
pipe()
{
register *ip, *rf, *wf;
int r;
ip = ialloc(rootdev);
if(ip == NULL)
return;
rf = falloc();
if(rf == NULL) {
iput(ip);
return;
}
r = u.u_ar0[R0];
wf = falloc();
if(wf == NULL) {
rf->f_count = 0;
u.u_ofile[r] = NULL;
iput(ip);
return;
}
u.u_ar0[R1] = u.u_ar0[R0]; /* wf's fd */
u.u_ar0[R0] = r; /* rf's fd */
wf->f_flag = FWRITE|FPIPE;
wf->f_inode = ip;
rf->f_flag = FREAD|FPIPE;
rf->f_inode = ip;
ip->i_count = 2;
ip->i_flag = IACC|IUPD;
ip->i_mode = IALLOC;
}
ಇಲ್ಲಿ ಏನು ನಡೆಯುತ್ತಿದೆ ಎಂಬುದನ್ನು ಕಾಮೆಂಟ್ ಸ್ಪಷ್ಟವಾಗಿ ವಿವರಿಸುತ್ತದೆ. ಆದರೆ ಕೋಡ್ ಅನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು ಅಷ್ಟು ಸುಲಭವಲ್ಲ, ಭಾಗಶಃ ಕಾರಣ "R0
и R1
ಸಿಸ್ಟಮ್ ಕರೆ ನಿಯತಾಂಕಗಳು ಮತ್ತು ರಿಟರ್ನ್ ಮೌಲ್ಯಗಳನ್ನು ರವಾನಿಸಲಾಗಿದೆ.
ಇದರೊಂದಿಗೆ ಪ್ರಯತ್ನಿಸೋಣ
pipe()
ಮೂಲಕ ಮಾಡಬೇಕು R0
и R1
ಓದಲು ಮತ್ತು ಬರೆಯಲು ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ ಸಂಖ್ಯೆಗಳನ್ನು ಹಿಂತಿರುಗಿಸಿ. falloc()
ಫೈಲ್ ರಚನೆಗೆ ಪಾಯಿಂಟರ್ ಅನ್ನು ಹಿಂತಿರುಗಿಸುತ್ತದೆ, ಆದರೆ ಮೂಲಕ "ರಿಟರ್ನ್" ಮಾಡುತ್ತದೆ u.u_ar0[R0]
ಮತ್ತು ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್. ಅಂದರೆ, ಕೋಡ್ ಉಳಿಸುತ್ತದೆ r
ಓದಲು ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ ಮತ್ತು ನೇರವಾಗಿ ಬರೆಯಲು ಫೈಲ್ ಡಿಸ್ಕ್ರಿಪ್ಟರ್ ಅನ್ನು ನಿಯೋಜಿಸುತ್ತದೆ u.u_ar0[R0]
ಎರಡನೇ ಕರೆ ನಂತರ falloc()
.
ಧ್ವಜ FPIPE
, ಪೈಪ್ಲೈನ್ ಅನ್ನು ರಚಿಸುವಾಗ ನಾವು ಹೊಂದಿಸುವ ಕಾರ್ಯದ ನಡವಳಿಕೆಯನ್ನು ನಿಯಂತ್ರಿಸುತ್ತದೆ
/*
* common code for read and write calls:
* check permissions, set base, count, and offset,
* and switch out to readi, writei, or pipe code.
*/
rdwr(mode)
{
register *fp, m;
m = mode;
fp = getf(u.u_ar0[R0]);
/* … */
if(fp->f_flag&FPIPE) {
if(m==FREAD)
readp(fp); else
writep(fp);
}
/* … */
}
ನಂತರ ಕಾರ್ಯ readp()
в pipe.c
ಪೈಪ್ಲೈನ್ನಿಂದ ಡೇಟಾವನ್ನು ಓದುತ್ತದೆ. ಆದರೆ ಪ್ರಾರಂಭದಿಂದ ಅನುಷ್ಠಾನವನ್ನು ಪತ್ತೆಹಚ್ಚುವುದು ಉತ್ತಮ writep()
. ಮತ್ತೊಮ್ಮೆ, ವಾದಗಳನ್ನು ಹಾದುಹೋಗುವ ಸಂಪ್ರದಾಯಗಳ ಕಾರಣದಿಂದಾಗಿ ಕೋಡ್ ಹೆಚ್ಚು ಸಂಕೀರ್ಣವಾಗಿದೆ, ಆದರೆ ಕೆಲವು ವಿವರಗಳನ್ನು ಬಿಟ್ಟುಬಿಡಬಹುದು.
writep(fp)
{
register *rp, *ip, c;
rp = fp;
ip = rp->f_inode;
c = u.u_count;
loop:
/* If all done, return. */
plock(ip);
if(c == 0) {
prele(ip);
u.u_count = 0;
return;
}
/*
* If there are not both read and write sides of the
* pipe active, return error and signal too.
*/
if(ip->i_count < 2) {
prele(ip);
u.u_error = EPIPE;
psignal(u.u_procp, SIGPIPE);
return;
}
/*
* If the pipe is full, wait for reads to deplete
* and truncate it.
*/
if(ip->i_size1 == PIPSIZ) {
ip->i_mode =| IWRITE;
prele(ip);
sleep(ip+1, PPIPE);
goto loop;
}
/* Write what is possible and loop back. */
u.u_offset[0] = 0;
u.u_offset[1] = ip->i_size1;
u.u_count = min(c, PIPSIZ-u.u_offset[1]);
c =- u.u_count;
writei(ip);
prele(ip);
if(ip->i_mode&IREAD) {
ip->i_mode =& ~IREAD;
wakeup(ip+2);
}
goto loop;
}
ನಾವು ಪೈಪ್ಲೈನ್ ಇನ್ಪುಟ್ಗೆ ಬೈಟ್ಗಳನ್ನು ಬರೆಯಲು ಬಯಸುತ್ತೇವೆ u.u_count
. ಮೊದಲು ನಾವು ಐನೋಡ್ ಅನ್ನು ಲಾಕ್ ಮಾಡಬೇಕಾಗಿದೆ (ಕೆಳಗೆ ನೋಡಿ plock
/prele
).
ನಂತರ ನಾವು ಐನೋಡ್ ರೆಫರೆನ್ಸ್ ಕೌಂಟರ್ ಅನ್ನು ಪರಿಶೀಲಿಸುತ್ತೇವೆ. ಪೈಪ್ಲೈನ್ನ ಎರಡೂ ತುದಿಗಳು ತೆರೆದಿರುವವರೆಗೆ, ಕೌಂಟರ್ 2 ಕ್ಕೆ ಸಮನಾಗಿರಬೇಕು. ನಾವು ಒಂದು ಲಿಂಕ್ ಅನ್ನು ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುತ್ತೇವೆ (ಇಂದ rp->f_inode
), ಆದ್ದರಿಂದ ಕೌಂಟರ್ 2 ಕ್ಕಿಂತ ಕಡಿಮೆಯಿದ್ದರೆ, ಓದುವ ಪ್ರಕ್ರಿಯೆಯು ಪೈಪ್ಲೈನ್ನ ಅಂತ್ಯವನ್ನು ಮುಚ್ಚಿದೆ ಎಂದು ಅರ್ಥೈಸಬೇಕು. ಬೇರೆ ರೀತಿಯಲ್ಲಿ ಹೇಳುವುದಾದರೆ, ನಾವು ಮುಚ್ಚಿದ ಪೈಪ್ಲೈನ್ಗೆ ಬರೆಯಲು ಪ್ರಯತ್ನಿಸುತ್ತಿದ್ದೇವೆ ಮತ್ತು ಇದು ದೋಷವಾಗಿದೆ. ಮೊದಲ ಬಾರಿಗೆ ದೋಷ ಕೋಡ್ EPIPE
ಮತ್ತು ಸಂಕೇತ SIGPIPE
ಯುನಿಕ್ಸ್ನ ಆರನೇ ಆವೃತ್ತಿಯಲ್ಲಿ ಕಾಣಿಸಿಕೊಂಡಿದೆ.
ಆದರೆ ಕನ್ವೇಯರ್ ತೆರೆದಿದ್ದರೂ, ಅದು ತುಂಬಿರಬಹುದು. ಈ ಸಂದರ್ಭದಲ್ಲಿ, ನಾವು ಲಾಕ್ ಅನ್ನು ಬಿಡುಗಡೆ ಮಾಡುತ್ತೇವೆ ಮತ್ತು ಇನ್ನೊಂದು ಪ್ರಕ್ರಿಯೆಯು ಪೈಪ್ಲೈನ್ನಿಂದ ಓದುತ್ತದೆ ಮತ್ತು ಅದರಲ್ಲಿ ಸಾಕಷ್ಟು ಜಾಗವನ್ನು ಮುಕ್ತಗೊಳಿಸುತ್ತದೆ ಎಂಬ ಭರವಸೆಯಲ್ಲಿ ನಿದ್ರೆಗೆ ಹೋಗುತ್ತೇವೆ. ಎಚ್ಚರವಾದ ನಂತರ, ನಾವು ಪ್ರಾರಂಭಕ್ಕೆ ಹಿಂತಿರುಗುತ್ತೇವೆ, ಲಾಕ್ ಅನ್ನು ಮತ್ತೆ ಸ್ಥಗಿತಗೊಳಿಸುತ್ತೇವೆ ಮತ್ತು ಹೊಸ ರೆಕಾರ್ಡಿಂಗ್ ಚಕ್ರವನ್ನು ಪ್ರಾರಂಭಿಸುತ್ತೇವೆ.
ಪೈಪ್ಲೈನ್ನಲ್ಲಿ ಸಾಕಷ್ಟು ಮುಕ್ತ ಸ್ಥಳವಿದ್ದರೆ, ನಾವು ಅದನ್ನು ಬಳಸಿಕೊಂಡು ಡೇಟಾವನ್ನು ಬರೆಯುತ್ತೇವೆ i_size1
inode ನಲ್ಲಿ (ಪೈಪ್ಲೈನ್ ಖಾಲಿಯಾಗಿದ್ದರೆ ಅದು 0 ಕ್ಕೆ ಸಮನಾಗಿರುತ್ತದೆ) ಅದು ಈಗಾಗಲೇ ಹೊಂದಿರುವ ಡೇಟಾದ ಅಂತ್ಯವನ್ನು ಸೂಚಿಸುತ್ತದೆ. ಸಾಕಷ್ಟು ರೆಕಾರ್ಡಿಂಗ್ ಸ್ಥಳವಿದ್ದರೆ, ನಾವು ಪೈಪ್ಲೈನ್ ಅನ್ನು ತುಂಬಬಹುದು i_size1
ಗೆ PIPESIZ
. ನಂತರ ನಾವು ಲಾಕ್ ಅನ್ನು ಬಿಡುಗಡೆ ಮಾಡುತ್ತೇವೆ ಮತ್ತು ಪೈಪ್ಲೈನ್ನಿಂದ ಓದಲು ಕಾಯುತ್ತಿರುವ ಯಾವುದೇ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಎಚ್ಚರಗೊಳಿಸಲು ಪ್ರಯತ್ನಿಸುತ್ತೇವೆ. ನಮಗೆ ಬೇಕಾದಷ್ಟು ಬೈಟ್ಗಳನ್ನು ಬರೆಯಲು ಸಾಧ್ಯವೇ ಎಂದು ನೋಡಲು ನಾವು ಪ್ರಾರಂಭಕ್ಕೆ ಹಿಂತಿರುಗುತ್ತೇವೆ. ಅದು ವಿಫಲವಾದರೆ, ನಾವು ಹೊಸ ರೆಕಾರ್ಡಿಂಗ್ ಚಕ್ರವನ್ನು ಪ್ರಾರಂಭಿಸುತ್ತೇವೆ.
ಸಾಮಾನ್ಯವಾಗಿ ಪ್ಯಾರಾಮೀಟರ್ i_mode
ಅನುಮತಿಗಳನ್ನು ಸಂಗ್ರಹಿಸಲು inode ಅನ್ನು ಬಳಸಲಾಗುತ್ತದೆ r
, w
и x
. ಆದರೆ ಪೈಪ್ಲೈನ್ಗಳ ಸಂದರ್ಭದಲ್ಲಿ, ಕೆಲವು ಪ್ರಕ್ರಿಯೆಯು ಬಿಟ್ಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಬರೆಯಲು ಅಥವಾ ಓದಲು ಕಾಯುತ್ತಿದೆ ಎಂದು ನಾವು ಸಂಕೇತಿಸುತ್ತೇವೆ IREAD
и IWRITE
ಕ್ರಮವಾಗಿ. ಪ್ರಕ್ರಿಯೆಯು ಧ್ವಜ ಮತ್ತು ಕರೆಗಳನ್ನು ಹೊಂದಿಸುತ್ತದೆ sleep()
, ಮತ್ತು ಭವಿಷ್ಯದಲ್ಲಿ ಕೆಲವು ಇತರ ಪ್ರಕ್ರಿಯೆಯು ಕಾರಣವಾಗಬಹುದು ಎಂದು ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ wakeup()
.
ನಿಜವಾದ ಮ್ಯಾಜಿಕ್ ಸಂಭವಿಸುತ್ತದೆ sleep()
и wakeup()
. ಅವುಗಳನ್ನು ಅಳವಡಿಸಲಾಗಿದೆ
/*
* Give up the processor till a wakeup occurs
* on chan, at which time the process
* enters the scheduling queue at priority pri.
* The most important effect of pri is that when
* pri<0 a signal cannot disturb the sleep;
* if pri>=0 signals will be processed.
* Callers of this routine must be prepared for
* premature return, and check that the reason for
* sleeping has gone away.
*/
sleep(chan, pri) /* … */
/*
* Wake up all processes sleeping on chan.
*/
wakeup(chan) /* … */
ಉಂಟುಮಾಡುವ ಪ್ರಕ್ರಿಯೆ sleep()
ನಿರ್ದಿಷ್ಟ ಚಾನಲ್ಗೆ, ನಂತರ ಮತ್ತೊಂದು ಪ್ರಕ್ರಿಯೆಯಿಂದ ಎಚ್ಚರಗೊಳ್ಳಬಹುದು, ಅದು ಕಾರಣವಾಗುತ್ತದೆ wakeup()
ಅದೇ ಚಾನಲ್ಗಾಗಿ. writep()
и readp()
ಅಂತಹ ಜೋಡಿ ಕರೆಗಳ ಮೂಲಕ ಅವರ ಕ್ರಿಯೆಗಳನ್ನು ಸಂಯೋಜಿಸಿ. ಎಂಬುದನ್ನು ಗಮನಿಸಿ pipe.c
ಯಾವಾಗಲೂ ಆದ್ಯತೆ ನೀಡುತ್ತದೆ PPIPE
ಕರೆದಾಗ sleep()
, ಆದ್ದರಿಂದ ಅದು ಇಲ್ಲಿದೆ sleep()
ಸಂಕೇತದಿಂದ ಅಡ್ಡಿಪಡಿಸಬಹುದು.
ಈಗ ನಾವು ಕಾರ್ಯವನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳಲು ಎಲ್ಲವನ್ನೂ ಹೊಂದಿದ್ದೇವೆ readp()
:
readp(fp)
int *fp;
{
register *rp, *ip;
rp = fp;
ip = rp->f_inode;
loop:
/* Very conservative locking. */
plock(ip);
/*
* If the head (read) has caught up with
* the tail (write), reset both to 0.
*/
if(rp->f_offset[1] == ip->i_size1) {
if(rp->f_offset[1] != 0) {
rp->f_offset[1] = 0;
ip->i_size1 = 0;
if(ip->i_mode&IWRITE) {
ip->i_mode =& ~IWRITE;
wakeup(ip+1);
}
}
/*
* If there are not both reader and
* writer active, return without
* satisfying read.
*/
prele(ip);
if(ip->i_count < 2)
return;
ip->i_mode =| IREAD;
sleep(ip+2, PPIPE);
goto loop;
}
/* Read and return */
u.u_offset[0] = 0;
u.u_offset[1] = rp->f_offset[1];
readi(ip);
rp->f_offset[1] = u.u_offset[1];
prele(ip);
}
ಈ ಕಾರ್ಯವನ್ನು ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಓದಲು ನಿಮಗೆ ಸುಲಭವಾಗಬಹುದು. ಪೈಪ್ಲೈನ್ನಲ್ಲಿ ಕೆಲವು ಡೇಟಾ ಇದ್ದಾಗ "ಓದಲು ಮತ್ತು ಹಿಂತಿರುಗಿ" ಶಾಖೆಯನ್ನು ಸಾಮಾನ್ಯವಾಗಿ ಬಳಸಲಾಗುತ್ತದೆ. ಈ ಸಂದರ್ಭದಲ್ಲಿ, ನಾವು ಬಳಸುತ್ತೇವೆ f_offset
ಓದುವುದು, ತದನಂತರ ಅನುಗುಣವಾದ ಆಫ್ಸೆಟ್ನ ಮೌಲ್ಯವನ್ನು ನವೀಕರಿಸಿ.
ನಂತರದ ರೀಡ್ಗಳಲ್ಲಿ, ರೀಡ್ ಆಫ್ಸೆಟ್ ತಲುಪಿದ್ದರೆ ಪೈಪ್ಲೈನ್ ಖಾಲಿಯಾಗಿರುತ್ತದೆ i_size1
ಇನೋಡ್ ನಲ್ಲಿ. ನಾವು ಸ್ಥಾನವನ್ನು 0 ಗೆ ಮರುಹೊಂದಿಸುತ್ತೇವೆ ಮತ್ತು ಪೈಪ್ಲೈನ್ಗೆ ಬರೆಯಲು ಬಯಸುವ ಯಾವುದೇ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಎಚ್ಚರಗೊಳಿಸಲು ಪ್ರಯತ್ನಿಸುತ್ತೇವೆ. ಕನ್ವೇಯರ್ ತುಂಬಿದಾಗ ನಮಗೆ ತಿಳಿದಿದೆ, writep()
ಮೇಲೆ ನಿದ್ದೆ ಬರುತ್ತದೆ ip+1
. ಮತ್ತು ಈಗ ಪೈಪ್ಲೈನ್ ಖಾಲಿಯಾಗಿದೆ, ಅದರ ಬರವಣಿಗೆಯ ಚಕ್ರವನ್ನು ಪುನರಾರಂಭಿಸಲು ನಾವು ಅದನ್ನು ಎಚ್ಚರಗೊಳಿಸಬಹುದು.
ನೀವು ಓದಲು ಏನೂ ಇಲ್ಲದಿದ್ದರೆ, ನಂತರ readp()
ಧ್ವಜವನ್ನು ಹೊಂದಿಸಬಹುದು IREAD
ಮತ್ತು ಮೇಲೆ ನಿದ್ರಿಸುವುದು ip+2
. ಅವನಿಗೆ ಏನು ಎಚ್ಚರವಾಗುತ್ತದೆ ಎಂದು ನಮಗೆ ತಿಳಿದಿದೆ writep()
, ಇದು ಪೈಪ್ಲೈನ್ಗೆ ಕೆಲವು ಡೇಟಾವನ್ನು ಬರೆಯುವಾಗ.
ಗೆ ಕಾಮೆಂಟ್ಗಳು u
"ನಾವು ಅವುಗಳನ್ನು ಸಾಮಾನ್ಯ I/O ಕಾರ್ಯಗಳಂತೆ ಪರಿಗಣಿಸಬಹುದು ಅದು ಫೈಲ್, ಸ್ಥಾನ, ಮೆಮೊರಿಯಲ್ಲಿ ಬಫರ್ ಅನ್ನು ತೆಗೆದುಕೊಳ್ಳುತ್ತದೆ ಮತ್ತು ಓದಲು ಅಥವಾ ಬರೆಯಲು ಬೈಟ್ಗಳ ಸಂಖ್ಯೆಯನ್ನು ಎಣಿಸಬಹುದು.
/*
* Read the file corresponding to
* the inode pointed at by the argument.
* The actual read arguments are found
* in the variables:
* u_base core address for destination
* u_offset byte offset in file
* u_count number of bytes to read
* u_segflg read to kernel/user
*/
readi(aip)
struct inode *aip;
/* … */
/*
* Write the file corresponding to
* the inode pointed at by the argument.
* The actual write arguments are found
* in the variables:
* u_base core address for source
* u_offset byte offset in file
* u_count number of bytes to write
* u_segflg write to kernel/user
*/
writei(aip)
struct inode *aip;
/* … */
"ಸಂಪ್ರದಾಯವಾದಿ" ತಡೆಗಟ್ಟುವಿಕೆಗೆ ಸಂಬಂಧಿಸಿದಂತೆ, ನಂತರ readp()
и writep()
ಅವರು ತಮ್ಮ ಕೆಲಸವನ್ನು ಪೂರ್ಣಗೊಳಿಸುವವರೆಗೆ ಅಥವಾ ಫಲಿತಾಂಶವನ್ನು ಪಡೆಯುವವರೆಗೆ ಐನೋಡ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಿ (ಅಂದರೆ, ಕರೆ ಮಾಡಿ wakeup
). plock()
и prele()
ಸರಳವಾಗಿ ಕೆಲಸ ಮಾಡಿ: ವಿಭಿನ್ನ ಕರೆಗಳನ್ನು ಬಳಸುವುದು sleep
и wakeup
ನಾವು ಇದೀಗ ಬಿಡುಗಡೆ ಮಾಡಿದ ಲಾಕ್ ಅಗತ್ಯವಿರುವ ಯಾವುದೇ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಎಚ್ಚರಗೊಳಿಸಲು ನಮಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡಿ:
/*
* Lock a pipe.
* If its already locked, set the WANT bit and sleep.
*/
plock(ip)
int *ip;
{
register *rp;
rp = ip;
while(rp->i_flag&ILOCK) {
rp->i_flag =| IWANT;
sleep(rp, PPIPE);
}
rp->i_flag =| ILOCK;
}
/*
* Unlock a pipe.
* If WANT bit is on, wakeup.
* This routine is also used to unlock inodes in general.
*/
prele(ip)
int *ip;
{
register *rp;
rp = ip;
rp->i_flag =& ~ILOCK;
if(rp->i_flag&IWANT) {
rp->i_flag =& ~IWANT;
wakeup(rp);
}
}
ಮೊದಲಿಗೆ ನನಗೆ ಏಕೆ ಎಂದು ಅರ್ಥವಾಗಲಿಲ್ಲ readp()
ಉಂಟು ಮಾಡುವುದಿಲ್ಲ prele(ip)
ಕರೆ ಮೊದಲು wakeup(ip+1)
. ಮೊದಲನೆಯದು writep()
ಅದರ ಚಕ್ರದಲ್ಲಿ ಕಾರಣವಾಗುತ್ತದೆ, ಇದು plock(ip)
, ಇದು ಸ್ಥಗಿತಕ್ಕೆ ಕಾರಣವಾಗುತ್ತದೆ readp()
ನನ್ನ ಬ್ಲಾಕ್ ಅನ್ನು ಇನ್ನೂ ತೆಗೆದುಹಾಕಿಲ್ಲ, ಆದ್ದರಿಂದ ಹೇಗಾದರೂ ಕೋಡ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸಬೇಕು. ನೀವು ನೋಡಿದರೆ wakeup()
, ನಂತರ ಅದು ನಿದ್ರೆಯ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲು ಸಿದ್ಧವಾಗಿದೆ ಎಂದು ಮಾತ್ರ ಗುರುತಿಸುತ್ತದೆ, ಇದರಿಂದ ಭವಿಷ್ಯದಲ್ಲಿ ಅದು ಸ್ಪಷ್ಟವಾಗುತ್ತದೆ sched()
ನಿಜವಾಗಿಯೂ ಅದನ್ನು ಪ್ರಾರಂಭಿಸಿದೆ. ಆದ್ದರಿಂದ readp()
ಕಾರಣಗಳು wakeup()
, ಲಾಕ್ ಅನ್ನು ತೆಗೆದುಹಾಕುತ್ತದೆ, ಹೊಂದಿಸುತ್ತದೆ IREAD
ಮತ್ತು ಕರೆಗಳು sleep(ip+2)
- ಇದೆಲ್ಲವೂ ಮೊದಲು writep()
ಚಕ್ರವನ್ನು ಪುನರಾರಂಭಿಸುತ್ತದೆ.
ಇದು ಆರನೇ ಆವೃತ್ತಿಯಲ್ಲಿ ಕನ್ವೇಯರ್ಗಳ ವಿವರಣೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸುತ್ತದೆ. ಸರಳ ಕೋಡ್, ದೂರಗಾಮಿ ಪರಿಣಾಮಗಳು.
Xv6, ಸರಳವಾದ Unix ತರಹದ ಕರ್ನಲ್
ಕರ್ನಲ್ ರಚಿಸಲು
ಕೋಡ್ ಸ್ಪಷ್ಟ ಮತ್ತು ಚಿಂತನಶೀಲ ಅನುಷ್ಠಾನವನ್ನು ಒಳಗೊಂಡಿದೆ pipealloc()
:
#define PIPESIZE 512
struct pipe {
struct spinlock lock;
char data[PIPESIZE];
uint nread; // number of bytes read
uint nwrite; // number of bytes written
int readopen; // read fd is still open
int writeopen; // write fd is still open
};
int
pipealloc(struct file **f0, struct file **f1)
{
struct pipe *p;
p = 0;
*f0 = *f1 = 0;
if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0)
goto bad;
if((p = (struct pipe*)kalloc()) == 0)
goto bad;
p->readopen = 1;
p->writeopen = 1;
p->nwrite = 0;
p->nread = 0;
initlock(&p->lock, "pipe");
(*f0)->type = FD_PIPE;
(*f0)->readable = 1;
(*f0)->writable = 0;
(*f0)->pipe = p;
(*f1)->type = FD_PIPE;
(*f1)->readable = 0;
(*f1)->writable = 1;
(*f1)->pipe = p;
return 0;
bad:
if(p)
kfree((char*)p);
if(*f0)
fileclose(*f0);
if(*f1)
fileclose(*f1);
return -1;
}
pipealloc()
ಕಾರ್ಯಗಳನ್ನು ಒಳಗೊಂಡಿರುವ ಉಳಿದ ಅನುಷ್ಠಾನದ ಸ್ಥಿತಿಯನ್ನು ಹೊಂದಿಸುತ್ತದೆ piperead()
, pipewrite()
и pipeclose()
. ನಿಜವಾದ ಸಿಸ್ಟಮ್ ಕರೆ sys_pipe
ಒಂದು ಹೊದಿಕೆಯನ್ನು ಅಳವಡಿಸಲಾಗಿದೆ
ಲಿನಕ್ಸ್ 0.01
Linux 0.01 ಮೂಲ ಕೋಡ್ ಅನ್ನು ಕಾಣಬಹುದು. ಅವರಲ್ಲಿ ಪೈಪ್ಲೈನ್ಗಳ ಅನುಷ್ಠಾನದ ಬಗ್ಗೆ ಅಧ್ಯಯನ ಮಾಡಲು ಇದು ಬೋಧಪ್ರದವಾಗಿರುತ್ತದೆ fs
/pipe.c
. ಇದು ಪೈಪ್ಲೈನ್ ಅನ್ನು ಪ್ರತಿನಿಧಿಸಲು ಐನೋಡ್ ಅನ್ನು ಬಳಸುತ್ತದೆ, ಆದರೆ ಪೈಪ್ಲೈನ್ ಅನ್ನು ಆಧುನಿಕ C ನಲ್ಲಿ ಬರೆಯಲಾಗಿದೆ. ನೀವು XNUMX ನೇ ಆವೃತ್ತಿ ಕೋಡ್ ಮೂಲಕ ಕೆಲಸ ಮಾಡಿದ್ದರೆ, ನಿಮಗೆ ಇಲ್ಲಿ ಯಾವುದೇ ತೊಂದರೆ ಇರುವುದಿಲ್ಲ. ಕಾರ್ಯವು ಈ ರೀತಿ ಕಾಣುತ್ತದೆ write_pipe()
:
int write_pipe(struct m_inode * inode, char * buf, int count)
{
char * b=buf;
wake_up(&inode->i_wait);
if (inode->i_count != 2) { /* no readers */
current->signal |= (1<<(SIGPIPE-1));
return -1;
}
while (count-->0) {
while (PIPE_FULL(*inode)) {
wake_up(&inode->i_wait);
if (inode->i_count != 2) {
current->signal |= (1<<(SIGPIPE-1));
return b-buf;
}
sleep_on(&inode->i_wait);
}
((char *)inode->i_size)[PIPE_HEAD(*inode)] =
get_fs_byte(b++);
INC_PIPE( PIPE_HEAD(*inode) );
wake_up(&inode->i_wait);
}
wake_up(&inode->i_wait);
return b-buf;
}
ರಚನೆಯ ವ್ಯಾಖ್ಯಾನಗಳನ್ನು ನೋಡದೆಯೇ, ಬರೆಯುವ ಕಾರ್ಯಾಚರಣೆಯು ಫಲಿತಾಂಶವನ್ನು ನೀಡುತ್ತದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಲು ಐನೋಡ್ ಉಲ್ಲೇಖ ಎಣಿಕೆಯನ್ನು ಹೇಗೆ ಬಳಸಲಾಗುತ್ತದೆ ಎಂಬುದನ್ನು ನೀವು ಲೆಕ್ಕಾಚಾರ ಮಾಡಬಹುದು SIGPIPE
. ಬೈಟ್-ಬೈ-ಬೈಟ್ ಕೆಲಸ ಮಾಡುವುದರ ಜೊತೆಗೆ, ಈ ಕಾರ್ಯವು ಮೇಲೆ ವಿವರಿಸಿದ ವಿಚಾರಗಳೊಂದಿಗೆ ಹೋಲಿಸಲು ಸುಲಭವಾಗಿದೆ. ತರ್ಕ ಕೂಡ sleep_on
/wake_up
ಅಷ್ಟು ಅನ್ಯವಾಗಿ ಕಾಣುತ್ತಿಲ್ಲ.
ಆಧುನಿಕ ಲಿನಕ್ಸ್ ಕರ್ನಲ್ಗಳು, FreeBSD, NetBSD, OpenBSD
ನಾನು ಕೆಲವು ಆಧುನಿಕ ಕರ್ನಲ್ಗಳ ಮೂಲಕ ತ್ವರಿತವಾಗಿ ಓಡಿದೆ. ಅವುಗಳಲ್ಲಿ ಯಾವುದೂ ಇನ್ನು ಮುಂದೆ ಡಿಸ್ಕ್ ಅನುಷ್ಠಾನವನ್ನು ಹೊಂದಿಲ್ಲ (ಆಶ್ಚರ್ಯಕರವಲ್ಲ). ಲಿನಕ್ಸ್ ತನ್ನದೇ ಆದ ಅನುಷ್ಠಾನವನ್ನು ಹೊಂದಿದೆ. ಮೂರು ಆಧುನಿಕ BSD ಕರ್ನಲ್ಗಳು ಜಾನ್ ಡೈಸನ್ ಬರೆದ ಕೋಡ್ನ ಆಧಾರದ ಮೇಲೆ ಅಳವಡಿಕೆಗಳನ್ನು ಹೊಂದಿದ್ದರೂ, ವರ್ಷಗಳಲ್ಲಿ ಅವು ಪರಸ್ಪರ ಭಿನ್ನವಾಗಿವೆ.
ಓದುವುದಕ್ಕಾಗಿ fs
/pipe.c
(Linux ನಲ್ಲಿ) ಅಥವಾ sys
/kern
/sys_pipe.c
(*BSD ನಲ್ಲಿ), ಇದು ನಿಜವಾದ ಸಮರ್ಪಣೆಯನ್ನು ತೆಗೆದುಕೊಳ್ಳುತ್ತದೆ. ಇಂದಿನ ಕೋಡ್ ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ವೆಕ್ಟರ್ ಮತ್ತು ಅಸಮಕಾಲಿಕ I/O ನಂತಹ ವೈಶಿಷ್ಟ್ಯಗಳಿಗೆ ಬೆಂಬಲವಾಗಿದೆ. ಮತ್ತು ಮೆಮೊರಿ ಹಂಚಿಕೆ, ಲಾಕ್ಗಳು ಮತ್ತು ಕರ್ನಲ್ ಕಾನ್ಫಿಗರೇಶನ್ನ ವಿವರಗಳು ಬಹಳವಾಗಿ ಬದಲಾಗುತ್ತವೆ. ಪರಿಚಯಾತ್ಮಕ ಆಪರೇಟಿಂಗ್ ಸಿಸ್ಟಂ ಕೋರ್ಸ್ಗೆ ಕಾಲೇಜುಗಳಿಗೆ ಇದು ಅಗತ್ಯವಿಲ್ಲ.
ಹೇಗಾದರೂ, ನಾನು ಕೆಲವು ಹಳೆಯ ಮಾದರಿಗಳನ್ನು ಅಗೆಯಲು ಆಸಕ್ತಿ ಹೊಂದಿದ್ದೇನೆ (ಉತ್ಪಾದಿಸುವಂತಹ SIGPIPE
ಮತ್ತು ಹಿಂತಿರುಗಿ EPIPE
ಮುಚ್ಚಿದ ಪೈಪ್ಲೈನ್ಗೆ ಬರೆಯುವಾಗ) ಈ ಎಲ್ಲಾ ವಿಭಿನ್ನ ಆಧುನಿಕ ಕರ್ನಲ್ಗಳಲ್ಲಿ. ನಾನು ಬಹುಶಃ ನಿಜ ಜೀವನದಲ್ಲಿ PDP-11 ಕಂಪ್ಯೂಟರ್ ಅನ್ನು ಎಂದಿಗೂ ನೋಡುವುದಿಲ್ಲ, ಆದರೆ ನಾನು ಹುಟ್ಟುವ ವರ್ಷಗಳ ಹಿಂದೆ ಬರೆದ ಕೋಡ್ನಿಂದ ಕಲಿಯಲು ಇನ್ನೂ ಬಹಳಷ್ಟು ಇದೆ.
2011 ರಲ್ಲಿ ದಿವಿ ಕಪೂರ್ ಬರೆದ ಲೇಖನ:
ಮೂಲ: www.habr.com