อะไรคือแอปพลิเคชันระบบไฟล์รูทขั้นต่ำที่ต้องใช้ในการบูทลินุกซ์เต็ม?


17

เป็นคำถามเกี่ยวกับแอปพลิเคชันพื้นที่ผู้ใช้ แต่ได้ยินฉัน!

สาม "แอปพลิเคชัน" ดังนั้นต้องพูดเพื่อบูตการแจกจ่ายการทำงานของ Linux:

  1. Bootloader - สำหรับการฝังตัวโดยทั่วไปคือ U-Boot แม้ว่าจะไม่ใช่ข้อกำหนดที่ยาก

  2. เคอร์เนล - มันค่อนข้างตรงไปตรงมา

  3. ระบบไฟล์รูท - ไม่สามารถบูตเชลล์ได้ มีระบบไฟล์ที่เคอร์เนลบูทไปยังและตำแหน่งที่initเรียกว่าฟอร์ม

คำถามของฉันเกี่ยวกับ # 3 หากมีคนต้องการสร้าง rootfs น้อยที่สุด (สำหรับคำถามนี้สมมติว่าไม่มี GUI, shell เท่านั้น) ไฟล์ / โปรแกรมใดที่จำเป็นต้องใช้ในการบู๊ตเชลล์


กำหนดน้อยที่สุด คุณสามารถใช้ไฟล์ปฏิบัติการเพียงไฟล์เดียวโดยไม่มีสิ่งอื่นใดตามที่อธิบายไว้ที่: superuser.com/a/991733/128124เพียงแค่ว่ามันไม่สามารถออกได้ทุกอย่างหรือตื่นตระหนกดังนั้นคุณจึงต้องวนรอบไม่สิ้นสุดหรือหลับยาว คำถามที่คล้ายกัน: unix.stackexchange.com/questions/17122/…
Ciro Santilli 事件改造中心中心法轮功六四事件

คำตอบ:


32

ทั้งหมดนี้ขึ้นอยู่กับบริการที่คุณต้องการบนอุปกรณ์ของคุณ

โปรแกรม

คุณสามารถทำให้ boot Linux เป็นเชลล์ได้โดยตรง มันไม่ได้มีประโยชน์มากในการผลิตใครจะแค่อยากให้เชลล์นั่งอยู่ตรงนั้น - แต่มันมีประโยชน์ในฐานะกลไกการแทรกแซงเมื่อคุณมี bootloader แบบโต้ตอบ: ส่งผ่านinit=/bin/shไปยังบรรทัดคำสั่งเคอร์เนล ทุกระบบลินุกซ์ (และระบบยูนิกซ์ทั้งหมด) มีเปลือกบอร์น / POSIX /bin/shสไตล์

คุณจะต้องชุดของสาธารณูปโภคเปลือก BusyBoxเป็นตัวเลือกที่ธรรมดามาก มันมีเปลือกและระบบสาธารณูปโภคที่พบบ่อยสำหรับไฟล์และการจัดการข้อความ ( cp, grep, ... ), การติดตั้งระบบเครือข่าย ( ping, ifconfig, ... ), การจัดการกระบวนการ ( ps, nice... ) และเครื่องมือต่างๆของระบบอื่น ๆ ( fdisk, mount, syslogd, ... ) BusyBox สามารถกำหนดค่าได้อย่างมาก: คุณสามารถเลือกเครื่องมือที่คุณต้องการและแม้กระทั่งคุณสมบัติแต่ละอย่างในเวลารวบรวมเพื่อให้ได้ขนาด / การทำงานที่เหมาะสมสำหรับแอปพลิเคชันของคุณ นอกเหนือจากshที่ต่ำสุดเปลือยที่คุณไม่สามารถจริงๆทำอะไรโดยไม่เป็นmount, umountและhalt, แต่มันจะผิดปกติไปไม่ได้นอกจากนี้ยังcat, cp, mv, rm,mkdir, rmdir, ps, syncและอีกไม่กี่ BusyBox ติดตั้งเป็นไบนารีเดียวที่เรียกว่าbusyboxพร้อมลิงก์สัญลักษณ์สำหรับแต่ละยูทิลิตี้

