Process trong Linux
Linux
99
White

BeanRedArmy viết ngày 04/11/2018

A. Tổng quan tiến trình (Process) trong Linux

1. Tiến trình vs Chương trình (Process vs Program)

Chương trình (program) là một file chứa những thông tin, mô tả cách tạo thành một tiến trình (process) ở runtime. Những thông tin này bao gồm:

  • Binary format identification: Mô tả định dạng của file thực thi, cho phép kernel dịch những thông tin còn lại trong file. Trước đây, có 2 định dạng được sử dụng rộng rãi cho file thực thi UNIX là kiểu a.out ("assembler output"), và kiểu COFF (Common Object File Format). Ngày nay thì hầu hết các hệ UNIX ( gồm Linux) triển khai mô hình Executable and Linking Format (ELF).
  • Machine-language instructions: Tập lệnh mã máy.
  • Program entry-point address: Chỉ ra vị trí của lệnh chương trình bắt đầu chạy.
  • Data: Chứa những giá trị dùng cho khởi tạo biến, hằng cho chương trình.
  • Symbol and relocation tables: Mô tả các vị trí, tên của hàm và biến trong chương trình. Những bảng ngày được sử dụng cho nhiều mục đích, bao gồm dynamic linking, debug...
  • Shared-library and dynamic-linking information : Danh sách cách shared library mà chương trình cần để chạy và đường dẫn của dynamic linker sử dụng để load những thư viện này.
  • Other information: Những thông tin khác...

Một program có thể sử dụng để tạo thành nhiều process nhưng những process được tạo này chỉ chạy một chương trình giống nhau.

Có thể nói rằng một tiến trình (process) là một thực thể trừu tượng, được định nghĩa bởi kernel, được kernel cấp tài nguyên để thực thi chương trình (program).

2. Process ID và Parent Process ID

Mỗi tiến trình có một ID (PID), đó là một số nguyên dương, dùng để định danh duy nhất tiến trình đó trong hệ thống. Process ID được sử dụng trong rất nhiều các system call. Ví dụ system call kill() cho phép một process gửi tín hiệu tới một process khác thông qua PID của process nhận. PID cũng rất hữu ích khi ta cần định danh cho process, ví dụ như dùng PID để tạo tên cho file...
System call getpid() sẽ trả về PID của process gọi nó:

#include <unistd.h>
pid_t getpid(void);

Hàm này luôn trả về thành công.
Ngoại trừ process init luôn có PID là 1, PID của một process được tạo ra hầu như không liên quan gì đến program (chương trình) tạo ra process đó.

Linux kernel cho phép PID nhỏ hơn hoặc bằng 32,767 (hệ 32bit). Khi một process mới được tạo thì nó được cấp PID một cách tuần tự nếu số PID còn available. Khi có một PID chạm tới ngưỡng 32,767 thì kernel sẽ reset giá trị cấp tiếp theo bắt đầu từ 300.

Giá trị 32,767 này ở từ Kernel 2.6 có thể thay đổi trong file /proc/sys/kernel/pid_max.

Mỗi process luôn có cha của nó. Ta có thể lấy được PID của process cha bằng systemcall:

#include <unistd.h>
pid_t getppid(void);

3. Bố trí bộ nhớ của Process

Vùng nhớ được cấp phát cho process gồm các phân vùng (segment):

  • Text segment: Bao gồm lệnh ngôn những máy của chương trình mà process chạy. Phân vùng này là read-only để không bị vô tình thay đổi bằng con trỏ khi lập trình. Do nhiều process có thể chạy cùng một chương trình, phân vùng này có thể chia sẻ để chỉ cần một bản copy code chương trình có thể được map tới không gian địa chỉ ảo của nhiều process khác nhau.
  • Initialized data segment: gồm các biến toàn cục và biến tĩnh được khai báo tường minh. Những giá trị này được đọc từ file thực thi khi chương trình được load vào RAM.
  • Uninitialized data segment: gồm những biến toàn cục và biến tĩnh không được khai báo tường minh. Trước khi chạy chương tình thì hệ thống sẽ đặt toàn bộ vùng nhớ này về giá trị 0.
  • Stack: là phân vùng tự động tăng kích thước. Phân vùng này bao gồm các stack frame. Mỗi stack frame được cấp phát cho mỗi function được gọi, và nó lưu trữ những biến cục bộ của function, các argument và giá trị trả về.
  • Heap: là vùng nhớ dùng cho việc cấp phát động ở runtime.

Ví dụ các phân vùng bộ nhớ của process:

#include <stdio.h>
#include <stdlib.h>
char globBuf[65536]; /* Uninitialized data segment */
int primes[] = { 2, 3, 5, 7 }; /* Initialized data segment */

static int square(int x) /* Allocated in frame for square() */
{
    int result; /* Allocated in frame for square() */
    result = x * x;
    return result; /* Return value passed via register */
}

