Kiểm soát tiến trình Linux: Tạo tiến trình, Kết thúc, Chờ đợi, Thay thế chương trình và Shell đơn giản

Tạo tiến trình

Hàm fork cơ bản

Trong Linux, hàm fork là một hàm cực kỳ quan trọng, nó tạo ra một tiến trình mới từ một tiến trình đã tồn tại. Tiến trình mới được gọi là tiến trình con, trong khi tiến trình ban đầu là tiến trình cha.

#include <unistd.h>
pid_t fork(void);
// Trả về: trong tiến trình con trả về 0, trong tiến trình cha trả về PID của tiến trình con, lỗi trả về -1

Khi một tiến trình gọi fork, khi quyền điều khiển chuyển đến mã fork trong kernel, kernel sẽ:

  • Cấp phát bộ nhớ và cấu trúc dữ liệu kernel mới cho tiến trình con
  • Sao chép một phần cấu trúc dữ liệu của tiến trình cha sang tiến trình con
  • Thêm tiến trình con vào danh sách tiến trình của hệ thống
  • fork trả về, bộ lập lịch bắt đầu lên lịch

Khi một tiến trình gọi fork, sẽ có hai tiến trình có mã nhị phân giống hệt nhau. Và chúng đều chạy đến cùng một vị trí. Nhưng mỗi tiến trình có thể bắt đầu hành trình riêng của mình. Xem ví dụ sau:

int main( void )
{
    pid_t pid;
    printf("Trước: pid là %d\n", getpid());

    if ( (pid=fork()) == -1 ) perror("fork()"),exit(1);
    printf("Sau:pid là %d, fork trả về %d\n", getpid(), pid);
    sleep(1);

    return 0;
}

Kết quả chạy:

[root@localhost linux]# ./a.out
Trước: pid là 43676
Sau:pid là 43676, fork trả về 43677
Sau:pid là 43677, fork trả về 0
Ở đây ta thấy ba dòng đầu ra, một dòng "trước", hai dòng "sau". Tiến trình 43676 in ra thông báo "trước" trước, sau đó nó in ra "sau". Thông báo "sau" khác được in bởi 43677. Lưu ý rằng tiến trình 43677 không in ra "trước", tại sao? Như hình dưới đây Vì vậy, trước khi fork, tiến trình cha thực thi độc lập, sau fork, hai luồng thực thi cha-con thực thi riêng biệt. Lưu ý rằng sau fork, tiến trình nào thực thi trước hoàn toàn do bộ lập lịch quyết định.

Giá trị trả về của hàm fork

  • Tiến trình con trả về 0
  • Tiến trình cha trả về PID của tiến trình con

Sao chép khi ghi (Copy-on-Write)

Thông thường, mã của cha-con được chia sẻ, dữ liệu cũng được chia sẻ nếu không có ghi, khi một bên cố gắng ghi, nó sẽ tạo một bản sao riêng cho mỗi bên theo cơ chế sao chép khi ghi. Chi tiết như hình dưới:

Thực thi gặp vấn đề
Chuỗi hằng số có tính chất hằng, không thể sửa đổi dữ liệu hằng số tương ứng trong vùng hằng số
hello Linux lưu trữ địa chỉ bắt đầu của chuỗi, đây là địa chỉ ảo, khi giải tham chiếu con trỏ để sửa đổi và ghi, cần phải chuyển đổi từ ảo sang địa chỉ vật lý, mục ánh xạ trong bảng trang được đặt là chỉ đọc, bảng trang không cho phép ánh xạ, do đó ghi thất bại
Về mặt hệ điều hành không cho phép sửa đổi, không liên quan đến ngôn ngữ

Cách sử dụng fork thông thường

  • Một tiến trình cha muốn sao chép chính nó, để tiến trình cha-con cùng thực thi các đoạn mã khác nhau. Ví dụ, tiến trình cha chờ yêu cầu từ client, tạo tiến trình con để xử lý yêu cầu.
  • Một tiến trình cần thực thi một chương trình khác. Ví dụ, tiến trình con trả về từ fork, sau đó gọi hàm exec.

Lý do fork gọi thất bại

  • Hệ thống có quá nhiều tiến trình
  • Số tiến trình của người dùng thực tế vượt quá giới hạn

Kết thúc tiến trình