initขั้นตอนแรกในระบบยูนิกซ์ตามปกติจะเรียกว่า หน้าที่ของมันคือการเริ่มบริการอื่น ๆ BusyBox มีระบบเริ่มต้น นอกเหนือจากinitไบนารี่ (โดยปกติจะอยู่ใน/sbin) คุณจะต้องใช้ไฟล์การกำหนดค่าของมัน (โดยปกติจะเรียกว่า/etc/inittab- การแทนที่ init แบบใหม่บางอย่างจะทำไปกับไฟล์นั้น แต่คุณจะไม่พบมันในระบบฝังตัวขนาดเล็ก) และเมื่อ. สำหรับ BusyBox /etc/inittabเป็นตัวเลือก ถ้ามันหายไปคุณจะได้รูทเชลล์บนคอนโซลและสคริปต์/etc/init.d/rcS(ตำแหน่งเริ่มต้น) จะถูกดำเนินการในเวลาบูต

นั่นคือทั้งหมดที่คุณต้องการนอกเหนือจากโปรแกรมที่ทำให้อุปกรณ์ของคุณทำสิ่งที่มีประโยชน์ ตัวอย่างเช่นในเราเตอร์ที่บ้านของฉันใช้ตัวแปรOpenWrtโปรแกรมเดียวคือ BusyBox nvram(เพื่ออ่านและเปลี่ยนการตั้งค่าใน NVRAM) และระบบเครือข่าย

เว้นแต่ทั้งหมด executables ของคุณมีการเชื่อมโยงแบบคงที่คุณจะต้องโหลดแบบไดนามิก ( ld.soซึ่งอาจจะเรียกชื่อแตกต่างกันไปขึ้นอยู่กับทางเลือกของ libc และสถาปัตยกรรมหน่วยประมวลผล) และทั้งหมดที่ห้องสมุดแบบไดนามิก ( /lib/lib*.soบางทีบางส่วนของเหล่านี้/usr/lib) ที่จำเป็นโดย ไฟล์ปฏิบัติการเหล่านี้

โครงสร้างไดเรกทอรี

ระบบแฟ้มลำดับชั้นมาตรฐานอธิบายถึงโครงสร้างไดเรกทอรีทั่วไปของระบบ Linux มันมุ่งไปสู่การติดตั้งเดสก์ท็อปและเซิร์ฟเวอร์: จำนวนมากสามารถถูกละเว้นในระบบฝังตัว นี่คือขั้นต่ำทั่วไป

  • /bin: โปรแกรมปฏิบัติการ (บางโปรแกรมอาจใช้/usr/binแทน)
  • /dev: โหนดอุปกรณ์ (ดูด้านล่าง)
  • /etc: ไฟล์การกำหนดค่า
  • /lib: shared library รวมถึง dynamic loader (เว้นแต่ว่า executables ทั้งหมดจะถูกลิงค์แบบสแตติก)
  • /proc: จุดเมานท์สำหรับระบบไฟล์ proc
  • /sbin: โปรแกรมปฏิบัติการ ความแตกต่างที่มี/binก็คือว่า/sbinเป็นโปรแกรมที่มีประโยชน์เฉพาะกับผู้ดูแลระบบ แต่ความแตกต่างนี้ไม่ได้มีความหมายเกี่ยวกับอุปกรณ์ฝังตัว คุณสามารถสร้าง/sbinลิงค์สัญลักษณ์/binได้
  • /mnt: มีประโยชน์ที่จะมีในระบบไฟล์รูทแบบอ่านอย่างเดียวเป็นจุดต่อรอยขีดข่วนระหว่างการบำรุงรักษา
  • /sys: จุดเมานท์สำหรับระบบไฟล์ sysfs
  • /tmp: ตำแหน่งสำหรับไฟล์ชั่วคราว (มักจะtmpfsเมานท์)
  • /usr: มีไดเรกทอรีย่อยbin, และlib มีอยู่สำหรับไฟล์พิเศษที่ไม่ได้อยู่ในระบบไฟล์รูท หากคุณไม่มีสิ่งนั้นคุณสามารถสร้างลิงก์สัญลักษณ์ไปยังไดเร็กทอรีรูทได้sbin/usr/usr

