Summary of process characteristics under Linux: working directory, environment variables, standard output to command line parameters, the role of the O_CLOEXEC flag, read-write locks to control process mutual exclusion

       A process is a running program and the smallest unit of resource allocation. It has some features that are very helpful for actual development. This blog summarizes the relevant features of the process, including working directory, environment variables, and standard output to command line parameters. , read-write lock controls process mutual exclusion.

Table of contents

1. Process working directory

 1.1 Obtaining CWD

1.2 Modify CWD

1.3 Code demonstration

2. Environment variables

2.1 Operation effect demonstration

3. Convert standard output to command line parameters

4.O_CLOEXEC flag function

5. Read-write lock controls process mutual exclusion


1. Process working directory

        Each process has a corresponding working directory (CWD). The relative paths of the processes are based on CWD. Regarding CWD, there are obtaining and modifying CWD.

 1.1 Obtaining CWD

 #include <unistd.h>
char *getcwd(char *buf, size_t size);

//Unsafe, not recommended to use
char *getwd(char *buf);
char *get_current_dir_name(void);

1.2 Modify CWD

#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);

1.3 Code demonstration

ppath.c

// 如果是gcc编译器,需要使用该宏,否则gcc -E之后发现找不到get_current_dir_name声明,运行时程序崩溃
//#define  _GNU_SOURCE   
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define MAX_LEN (1024)

int main(void)
{
    char buffer[MAX_LEN] = {0};
    char *path = getcwd(buffer, MAX_LEN);
    if (path) {
        printf("buffer: %s path: %s size: %ld\n", buffer, path, strlen(buffer));
    }
    
    char* curdir = get_current_dir_name();
    if (curdir) {
        printf("curdir: %s\n" , curdir);
        free(curdir);
    }
 
    //不安全函数,不建议使用
    char *twd = getwd(buffer);  
    if (twd) {
        printf("buffer:%s  twd:%s \n", buffer, twd);
    }  

    //修改进程工作目录
    chdir("otherpath");
    curdir = get_current_dir_name();
    if (curdir) {
        printf("after curdir: %s\n" , curdir);
        free(curdir);
    }

    return 0 ; 
}

g++ -o ppath ppath.c

2. Environment variables

        The operating system has system environment variables, and each process also has its own environment variables. After the process is started, a copy of the environment variables will be copied to the process space. During the running process, you can add, delete, modify and check your own environment variables without affecting the system environment. variable.

2.1 Operation effect demonstration

penv.c

#include<stdio.h>
#include<stdlib.h>

extern char **environ;

int main(int argc,char *argv[],char *envp[]) {
    int i=0;

    // 通过envp获取系统所有环境变量
    for(; envp[i] != NULL; i++) {
        printf("%s\n", envp[i]);
    }
    
    printf("=========================================================\n");

    // 使用系统全局变量environ获取系统所有环境变量
    i = 0;
    for(; environ[i] != NULL; i++) {
        printf("%s\n", environ[i]);
    }

    char *pEnv = getenv("XDG_DATA_DIRS");
    if (pEnv) {
        printf("XDG_DATA_DIRS: %s\n", pEnv);
    }

    //设置的环境变量只在当前进程生效,不会影响系统的环境变量
    putenv("XDG_DATA_DIRS=Hello");
    pEnv = getenv("XDG_DATA_DIRS");
    if (pEnv) {
        printf("XDG_DATA_DIRS: %s\n", pEnv);
    }

    //修改或者增加环境变量
    setenv("XDG_DATA_DIRS", "world", 1);
    pEnv = getenv("XDG_DATA_DIRS");
    if (pEnv) {
        printf("XDG_DATA_DIRS: %s\n", pEnv);
    }

    //删除环境变量
    unsetenv("XDG_DATA_DIRS");
    pEnv = getenv("XDG_DATA_DIRS");
    if (pEnv) {
        printf("XDG_DATA_DIRS: %s\n", pEnv);
    }
    else {
        printf("no XDG_DATA_DIRS\n");
    }

    setenv("XDG_DATA_DIRS", "china", 1);
    pEnv = getenv("XDG_DATA_DIRS");
    if (pEnv) {
        printf("XDG_DATA_DIRS: %s\n", pEnv);
    }

    // 清除所有环境变量
    clearenv();
    pEnv = getenv("XDG_DATA_DIRS");
    if (pEnv) {
        printf("XDG_DATA_DIRS: %s\n", pEnv);
    }
    else {
        printf("clear XDG_DATA_DIRS\n");
    }
    pEnv = getenv("PATH");
    if (pEnv) {
        printf("PATH: %s\n", pEnv);
    }

    return 0;
}

gcc -o penv penv.c 

execle starts the process penv and modifies its environment variables.

testingv.c

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

int main(void){

    const char* ps_env[] = {"PATH=hello:world", NULL};

    execle("./penv", "penv", NULL, ps_env);

    return 0;
}

gcc -o testv testv.c 

3. Convert standard output to command line parameters

        Each process can input parameters through command line parameters. How to convert a standard output data into the command line parameters of the process? Yes, everyone should think of the xargs command. This command converts the standard output parameters into the command line parameters of the process, for example