Giá trị trả về của hàm main chính là mã thoát của tiến trình, dùng để chỉ tình trạng thực thi của tiến trình hiện tại
0 biểu thị thành công, khác 0 biểu thị thất bại
Các hàm khác thoát, chỉ biểu thị việc gọi hàm đã hoàn tất

echo $?

Bash nội bộ sẽ ghi lại mã thoát khi tiến trình thực thi gần nhất hoàn tất

Tùy chỉnh

Các hàm khác thoát, chỉ biểu thị việc gọi hàm đã hoàn tất, printf sau đó cũng được thực thi

errno: Ngoài việc tiến trình thoát, hàm thoát, biết tình trạng thực thi của hàm qua giá trị trả về của hàm
Gọi hàm thường thấy hai kết quả

  1. Kết quả thực thi của hàm–FILE\*, NULL
  2. Tình trạng thực thi của hàm–thành công, thất bại, lý do gì

Hàm có mã lỗi

Tình huống kết thúc tiến trình

  • Mã chạy xong, kết quả đúng
  • Mã chạy xong, kết quả sai
  • Mã kết thúc bất thường
    Bản chất của việc tiến trình bất thường là tiến trình nhận được tín hiệu bất thường, mỗi tín hiệu có số hiệu khác nhau, số hiệu tín hiệu khác nhau biểu thị lý do bất thường khác nhau

Bất kỳ tiến trình nào cuối cùng cũng có thể sử dụng hai số để chỉ rõ tình trạng thực thi cụ thể

  1. Số hiệu tín hiệu signumber
  2. Mã thoát tiến trình exit_code

Cách kết thúc tiến trình phổ biến

Kết thúc bình thường (có thể xem mã thoát của tiến trình qua echo $?):

  1. Từ main trả về
  2. Gọi exit

exit dùng để kết thúc tiến trình, exit(đkod thoát)
Trong mã tiến trình, gọi exit ở bất kỳ đâu đều biểu thị tiến trình thoát
3. _exit

Kết thúc bất thường:

  • ctrl + c, kết thúc bằng tín hiệu

Hàm _exit

#include <unistd.h>
void _exit(int status);
// Tham số: status định nghĩa trạng thái kết thúc của tiến trình, tiến trình cha lấy giá trị này qua wait

Giải thích: Mặc dù status là int, nhưng chỉ có 8 bit thấp có thể được sử dụng bởi tiến trình cha. Vì vậy, _exit(-1) khi thực thi trên terminal $?, ta thấy giá trị trả về là 255.

Hàm exit

#include <unistd.h>
void exit(int status);

exit cuối cùng cũng sẽ gọi _exit, nhưng trước khi gọi _exit, nó còn thực hiện các công việc khác:

  1. Thực thi các hàm dọn dẹp do người dùng định nghĩa qua atexit hoặc on_exit.
  2. Đóng tất cả các luồng đã mở, tất cả dữ liệu đệm đều được ghi
  3. Gọi _exit
int main()
{
    printf("hello");
    exit(0);
}

Kết quả chạy:

[root@localhost linux]# ./a.out
hello[root@localhost linux]#

int main()
{
    printf("hello");
    _exit(0);
}

Kết quả chạy:

[root@localhost linux]# ./a.out
[root@localhost linux]#
exit hỗ trợ làm mới bộ đệm, _exit không hỗ trợ

Kết thúc bằng return

return là một cách kết thúc tiến trình phổ biến hơn. Thực thi return n tương đương với thực thi exit(n), vì hàm thời gian chạy gọi main sẽ coi giá trị trả về của main là tham số của exit.

Chờ đợi tiến trình

Tính cần thiết của việc chờ đợi tiến trình

  • Tiến trình con thoát, nếu tiến trình cha không quan tâm, có thể gây ra vấn đề "tiến trình ma", từ đó gây rò rỉ bộ nhớ.
  • Bên cạnh đó, một khi tiến trình trở thành trạng thái ma, nó sẽ "bất khả xâm phạm", "giết người không chớp mắt" của kill -9 cũng không thể làm gì được, vì không ai có thể giết một tiến trình đã chết.
  • Cuối cùng, chúng ta cần biết kết quả hoàn thành nhiệm vụ mà tiến trình cha giao cho tiến trình con. Ví dụ, tiến trình con chạy xong, kết quả đúng hay sai, hoặc có thoát bình thường không.
  • Tiến trình cha thu hồi tài nguyên tiến trình con và lấy thông tin thoát của tiến trình con thông qua cách chờ đợi tiến trình

