进程是运行中的程序,是资源分配的最小单位,其有一些特性对于实际开发很有帮助,本篇博客将进程的相关特性进行梳理总结,包含工作目录,环境变量,标准输出转命令行参数,读写锁控制进程互斥。
目录
1.进程工作目录
1.1获取CWD
1.2修改CWD
1.3代码演示
2.环境变量
2.1操作效果演示
3.标准输出转命令行参数
4.O_CLOEXEC标志作用
5.读写锁控制进程互斥
每个进程都有一个与之对应的工作目录(CWD),进程相对路径都是基于CWD,关于CWD有获取和修改CWD。
#include
char *getcwd(char *buf, size_t size);
//不安全,不建议使用
char *getwd(char *buf);
char *get_current_dir_name(void);
#include
int chdir(const char *path);
int fchdir(int fd);
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
操作系统有系统环境变量,每个进程也有自己的环境变量,进程启动之后会拷贝一份环境变量到进程空间中,进程运行过程中可以增删改查自己的环境变量,而不会影响系统的环境变量。
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
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
每个进程可以命令行参数输入参数,如何将一个标准输出数据转换为进程的命令行参数呢?不错大家应该会想到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
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);
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
读锁 -> 读锁
读锁 -> 写锁
写锁 -> 读锁
写锁 -> 写锁
可以看到两个进程同时加读锁,互不相互影响。加写锁时,要是有进程已经加了读锁或者写锁,则会阻塞,直到另外的进程将读锁或者写锁进行解锁。写锁是排他的。