ไฟล์อุปกรณ์

นี่คือรายการทั่วไปในขั้นต่ำ/dev:

  • console
  • full (เขียนถึงมันเสมอรายงาน“ ไม่มีพื้นที่เหลือบนอุปกรณ์”)
  • log(ซ็อกเก็ตที่โปรแกรมใช้เพื่อส่งรายการบันทึก) หากคุณมีsyslogdดีมอน (เช่น BusyBox) อ่านจากมัน
  • null (ทำหน้าที่เหมือนไฟล์ที่ยังว่างอยู่เสมอ)
  • ptmxและptsไดเรกทอรีถ้าคุณต้องการใช้เทอร์มินัลหลอก (เช่นเทอร์มินัลอื่นนอกเหนือจากคอนโซล) - เช่นหากอุปกรณ์มีเครือข่ายและคุณต้องการ telnet หรือ ssh ใน
  • random (ส่งกลับสุ่มไบต์ความเสี่ยงที่ถูกบล็อก)
  • tty (กำหนดเทอร์มินัลของโปรแกรมเสมอ)
  • urandom (ส่งกลับสุ่มไบต์ไม่เคยบล็อก แต่อาจไม่สุ่มบนอุปกรณ์ที่บูตใหม่)
  • zero (มีลำดับที่ไม่มีที่สิ้นสุดของไบต์เป็นโมฆะ)

นอกเหนือจากนั้นคุณจะต้องมีรายการสำหรับฮาร์ดแวร์ของคุณ (ยกเว้นอินเทอร์เฟซเครือข่ายสิ่งเหล่านี้ไม่ได้รับรายการ/dev): พอร์ตอนุกรมที่เก็บข้อมูล ฯลฯ

สำหรับอุปกรณ์ฝังตัวปกติแล้วคุณจะสร้างรายการอุปกรณ์โดยตรงบนระบบไฟล์รูท ระบบระดับสูงมีสคริปต์ที่เรียกว่าMAKEDEVสร้าง/devรายการ แต่ในระบบฝังตัวสคริปต์มักจะไม่รวมอยู่ในภาพ หากฮาร์ดแวร์บางตัวสามารถเสียบปลั๊กได้ (เช่นหากอุปกรณ์มีพอร์ตโฮสต์ USB) ดังนั้น/devควรจัดการโดยudev (คุณยังอาจตั้งค่าขั้นต่ำไว้ที่ระบบไฟล์รูท)

การดำเนินการบูตเวลา