Cách chờ đợi tiến trình

Phương thức wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

Trả về:
Thành công trả về PID của tiến trình con được chờ, thất bại trả về -1.
Tham số:
Tham số đầu ra, lấy trạng thái thoát của tiến trình con, không quan tâm thì có thể đặt thành NULL

Phương thức waitpid

pid_t waitpid(pid_t pid, int *status, int options);

Trả về:
Khi trả về bình thường, waitpid trả về PID của tiến trình con đã thu thập;
Nếu đặt tùy chọn WNOHANG và trong cuộc gọi waitpid, phát hiện không có tiến trình con đã thoát để thu thập, thì trả về 0;
Nếu có lỗi trong cuộc gọi, trả về -1, lúc này errno sẽ được đặt thành giá trị tương ứng để chỉ ra lỗi;
Tham số:
pid:
Pid=-1, chờ bất kỳ tiến trình con nào. Tương đương với wait.
Pid>0. Chờ tiến trình có PID bằng pid.
status:
WIFEXITED(status): Nếu là trạng thái thoát bình thường của tiến trình con, thì là true. (Kiểm tra tiến trình có thoát bình thường không)
WEXITSTATUS(status): Nếu WIFEXITED khác 0, trích xuất mã thoát của tiến trình con. (Kiểm tra mã thoát của tiến trình)
options:
WNOHANG: Nếu tiến trình con được chỉ định bởi pid chưa kết thúc, thì hàm waitpid() trả về 0, không chờ đợi. Nếu kết thúc bình thường, trả về PID của tiến trình con.
  • Nếu tiến trình con đã thoát, khi gọi wait/waitpid, wait/waitpid sẽ trả về ngay lập tức, giải phóng tài nguyên và lấy thông tin thoát của tiến trình con.
  • Nếu tại bất kỳ thời điểm nào gọi wait/waitpid, tiến trình con tồn tại và đang chạy bình thường, tiến trình có thể bị chặn.
  • Nếu không tồn tại tiến trình con đó, trả về ngay lập tức với lỗi.

Lấy trạng thái status của tiến trình con

  • wait và waitpid, đều có một tham số status, đây là tham số đầu ra, được hệ điều hành điền vào.
  • Nếu truyền NULL, biểu thị không quan tâm thông tin thoát của tiến trình con.
  • Nếu không, hệ điều hành sẽ dựa trên tham số này, phản hồi thông tin thoát của tiến trình con cho tiến trình cha.
  • status không thể coi đơn giản là số nguyên, có thể coi là bản đồ bit, chi tiết như hình dưới (chỉ nghiên cứu 16 bit thấp của status):
    status trong hàm là số nguyên, trả về hai thông tin, mã thoát và tín hiệu thoát, số nguyên này được sử dụng cục bộ theo cách tương tự bản đồ bit, status có định dạng riêng của nó
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{
    pid_t pid;
    if ( (pid=fork()) == -1 )
        perror("fork"),exit(1);
    if ( pid == 0 ){
        sleep(20);
        exit(10);
    } else {
        int st;
        int ret = wait(&st);
        if ( ret > 0 && ( st & 0X7F ) == 0 ){ // Thoát bình thường
            printf("mã thoát của tiến trình con:%d\n", (st>>8)&0XFF);
        } else if( ret > 0 ) { // Thoát bất thường
            printf("mã tín hiệu : %d\n", st&0X7F );
        }
    }
}

Kết quả chạy:

[root@localhost linux]# ./a.out #chờ 20 giây thoát
mã thoát của tiến trình con:10
[root@localhost linux]# ./a.out #kill trong terminal khác
mã tín hiệu : 9
Thêm lỗi chia cho 0
Thêm con trỏ hoang dã
Xem status qua macro

Cài đặt mã cụ thể

  • Cách chờ đợi chặn của tiến trình:
int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        printf("%s fork error\n",__FUNCTION__);
        return 1;
    } else if( pid == 0 ){ //child
        printf("tiến trình con đang chạy, pid là : %d\n",getpid());
        sleep(5);
        exit(257);
    } else{
        int status = 0;
        pid_t ret = waitpid(-1, &status, 0);//chờ chặn, chờ 5S
        printf("đây là bài kiểm tra cho wait\n");
        if( WIFEXITED(status) && ret == pid ){
            printf("chờ tiến trình con 5s thành công, mã trả về của tiến trình con là :%d.\n",WEXITSTATUS(status));
        }else{
            printf("chờ tiến trình con thất bại, trả về.\n");
            return 1;
        }
    }
    return 0;
}