static void doCalc(int val) /* Allocated in frame for doCalc() */
{
    printf("The square of %d is %d\n", val, square(val));
    if (val < 1000) {
        int t; /* Allocated in frame for doCalc() */
        t = val * val * val;
        printf("The cube of %d is %d\n", val, t);
    }
}

int main(int argc, char *argv[]) /* Allocated in frame for main() */
{
    static int key = 9973; /* Initialized data segment */
    static char mbuf[10240000]; /* Uninitialized data segment */
    char *p; /* Allocated in frame for main() */
    p = malloc(1024); /* Points to memory in heap segment */
    doCalc(key);
    exit(EXIT_SUCCESS);
}

Những comment trong code trên đang giả sử là compiler không tối ưu code.
alt text

Ta có thể lấy địa chỉ của một số các phân vùng bằng các biến etext, edata, end:

extern char etext, edata, end;
/* For example, &etext gives the address of the end
of the program text / start of initialized data */

4. Quản lý bộ nhớ ảo

Giống hầu hết các hệ điều hành tiên tiến, Linux triển khai kĩ thuật gọi là virtual memory management.

Bộ nhớ ảo chia vùng nhớ của chương trình thành các đơn vị nhớ có kích thước nhỏ và cố định, gọi là trang nhớ (page). RAM sẽ được chia thành một chuỗi các khung trang (page frame) có cùng kích thước. Ở một thời điểm, chỉ có một vài trang nhớ của chương trình cần được đưa lên khung trang của bộ nhớ vật lý. Các trang nhớ không được sử dụng của chương trình được duy trì trong một vùng swap nằm trên ổ đĩa. Khi một tiến trình tham chiếu đến một trang nhớ không có trên địa chỉ nhớ vật lý, lỗi trang xảy ra và kernel sẽ dừng việc thực thi của tiến trình lại đồng thời load trang nhớ từ ổ đĩa lên bộ nhớ vật lý.
alt text

Để hỗ trợ thì kernel duy trì một bảng trang (page table) cho mỗi process. Bảng trang mô tả địa chỉ của mỗi trang trong không gian địa chỉ ảo của process. Thông thường có một số lượng các không gian địa chỉ ảo không được sử dụng nên những không gian này không cần thiết phải có một chỉ mục ở page table. Khi truy cập đến địa chỉ mà không có chỉ mục thì process sẽ nhận SIGSEGV signal.

Việc tách bạch không gian địa chỉ ảo của process khỏi không gian địa chỉ vật lý của RAM có nhiều ưu điểm:

  • Các process hoạt động độc lập với nhau và với kernel, một process không thể đọc cũng nhớ chỉnh sửa vùng nhớ của một process khác.
  • Hai hay nhiều process có thể chia sẻ vùng nhớ. Kernel sẽ cung cấp các chỉ mục ở bảng trang sao cho các process khác nhau sẽ có thể trỏ đến cùng một một trang nhớ trên RAM. Chia sẻ bộ nhớ có thể xảy ra khi:
    • Nhiều process thực thi cùng một chương trình có thể chia sẻ một bản copy code. Kiểu chia sẻ này luôn xảy ra khi ta thực thi nhiều process từ một program file.
    • Các process sử dụng shmget() hoặc mmap() để yêu cầu chia sẻ vùng nhớ với các process khác, với mục đích giao tiếp liên tiến trình.
  • Người lập trình và một số công cụ khác như trình biên dịch hay linker không cần phải quan tâm để việc bố trí vật lý của chương trình lên RAM.
  • Do chỉ có 1 phần của chương trình cần phải ở trên RAM nên chương trình có thể load và chạy nhanh hơn. Hơn thế nữa có thể giảm tải trên RAM do kích thước chiếm dụng của chương trình (virtual size) có thể vượt quá dung lượng RAM. Việc giảm tải đồng thời có thể giúp ta chạy nhiều tiến trình cùng lúc trên RAM.

5. Stack và Stack Frame

Với Linux kiến trúc x86-32 (và trên hầu hết các biến thể UNIX) thì stack được lưu trên RAM ở high end, tức là giảm địa chỉ đỉnh stack để tăng kích thước stack. Con trỏ stack sẽ giữ địa chỉ của đỉnh stack. Mỗi lần một hàm được gọi, sẽ có thêm một frame được cấp phát trên stack, và mỗi lần hàm return thì frame này sẽ được xóa.

Thuật ngữ user stack được sử dụng để phân biệt stack mà ta mô tả với kernel stack. Kernel stack là vùng nhớ được điều hành bởi kernel và được sử dụng như ngăn xếp của system call. Mỗi user stack frame bao gồm một số thông tin như: Tham số của hàm và biến cục bộ, thông tin về call linkage ( Mỗi hàm sử dụng một số thanh ghi CPU nhất định, ví dụ như program counter. Mỗi lần hàm này gọi hàm khác thì những thanh ghi này được lưu vào stack frame của hàm được gọi để phục vụ công tác trả về cho hàm gọi).
alt text

6. Command-line Argument (argc, argv)