นอกเหนือจากระบบไฟล์รูทแล้วคุณต้องติดตั้งอีกสองสามครั้งเพื่อการทำงานปกติ:

  • procfs on /proc(ที่ขาดไม่ได้เลยทีเดียว)
  • sysfs on /sys(ขาดไม่ได้เลยทีเดียว)
  • tmpfsระบบไฟล์บน/tmp(เพื่ออนุญาตให้โปรแกรมสร้างไฟล์ชั่วคราวที่จะอยู่ใน RAM แทนที่จะอยู่ในระบบไฟล์รูทซึ่งอาจเป็นแบบแฟลชหรืออ่านอย่างเดียว)
  • tmpfs, devfs หรือ devtmpfs เปิด/devถ้าไดนามิก (ดู udev ใน“ ไฟล์อุปกรณ์” ด้านบน)
  • devpts on /dev/ptsหากคุณต้องการใช้ [หลอกขั้ว (ดูหมายเหตุเกี่ยวกับptsด้านบน)

คุณสามารถสร้าง/etc/fstabไฟล์และโทรmount -aหรือเรียกใช้mountด้วยตนเอง

เริ่มsyslog daemon (เช่นเดียวกับklogdบันทึกเคอร์เนลหากsyslogdโปรแกรมไม่ได้ดูแลมัน) ถ้าคุณมีที่ที่จะเขียนล็อก

หลังจากนี้อุปกรณ์พร้อมที่จะเริ่มบริการเฉพาะแอปพลิเคชัน

วิธีสร้างระบบไฟล์รูท

นี่คือเรื่องราวที่ยาวนานและมีความหลากหลายดังนั้นสิ่งที่ฉันจะทำที่นี่คือให้คำแนะนำไม่กี่

ระบบไฟล์รูทอาจถูกเก็บไว้ใน RAM (โหลดจากภาพ (มักบีบอัด) ใน ROM หรือแฟลช) หรือบนระบบไฟล์ที่ใช้ดิสก์ (เก็บไว้ใน ROM หรือแฟลช) หรือโหลดจากเครือข่าย (มักจะมากกว่าTFTP ) . หากระบบไฟล์รูทอยู่ใน RAM ให้สร้างเป็นinitramfsซึ่งเป็นระบบไฟล์ RAM ที่มีการสร้างเนื้อหาในเวลาบูต

มีเฟรมเวิร์กมากมายสำหรับการรวมอิมเมจรูทสำหรับระบบฝังตัว มีไม่กี่ตัวชี้ในมีBusyBox คำถามที่พบบ่อย Buildrootเป็นรูปแบบที่ได้รับความนิยมทำให้คุณสามารถสร้างอิมเมจรูททั้งหมดด้วยการตั้งค่าคล้ายกับเคอร์เนล Linux และ BusyBox OpenEmbeddedเป็นอีกหนึ่งเฟรมเวิร์กดังกล่าว

วิกิพีเดียมี (สมบูรณ์) รายการที่นิยมลินุกซ์ฝังตัว ตัวอย่างของลินุกซ์ในตัวที่คุณอาจมีอยู่ใกล้ ๆ คุณคือตระกูลของระบบปฏิบัติการOpenWrtสำหรับอุปกรณ์เครือข่าย (เป็นที่นิยมในเราเตอร์ที่บ้านของ tinkerers) หากคุณต้องการเรียนรู้จากประสบการณ์คุณสามารถลองใช้Linux ตั้งแต่เริ่มต้นแต่จะมุ่งสู่ระบบเดสก์ท็อปสำหรับมือสมัครเล่นมากกว่าไปยังอุปกรณ์ฝังตัว

หมายเหตุเกี่ยวกับ Linux vs Linux kernel

พฤติกรรมเดียวที่ถูกนำเข้าสู่ลินุกซ์เคอร์เนลคือโปรแกรมแรกที่เปิดตัวในเวลาบูต (ฉันจะไม่ได้เป็นinitrdและinitramfsรายละเอียดปลีกย่อยที่นี่.) โปรแกรมนี้เรียกว่าประเพณีinitมีกระบวนการ ID ที่ 1 และมีสิทธิพิเศษบางอย่าง (ภูมิคุ้มกันต่อสัญญาณฆ่า ) และความรับผิดชอบ (เก็บเกี่ยวเด็กกำพร้า ) คุณสามารถเรียกใช้ระบบด้วยเคอร์เนล Linux และเริ่มต้นสิ่งที่คุณต้องการเป็นกระบวนการแรก แต่สิ่งที่คุณมีคือระบบปฏิบัติการที่ใช้เคอร์เนล Linux และไม่ใช่สิ่งที่ปกติเรียกว่า“ Linux” -  Linuxในแง่สามัญสำนึก คำนี้เป็นระบบปฏิบัติการ Unixที่มีเคอร์เนลเป็นเคอร์เนล Linux. ตัวอย่างเช่น Android เป็นระบบปฏิบัติการที่ไม่เหมือน Unix แต่ใช้ Linux kernel


คำตอบที่ยอดเยี่ยม ฉันเพียงกล่าวถึงการบูทเข้าสู่ลีนุกซ์ในชื่อ b / c ซึ่งเป็นสิ่งที่มีแนวโน้มว่าจะถูกค้นหา, นอกจากนี้ยอดเยี่ยมเกี่ยวกับลินุกซ์ vs Linux Kernel, ที่ต้องการความรู้ที่แพร่หลายมากขึ้น.
MDMoore313

@BigHomie โปรดจำไว้ว่ามูลนิธิซอฟต์แวร์เสรีต้องการให้เราทุกคนเรียกมันว่า GNU / Linux เนื่องจากส่วนใหญ่ (ทั้งหมด) ซอฟต์แวร์ "Linux distros" คือ GNU แม้ว่าเคอร์เนลคือ Linux (ดังนั้น GNU / Linux)
BenjiWiebe

Meh ไม่มีใครไม่มีเวลาสำหรับเรื่องนั้น ถ้าอย่างนั้น distro ของฉันควรจะเรียกว่า Busybox / Linux? ฉันรู้ว่าฉันรู้ว่าไม่ใช่คุณ Stallworth เพียงระบาย;)
MDMoore313