Kết quả chạy:

[root@localhost linux]# ./a.out
tiến trình con đang chạy, pid là : 45110
đây là bài kiểm tra cho wait
chờ tiến trình con 5s thành công, mã trả về của tiến trình con là :1.

  • Cách chờ đợi không chặn của tiến trình:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        printf("%s fork error\n",__FUNCTION__);
        return 1;
    }else if( pid == 0 ){ //child
        printf("tiến trình con đang chạy, pid là : %d\n",getpid());
        sleep(5);
        exit(1);
    } else{
        int status = 0;
        pid_t ret = 0;
        do
        {
            ret = waitpid(-1, &status, WNOHANG);//chờ không chặn
            if( ret == 0 ){
                printf("tiến trình con đang chạy\n");
            }
            sleep(1);
        }while(ret == 0);
        if( WIFEXITED(status) && ret == pid ){
            printf("chờ tiến trình con 5s thành công, mã trả về của tiến trình con là :%d.\n",WEXITSTATUS(status));
        }else{
            printf("chờ tiến trình con thất bại, trả về.\n");
        return 1;
        }
    }
    return 0;
}

Thay thế chương trình tiến trình

Nguyên lý thay thế

Sau khi tạo tiến trình con bằng fork, tiến trình con thực thi cùng chương trình với tiến trình cha (nhưng có thể thực thi các nhánh mã khác nhau), tiến trình con thường cần gọi một hàm exec để thực thi một chương trình khác. Khi một tiến trình gọi một hàm exec, không gian mã và dữ liệu của tiến trình đó bị thay thế hoàn toàn bằng chương trình mới, bắt đầu thực thi từ hàm khởi động của chương trình mới. Gọi exec không tạo tiến trình mới, vì vậy PID của tiến trình không thay đổi trước và sau khi gọi exec.

Hàm thay thế

Thực tế có sáu hàm bắt đầu bằng exec, gọi chung là hàm exec:

#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

Giải thích hàm

  • Nếu các hàm này gọi thành công, chúng sẽ tải chương trình mới và bắt đầu thực thi từ hàm khởi động, không bao giờ trả về.
  • Nếu gọi lỗi thì trả về -1
  • Vì vậy, hàm exec chỉ có giá trị trả về khi lỗi, không có giá trị trả về khi thành công.

Hiểu tên

Các nguyên mẫu hàm này trông rất dễ nhầm lẫn, nhưng chỉ cần nắm được quy luật thì rất dễ nhớ.

  • l(list) : Biểu thị tham số dùng danh sách
  • v(vector) : Tham số dùng mảng
  • p(path) : Có p tự động tìm kiếm biến môi trường PATH
  • e(env) : Biểu thị tự quản lý biến môi trường
Tên hàm Định dạng tham số Có đường dẫn không Có sử dụng biến môi trường hiện tại không
execl Danh sách Không
execlp Danh sách
execle Danh sách Không Không, phải tự lắp ráp biến môi trường
execv Mảng Không
execvp Mảng
execve Mảng Không Không, phải tự lắp ráp biến môi trường

Ví dụ gọi exec:

#include <unistd.h>
int main()
{
    char *const argv[] = {"ps", "-ef", NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};

    execl("/bin/ps", "ps", "-ef", NULL);

    // Có p, có thể sử dụng biến môi trường PATH, không cần viết đường dẫn đầy đủ
    execlp("ps", "ps", "-ef", NULL);

    // Có e, cần tự lắp ráp biến môi trường
    execle("ps", "ps", "-ef", NULL, envp);

    execv("/bin/ps", argv);

    // Có p, có thể sử dụng biến môi trường PATH, không cần viết đường dẫn đầy đủ
    execvp("ps", argv);

    // Có e, cần tự lắp ráp biến môi trường
    execve("/bin/ps", argv, envp);

    exit(0);
}

Thực tế, chỉ có execve là hệ thống gọi thực sự, năm hàm còn lại cuối cùng đều gọi execve, vì vậy execve ở trang man 2, các hàm khác ở trang man 3. Mối quan hệ giữa các hàm này như hình dưới.
Hình exec function family Một ví dụ đầy đủ

Ví dụ