find ./* -name "*.c" | xargs grep main
How is xargs implemented? The following code will implement an own xargs

args.cpp

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <iostream>
#include <string>
#include <vector>

using namespace std;

string getCommand(const vector<string> &cmd) {
    string command = "";
        for (int i = 0; i < cmd.size(); i++) {
        command = command + cmd[i] + " ";
    }
    return command;
}

void execCmd(vector<string> &cmd) {
    if (cmd.empty()) {
        return;
    }
    string file = cmd[0];
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork error.");
        return;
    }
    if (pid == 0) {
    execl("/bin/bash", "bash", "-c", getCommand(cmd).c_str(), nullptr);
    exit(1);
  }
  int status = 0;
  int ret = waitpid(pid, &status, 0);
  if (ret != pid) {
    perror("waitpid failed.");
  }
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        cout << "arguments is invalid, use xargs cmd" << endl;
        return -1;
    }
    string line;
    string argument;
    vector<string> cmd{argv[1]};

    auto add_arg = [&](string &arg) {
        if (arg != "") {
            cmd.push_back(arg);
        }
        arg = "";
    };

    while (cin >> line) {
        for (size_t i = 0; i < line.size(); i++) {
            if (isblank(line[i])) {
                add_arg(argument);
                continue;
            }
            argument += line[i];
        }
        add_arg(argument);
    }
    execCmd(cmd);

    return 0;
}

g++ -std=c++11 -o exargs args.cpp 

4.O_CLOEXEC flag function

The function of the O_CLOEXEC flag is
    that when the exec (3) function family is executed, the file with the close_on_exec flag set is closed;

Under Linux, the fd obtained by the parent process opening the file will be inherited by the child process, and the child process can read and write the fd. In actual business development, when forking out of a child process, exec will be called to execute another program. At this time, the text, data, heap and stack of the child process will be replaced with a new program. At this time, of course, the variable that holds the file descriptor no longer exists, making it impossible to close the useless file descriptor.
In addition, in complex systems, sometimes we no longer know how many file descriptors (including socket handles, etc.) are opened when we fork the child process. At this time, it is really difficult to clean up one by one. At this time, the parent process can use the flag to open the file. O_CLOEXEC, fd can still be used after forking the child process. But after executing exec, the system will close the fd in the child process.

fd set flag O_CLOEXEC method

//Method 1: Note: The O_CLOEXEC flag is only used in kernel >=2.6.23 and glibc≥2.7 or above. Use Linux to check the system kernel version: uname -r / uname -a
int fd = open("xxx", O_RDWR | O_CLOEXEC) ;

// 方法二:
int fd=open("foo.txt", O_RDWR);
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);

// Method three: Linux kernel ≥ 2.6.24 and glibc ≥ 2.7, fcntl accepts the new parameter F_DUPFD_CLOEXEC:
#include <fcntl.h>
newfd = fcntl(oldfd, F_DUPFD_CLOEXEC);

// Method 4: Linux kernel ≥ 2.6.27 and glibc ≥ 2.9
#define _GNU_SOURCE
#include <unistd.h>
pipe2(pipefds, O_CLOEXEC);
dup3(oldfd, newfd, O_CLOEXEC);

5. Read-write lock controls process mutual exclusion

fcntl_lock.c

#include<stdio.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>

int set_lock(int fd, int type)
{
    struct flock lock;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_type = type;
    lock.l_pid = -1;

    fcntl(fd, F_GETLK, &lock);

    if(lock.l_type != F_UNLCK)
    {
        if (lock.l_type == F_RDLCK)  
        {
            printf("read lock already set by %d\n", lock.l_pid);
        }
        else if (lock.l_type == F_WRLCK) 
        {
            printf("write lock already set by %d\n", lock.l_pid);
        }           
    }

    lock.l_type = type;

    if ((fcntl(fd, F_SETLKW, &lock)) < 0)
    {
        printf("lock failed : type = %d\n", lock.l_type);
        return 1;
    }

    switch (lock.l_type)
    {
        case F_RDLCK:
            printf("read lock set by %d\n", getpid());
            break;
        case F_WRLCK:
            printf("write lock set by %d\n", getpid());
            break;
        case F_UNLCK:
            printf("unlock by %d\n", getpid());
            break;
        default:
            break;
    }

    return 0;
}

int main(int argc, char** argv)
{  
    int fd;
    if (argc < 2) {
        printf("a.out [1 / 2]\n");  
        return -1;
    }

    fd = open("test.txt", O_RDWR | O_CREAT, 0644);
    if(fd < 0)
    {     
        printf("opem file error\n");  
        return -1; 
    }   
    
    if (1 == atoi(argv[1])) {
        // 上读锁
        set_lock(fd, F_RDLCK);
    }
    else {
        // 上写锁
        set_lock(fd, F_WRLCK);
    }
    getchar(); 
    set_lock(fd, F_UNLCK);  
    close(fd);   

    return 0;
}

gcc -o fcntl_lock fcntl_lock.c 

Read lock->Read lock

 Read lock->Write lock

 Write lock -> Read lock

Write lock -> Write lock

 It can be seen that the two processes add read locks at the same time without affecting each other. When adding a write lock, if a process has already added a read lock or write lock, it will be blocked until another process unlocks the read lock or write lock. Write locks are exclusive.

Guess you like

Origin blog.csdn.net/hsy12342611/article/details/132000740