1
@BenjiWiebe หรือGNU / X11 / Apache / Linux / เท็กซ์ / Perl / หลาม / FreeCiv นอกเหนือจาก RMS แล้วทุกคนเรียกมันว่า "Linux"
Gilles 'หยุดความชั่วร้าย'

@Gilles ดีนอกเหนือจาก Debian ฉันเดา :)
CVn

5

สิ่งที่คุณต้องมีก็คือไฟล์ที่เชื่อมโยงกับระบบปฏิบัติการแบบสแตติกซึ่งอยู่ในระบบไฟล์ คุณไม่ต้องการไฟล์อื่นใด ไฟล์เรียกทำงานนั้นเป็นกระบวนการเริ่มต้น มันอาจเป็นช่องว่าง ที่ให้เปลือกและโฮสต์ของยูทิลิตี้อื่น ๆ ทั้งหมดในตัวมันเอง คุณสามารถไปที่ระบบที่ใช้งานได้อย่างสมบูรณ์โดยเพียงแค่ดำเนินการคำสั่งด้วยตนเองใน busybox เพื่อติดตั้งระบบไฟล์รากอ่าน - เขียนสร้าง / dev โหนด, exec จริง init ฯลฯ


ใช่ฉันรู้ว่าช่องว่างคือ comin ' ให้ดูว่ามีอะไรปรากฏขึ้นหรือไม่
MDMoore313

4

หากคุณไม่ต้องการเชลล์ยูทิลิตี้ใด ๆmkshไบนารีที่เชื่อมโยงแบบคงที่(เช่นเทียบกับ klibc - 130K บน Linux / i386) จะทำ คุณจำเป็นต้องมี/linuxrcหรือ/initหรือ/sbin/initสคริปต์ที่โทรเพียงmksh -l -T!/dev/tty1ในวง:

#!/bin/mksh
while true; do
    /bin/mksh -l -T!/dev/tty1
done

-T!$ttyตัวเลือกเป็นนอกจากนี้ที่ผ่านมาเพื่อmkshที่จะบอกว่ามันจะวางไข่เปลือกใหม่ใน terminal ที่กำหนดและรอให้มัน (ก่อนหน้านั้นมีเพียง-T-การdæmonise programm และ-T$ttyจะวางไข่บนขั้ว แต่ไม่รอให้มัน. นี้ไม่ได้ดีดังนั้น.) The -lตัวเลือกเพียงแค่บอกว่ามันจะเรียกใช้เปลือกเข้าสู่ระบบ (ซึ่งอ่าน/etc/profile, ~/.profileและ~/.mkshrc)

สิ่งนี้ถือว่าเทอร์มินัลของคุณคือ/dev/tty1แทนที่ (ด้วยเวทย์มนตร์ที่มากขึ้นเทอร์มินัลจะถูกค้นพบโดยอัตโนมัติ/dev/consoleจะไม่ให้การควบคุมงานเต็มรูปแบบ)

คุณต้องใช้ไฟล์สองสามไฟล์/devเพื่อให้สิ่งนี้ทำงาน:

  • / dev / คอนโซล
  • / dev / null
  • / dev / TTY
  • / dev / tty1

การบูตด้วยตัวเลือกเคอร์เนลdevtmpfs.mount=1ช่วยลดความจำเป็นในการเติม/devให้เป็นไดเรกทอรีที่ว่างเปล่า (เหมาะสำหรับใช้เป็นจุดเมานท์)

โดยปกติคุณจะต้องมียูทิลิตี้บางอย่าง (จาก klibc, busybox, beastiebox, toybox หรือ toolbox) แต่พวกมันไม่จำเป็นจริงๆ

คุณอาจต้องการเพิ่ม~/.mkshrcไฟล์ซึ่งตั้งค่า $ PS1 และชื่อแทนเชลล์และฟังก์ชั่นพื้นฐานบางอย่าง