Có thể dùng chương trình của mình để thực thi lệnh hệ thống

  • Thay thế chương trình một khi thành công, mã sau exec* không được thực thi nữa, vì đã bị thay thế
  • exec* chỉ có giá trị trả về khi lỗi, không có giá trị trả về khi thành công
  • Thay thế hoàn tất, không tạo chương trình mới
  • Tạo một tiến trình, trước tiên tạo pcb, không gian địa chỉ, bảng trang, v.v.; sau đó tải chương trình vào bộ nhớ. Bản chất công việc của thay thế chương trình là tải
  • Truyền tham số tiêu chuẩn, cũng có thể truyền tham số không tiêu chuẩn
  • Phiên bản nhiều tiến trình

Vì tiến trình con thực hiện thay thế mã và dữ liệu, do tính độc lập của tiến trình, để không ảnh hưởng đến tiến trình cha, tiến hành sao chép khi ghi
Bản chất của sao chép khi ghi là mở ra không gian mới

execlp, p:PATH, chỉ cần thông báo tên chương trình, hệ thống sẽ tự động tìm trong biến môi trường PATH khi thay thế

execv,

execvp

Thay thế chương trình của chính mình, dùng process gọi mytest

makefile

.PHONY:all
all:myprocess mytest

mytest:mytest.cc
	g++ -o $@ $^ -std=c++11
myprocess:myprocess.c
	gcc -o $@ $^
.PHONY:clean
	rm -f myprocess mytest

Gọi shell script

Gọi file python

Mọi ngôn ngữ đều là tiến trình, ở cấp hệ thống, chỉ có mã và dữ liệu, chỉ có thể thấy tiến trình

Thay thế chương trình có thể lấy tất cả các biến môi trường

Kết quả như trên
Mặc định có thể lấy tất cả các biến môi trường của tiến trình con thông qua cách kế thừa không gian địa chỉ
Thay thế chương trình tiến trình, không thay thế dữ liệu biến môi trường

  • Nếu muốn tiến trình con kế thừa tất cả các biến môi trường, có thể lấy trực tiếp
  • Thêm biến môi trường
    putenv

putenv, đưa vào tham số dòng lệnh của tiến trình cha
Thêm biến môi trường không ảnh hưởng đến cái trước, sẽ ảnh hưởng đến cái sau

  • Thiết lập biến môi trường hoàn toàn mới cho tiến trình con
    execle

Truyền trực tiếp bảng biến môi trường của tiến trình cha cho tiến trình con

Shell đơn giản

Xem xét tương tác điển hình với shell sau:

