Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥

       进程是运行中的程序,是资源分配的最小单位,其有一些特性对于实际开发很有帮助,本篇博客将进程的相关特性进行梳理总结,包含工作目录,环境变量,标准输出转命令行参数,读写锁控制进程互斥。

目录

1.进程工作目录

 1.1获取CWD

1.2修改CWD

1.3代码演示

2.环境变量

2.1操作效果演示

3.标准输出转命令行参数

4.O_CLOEXEC标志作用

5.读写锁控制进程互斥


1.进程工作目录

        每个进程都有一个与之对应的工作目录(CWD),进程相对路径都是基于CWD,关于CWD有获取和修改CWD。

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第1张图片

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第2张图片

 1.1获取CWD

 #include
char *getcwd(char *buf, size_t size);

//不安全,不建议使用
char *getwd(char *buf);
char *get_current_dir_name(void);

1.2修改CWD

#include
int chdir(const char *path);
int fchdir(int fd);

1.3代码演示

ppath.c

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

#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

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第3张图片

2.环境变量

        操作系统有系统环境变量,每个进程也有自己的环境变量,进程启动之后会拷贝一份环境变量到进程空间中,进程运行过程中可以增删改查自己的环境变量,而不会影响系统的环境变量。

2.1操作效果演示

penv.c

#include
#include

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 

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第4张图片

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第5张图片

execle启动进程penv并修改其环境变量。

testenv.c

#include
#include

int main(void){

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

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

    return 0;
}

gcc -o testenv testenv.c 

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第6张图片

3.标准输出转命令行参数

        每个进程可以命令行参数输入参数,如何将一个标准输出数据转换为进程的命令行参数呢?不错大家应该会想到xargs命令,这个命令就是将标准输出参数转换为进程的命令行参数,例如

find ./* -name "*.c" | xargs grep main
xargs到底是怎么实现的呢?以下代码将实现一个自己的xargs

args.cpp

#include 
#include 
#include 
#include 

#include 
#include 
#include 

using namespace std;

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

void execCmd(vector &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 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 

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第7张图片

4.O_CLOEXEC标志作用

O_CLOEXEC标志作用
    当执行exec (3)函数族时, 被设置close_on_exec标志的文件被关闭;

Linux下父进程打开文件得到的fd,将会被子进程继承,子进程可对fd的读写操作。实际业务开发中,fork出子进程中会调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等。此时保存文件描述符的变量当然也不存在了,导致无法关闭无用的文件描述符了。
另外在复杂系统中,有时我们fork子进程时已经不知道打开了多少个文件描述符(包括socket句柄等),这此时进行逐一清理确实有很大难度,这时父进程打开文件可以使用标志O_CLOEXEC,当fork子进程后仍然可以使用fd。但执行exec后系统就会字段关闭子进程中的fd了。

fd设置标志O_CLOEXEC方法

// 方法一: 注意: O_CLOEXEC标志只在内核>=2.6.23和glibc≥2.7以上版本使用 Linux查看系统内核版本: 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);

// 方法三: Linux内核≥2.6.24且glibc≥2.7,fcntl接受新参数F_DUPFD_CLOEXEC:
#include
newfd = fcntl(oldfd, F_DUPFD_CLOEXEC);

// 方法四:inux内核≥2.6.27和glibc≥2.9
#define _GNU_SOURCE
#include
pipe2(pipefds, O_CLOEXEC);
dup3(oldfd, newfd, O_CLOEXEC);

5.读写锁控制进程互斥

fcntl_lock.c

#include
#include 
#include 
#include 
#include 
#include 

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 

读锁 -> 读锁

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第8张图片

 读锁 -> 写锁

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第9张图片

 写锁  ->  读锁

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第10张图片

写锁 -> 写锁

Linux下进程特性总结:工作目录,环境变量,标准输出转命令行参数,O_CLOEXEC标志作用,读写锁控制进程互斥_第11张图片

 可以看到两个进程同时加读锁,互不相互影响。加写锁时,要是有进程已经加了读锁或者写锁,则会阻塞,直到另外的进程将读锁或者写锁进行解锁。写锁是排他的。

你可能感兴趣的:(linux)