Mỗi chương trình C đều phải có hàm main(), là hàm bắt đầu việc thực thi của chương trình. Hàm main nhận hai tham số khi bắt đầu thực thi đó là int argc (số tham số được truyền vào) và char *argv[] (mảng các tham số đó).
alt text

7. Danh sách biến môi trường

Mỗi process được liên kết với một danh sách các chuỗi gọi là environment list, hoặc đơn giản là environment, Mỗi chuỗi là một định nghĩa có dạng name=value.

Khi một process được tạo ra, nó thừa kế một bản copy những biến môi trường của process cha. Do đó có vẫn thường được sử dụng để giao tiếp giữa các process (cha con) mặc dù hơi cách hay hơi cổ và việc truyền dữ liệu diễn ra chỉ một chiều từ cha đến con. Sau khi process con được tạo và nhận những biến môi trường từ process cha, nó có thể thay đổi giá trị các biến môi trường này, những sự thay đổi này thì các process khác không được biết.
Một cách sử dụng khác của biến môi trường là trong shell. Bằng cách đặt các giá trị trong môi trường của chúng, shell có thể đảm bảo rằng những giá trị này được truyền vào process mà shell tạo ra để thực thi user command.
Ở hầu hết các shell thì có thể đặt giá trị của biến môi trường bằng lệnh export:

$ SHELL=/bin/bash
$ export SHELL

Ta có thể dùng lệnh printenv để hiện thị danh sách biến môi trường hiện tại,

Truy cập biến môi trường trong chương trình

Ta có thể truy cập vào danh sách biến môi trường bằng cách sử dụng biến toàn cục char **environ.
alt text
Ví dụ:

#include "tlpi_hdr.h"
extern char **environ;
int main(int argc, char *argv[])
{
    char **ep;
    for (ep = environ; *ep != NULL; ep++)
        puts(*ep);
    exit(EXIT_SUCCESS);
}

Một cách khác là khai báo hàm hàm main() có tham số thứ ba:

int main(int argc, char *argv[], char *envp[])

Tuy nhiên vì việc bị giới hạn ở hàm main(), ta nên sử dụng hàm:

#include <stdlib.h>
char *getenv(const char * name );

Hàm sẽ trả về con trỏ chuỗi của biến có tên là name. Ví dụ nó sẽ trả về là con trỏ đến chuỗi /bin/bash nếu truyền vào name giá trị là SHELL.

Chỉnh sửa biến môi trường

  • Hàm putenv() sẽ thêm một biến môi trường mới cho process hoặc chỉnh sửa một biến có sẵn
#include <stdlib.h>
int putenv(char * string );

trong đo string là là con trỏ đến chuỗi có dạng name=value. Hàm trả về 0 nếu thành công, khác 0 nếu lỗi.

  • Hàm setenv() có thể hay thế hàm putenv():
#include <stdlib.h>
int setenv(const char * name , const char * value , int overwrite );

Hàm trả về 0 nếu thành công, trả về -1 nếu lỗi.

  • Hàm unsetenv() xóa biến có tên là name khỏi môi trường của process:
#include <stdlib.h>
int unsetenv(const char * name );

Hàm trả về 0 nếu thành công, trả về -1 nếu lỗi.

  • Xóa toàn bộ biến môi trường bằng clearenv() :
#define _BSD_SOURCE /* Or: #define _SVID_SOURCE */
#include <stdlib.h>
int clearenv(void)

Hàm trả về 0 nếu thành công, trả về khác 0 nếu lỗi.

BeanRedArmy 30-10-2018

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

BeanRedArmy

11 bài viết.
24 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
7 2
Device Tree trong Linux Device Tree (DT) là một file mô tả phần cứng, có kiểu định dạng giống JSON, nó mô tả một cấu trúc cây, ở đó thì các device...
BeanRedArmy viết 9 tháng trước
7 2
White
4 0
Context trong Linux Process context, interrupt context, user(space) context, system call context, atomic context, nonatomic context,... là những k...
BeanRedArmy viết 11 tháng trước
4 0
White
4 0
Reentrant và Threadsafe Một chương trình máy tính, một hàm A hoặc một chương trình con được gọi là reentrant nếu nó có thể bị ngắt lúc đang thực t...
BeanRedArmy viết 10 tháng trước
4 0
Bài viết liên quan
White
1 0
sudo du sh
t viết hơn 3 năm trước
1 0
White
35 10
Thời kỳ mới đi làm tôi nghĩ cứ phải gõ thật nhiều cho quen cho nhớ nhưng lâu dần việc đó cho cảm giác thật nhàm chán. Hiện giờ, những gì tôi hay là...
manhdung viết hơn 4 năm trước
35 10
White
1 0
Sử dụng option I với xargs Với option I thì bạn có thể sử dụng place holder với biến được lấy ra từ xargs man của option này: I replacestr R...
LinhPT viết hơn 3 năm trước
1 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
11 bài viết.
24 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!