[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps
Dùng trục thời gian bên dưới để biểu thị thứ tự xảy ra sự kiện. Trong đó thời gian từ trái sang phải. Shell được biểu thị bằng khối sh, nó di chuyển từ trái sang phải theo thời gian trôi. Shell đọc chuỗi "ls" từ người dùng. Shell tạo một tiến trình mới, sau đó chạy chương trình ls trong tiến trình đó và chờ tiến trình đó kết thúc. Sau đó shell đọc một dòng đầu vào mới, tạo một tiến trình mới, chạy chương trình trong tiến trình đó và chờ tiến trình đó kết thúc. Vì vậy, để viết một shell, cần lặp lại quy trình sau:
  1. Lấy dòng lệnh
  2. Phân tích cú pháp dòng lệnh
  3. Tạo một tiến trình con (fork)
  4. Thay thế tiến trình con (execvp)
  5. Tiến trình cha chờ tiến trình con thoát (wait)
Dựa trên những ý tưởng này và các công nghệ đã học trước đây, chúng ta có thể tự mình triển khai một shell. Cài đặt mã:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

#define MAX_CMD 1024
char command[MAX_CMD];

int do_face()
{
    memset(command, 0x00, MAX_CMD);
    printf("minishell$ ");
    fflush(stdout);
    if (scanf("%[^\n]%*c", command) == 0) {
        getchar();
        return -1;
    }
    return 0;
}

char **do_parse(char *buff)
{
    int argc = 0;
    static char *argv[32];
    char *ptr = buff;
    while(*ptr != '\0') {
        if (!isspace(*ptr)) {
            argv[argc++] = ptr;
            while((!isspace(*ptr)) && (*ptr) != '\0') {
                ptr++;
            }
        }else {
            while(isspace(*ptr)) {
                *ptr = '\0';
                ptr++;
            }
        }
    }
    argv[argc] = NULL;
    return argv;
}

int do_exec(char *buff)
{
    char **argv = {NULL};
    int pid = fork();
    if (pid == 0) {
        argv = do_parse(buff);
        if (argv[0] == NULL) {
            exit(-1);
        }
        execvp(argv[0], argv);
    }else {
        waitpid(pid, NULL, 0);
    }
    return 0;
}

int main(int argc, char *argv[])
{
    while(1) {
        if (do_face() < 0)
            continue;
        do_exec(command);
    }
    return 0;
}

In dấu nhắc dòng lệnh

Nhận đầu vào người dùng

scanf mặc định chỉ có thể lấy chuỗi bằng cách phân tách bằng dấu cách
fgets, lấy một dòng nội dung qua luồng tệp được chỉ định, đặt nội dung dòng lấy được vào bộ đệm mà con trỏ trỏ đến, lấy thành công thì trả về địa chỉ, lấy thất bại thì trả về NULL
Khi nhập, nhấn enter sẽ nhập xuống dòng, cần loại bỏ ký tự xuống dòng
fgets là hàm c, mặc định sẽ thêm \0 vào cuối chuỗi nhận được
Đóng gói

Cắt chuỗi đầu vào

strtok dựa trên dấu phân cách, chia chuỗi được chỉ định thành nhiều chuỗi con
Mảng char* được tạo phải kết thúc bằng NULL, thuận tiện để truyền tham số trực tiếp
Điều kiện vòng lặp while dùng = vì khi strtok cắt thất bại, nó sẽ trả về NULL, đúng một lần gán, đặt phần tử cuối cùng của mảng argv thành NULL
Sau đó vòng lặp while kiểm tra argv[i] là NULL, không phù hợp với điều kiện, thoát vòng lặp
Đóng gói

Thực thi lệnh

Đóng gói

Sử dụng shell lệnh lặp

Xử lý lệnh nội tại

Tiến trình con thực thi lệnh tương ứng, thực thi lệnh cd là tiến trình con, nên để tiến trình cha thay đổi đường dẫn

Vấn đề chuỗi rỗng khi nhấn enter

Thay đổi đường dẫn gợi ý

…cần thực hiện
Sử dụng giao diện getpwd
Không phải lấy …, mà là lấy đường dẫn tuyệt đối bắt đầu từ thư mục gốc

Nhập biến môi trường

Tiến trình con thực thi export, không ảnh hưởng đến tiến trình bash, cần nhập vào ngữ cảnh của bash
export cũng là lệnh nội tại

Cách này, biến môi trường mới được tạo, lần truy vấn đầu tiên có, nhưng sau đó truy vấn qua lệnh shell, lại biến mất
Bởi vì argv trỏ đến một chuỗi, tức là trong commandline, mỗi lần sử dụng shell, nội dung chuỗi commandline bị ghi đè trực tiếp, vì vậy biến môi trường được nhập liên tục bị ghi đè, ảnh hưởng đến biến môi trường

Sao chép biến môi trường cần nhập vào argv vào env, bộ đệm toàn cục được định nghĩa, sau đó putenv trực tiếp
Như vậy không bị ghi đè do dòng lệnh, ảnh hưởng liên tục đến biến môi trường

Mã thoát tiến trình

ls có màu

Tương đồng giữa hàm và tiến trình

exec/exit giống như call/return
Một chương trình C gồm nhiều hàm. Một hàm có thể gọi một hàm khác, đồng thời truyền một số tham số cho nó. Hàm được gọi thực hiện một số thao tác, sau đó trả về một giá trị. Mỗi hàm có biến cục bộ của riêng nó, các hàm khác nhau giao tiếp với nhau thông qua hệ thống call/return. Mô hình giao tiếp này giữa các hàm có dữ liệu riêng tư thông qua tham số và giá trị trả về là nền tảng của lập trình có cấu trúc. Linux khuyến khích mở rộng mô hình này được áp dụng trong chương trình sang giữa các chương trình. Như hình dưới Một chương trình C có thể fork/exec một chương trình khác, và truyền một số tham số cho nó. Chương trình được gọi thực hiện một số thao tác, sau đó trả về giá trị qua exit(n). Tiến trình gọi nó có thể lấy giá trị trả về của exit qua wait(&ret).

Thẻ: linux tiến trình fork exec shell

Đăng vào ngày 25 tháng 5 lúc 03:10