ฉันเคยทำการบีบอัด 171K (371K ที่ไม่บีบอัด) initrd สำหรับ Linux / m68k โดยใช้ mksh (และไฟล์ตัวอย่าง mkshrc) และ klibc-utils เท่านั้น (นี่คือก่อนหน้า -T! ถูกเพิ่มลงในเชลล์ดังนั้นจึงสร้างเชลล์ล็อกอินขึ้นมา/dev/tty2แทนและแสดงข้อความไปที่คอนโซลบอกให้ผู้ใช้สลับเทอร์มินัล) ทำงานได้ดี

นี่เป็นการตั้งค่าขั้นต่ำสุดๆ คำตอบอื่น ๆ ให้คำแนะนำที่ดีต่อระบบที่มีคุณลักษณะมากกว่านี้ นี่เป็นกรณีพิเศษจริง ๆ

ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้พัฒนา mksh


mkshนี่คือคำตอบที่ดีขอบคุณสำหรับการแบ่งปันและยังขอบคุณสำหรับ
JoshuaRLi

2

ขั้นตอนเริ่มต้นโปรแกรม Hello World ขั้นต่ำ

ป้อนคำอธิบายรูปภาพที่นี่

คอมไพล์สวัสดีชาวโลกโดยไม่ต้องพึ่งพาใด ๆ ที่สิ้นสุดในวงวนไม่สิ้นสุด init.S:

.global _start
_start:
    mov $1, %rax
    mov $1, %rdi
    mov $message, %rsi
    mov $message_len, %rdx
    syscall
    jmp .
    message: .ascii "FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR\n"
    .equ message_len, . - message

เราไม่สามารถใช้sys_exitหรืออื่น ๆ ที่ทำให้ตกใจเคอร์เนล

แล้ว:

mkdir d
as --64 -o init.o init.S
ld -o init d/init.o
cd d
find . | cpio -o -H newc | gzip > ../rootfs.cpio.gz
ROOTFS_PATH="$(pwd)/../rootfs.cpio.gz"

สิ่งนี้จะสร้างระบบไฟล์กับ hello world ของเรา/initซึ่งเป็นโปรแกรม userland แรกที่เคอร์เนลจะทำงาน เราสามารถเพิ่มไฟล์เพิ่มเติมd/และพวกเขาจะสามารถเข้าถึงได้จาก/initโปรแกรมเมื่อเคอร์เนลทำงาน

จากนั้นcdเข้าไปในเคอร์เนลลินุกซ์สร้างเป็นปกติและเรียกใช้ใน QEMU:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v4.9
make mrproper
make defconfig
make -j"$(nproc)"
qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd "$ROOTFS_PATH"

และคุณควรเห็นบรรทัด:

FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR

บนหน้าจออีมูเลเตอร์! โปรดทราบว่ามันไม่ใช่บรรทัดสุดท้ายดังนั้นคุณต้องค้นหาต่อไปอีกเล็กน้อย

คุณยังสามารถใช้โปรแกรม C หากคุณเชื่อมโยงโปรแกรมเหล่านี้แบบคงที่:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR\n");
    sleep(0xFFFFFFFF);
    return 0;
}

ด้วย:

gcc -static init.c -o init

คุณสามารถเรียกใช้บนฮาร์ดแวร์จริงด้วยการเปิด USB /dev/sdXและ:

make isoimage FDINITRD="$ROOTFS_PATH"
sudo dd if=arch/x86/boot/image.iso of=/dev/sdX

แหล่งที่ดีในเรื่องนี้: http://landley.net/writing/rootfs-howto.htmlนอกจากนี้ยังอธิบายถึงวิธีการใช้gen_initramfs_list.shซึ่งเป็นสคริปต์จากทรีของเคอร์เนลลินุกซ์เพื่อช่วยให้กระบวนการทำงานโดยอัตโนมัติ

ขั้นตอนถัดไป: ตั้งค่า BusyBox เพื่อให้คุณสามารถโต้ตอบกับระบบ: https://github.com/cirosantilli/runlinux

ทดสอบกับ Ubuntu 16.10, QEMU 2.6.1

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.