Linux -- 进程控制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikeyone/article/details/88846846

进程id和用户id

#include <unistd.h>
pid_t getpid(void);   //返回当前进程的id
pid_t getppid(void);  //返回父进程的id
uid_t getuid(void);   //返回实际用户id
uid_t geteuid(void);  //返回有效用户id
gid_t getgid(void);   //返回实际组id
gid_t getegid(void);  //返回有效组id

关于实际id和有效id在我的另一篇博客做了详细介绍:设置用户ID和设置组ID

#include <unistd.h>

int setuid(uid_t uid);
int setgid(gid_t gid);

进程可以选择设置对应的实际用户id、有效用户id、实际组id、有效组id。分为两种情况:

  1. 如果当前进程具有超级用户权限,那么可以设置任意的用户id,并且进程的实际id和有效id,以及保存的设置用户id都会跟着改变。
  2. 如果当前进程不具有超级用户权限,这两个函数设置的是有效id,并且只能uid只能选择实际id和保存的设置用户id中的一个。
  3. 其他情况下会返回-1, 并设置对应的errno。
#include <unistd.h>

int seteuid(uid_t uid);
int setegid(gid_t gid);

这两个函数和上面介绍的功能基本一致,但只会影响到有效id,对其他id不做修改。

创建新进程

#include <unistd.h>

pid_t fork(void);

此函数会通过克隆方式创建子进程,如果fork出错则返回-1; 如果成功,函数在父进程和子进程中的返回值不同:父进程返回新创建子进程的进程pid,子进程返回值为0。
子进程是父进程的一个副本,因此会继承父进程的很多现有属性,比如打开的文件描述符,用户id,组id,进程组id,会话id,控制终端,环境,umask标志,信号屏蔽字等。关于这里有一点需要注意:设置的记录锁是不继承的。
fork创建新进程并不是无节制的创建,系统会对用户设置拥有进程的数量限制,通过ulimit命令可以修改此值,参见我的另一篇博客Linux rlimit 函数详解

#include <unistd.h>

pid_t vfork(void);

vfork和fork的返回值一样,但是两者的定义有些区别,vfork是为了立即执行exec函数而创建的一个子进程,处于此目的,所以它不需要复制父进程的地址空间,因此提高了创建进程的效率。由于它不复制父进程的地址空间,所以它不可以引用
父进程空间中的数据,否则可能会导致异常出错,另外vfork会让子进程优先开始运行,当子进程运行了exec或者exit后,父进程才会开始运行,如果子进程等待父进程先运行那么会发生死锁行为,有人认为此函数存在一些瑕疵,所以有正在被弃用的趋势。
新的应用程序中应该尽量避免使用它。

exit系列函数

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

此函数是一个系统调用,它并不会执行冲洗流的操作。

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

exit属于stdlib标准库中的一个接口,它在实现中会调用_exit函数,但是在调用前会执行文件流的清理操作,对所有输出流执行fclose操作写入到文件中。
exit中的清理操作是通过 atexit 函数来注册的,后面进行介绍。

#include <stdlib.h>
int atexit(void (*func)(void));

通过该函数注册的func回调会在exit进程退出前执行,根据ISO C的规定,一个进程可以最多注册32个回调,执行顺序是与注册顺序相反进行的,同一个函数注册多次将会执行多次。

wait系列函数

#include <sys/wait.h>

pid_t wait(int *pstatus);
pid_t waitpid(pid_t pid, int *pstatus, int options);

成功返回进程pid,出错返回0或者-1。 此函数用于父进程等待子进程结束,并执行清理回收工作,如果一个子进程结束后父进程没有进行wait操作,那么子进程会成为僵尸进程残留在内存中。
这两个API有一些区别:

  1. wait会阻塞父进程等待子进程的结束,而waitpid有一个选项可以非阻塞运行。
  2. wait调用后会等待第一个终止的子进程,而waitpid可以指定要等待的子进程pid,因此它等待的并不一定是第一个终止的子进程。
  3. waitpid可以进行作业控制,此功能本文不做详细介绍

status会返回子进程终止时的状态,如果不关系子进程的终止状态,可以传入NULL。对于终止状态的判断可以使用:

WIFEXITED(status)  //正常终止
WIFSIGNALED(status) //信号引起的异常终止
WIFSTOPPED(status)  //子进程暂停会返回对应状态
WIFCONTINUED(status) //暂停后再次继续的进程会返回对应状态

waitpid传入的pid参数说明:
pid == -1 等待任一个子进程
pid > 0 等待特定pid的子进程
pid == 0 等待组ID等于调用进程组ID的任一个子进程
pid < -1 等待组ID等于pid绝对值的任一个子进程

waitpid对应的options选项:
特别说明一下WNOHANG,由于waitpid可以指定等待特定pid的子进程,如果pid指定的子进程并不是立即可用的,则不阻塞,并返回0。

exec系列函数

#include <unistd.h>

int execl(const char* pathname, const char *arg0, .../* (char *) 0 */);
int execv(const char* pathname, char *const argv[]);
int execle(const char* pathname, const char *arg0,.../* (char *) 0 , char * const envp[] */);
int execve(const char* pathname, char *const argv[], char * const envp[]);
int execlp(const char* filename, const char *arg0,.../* (char *) 0 */);
int execvp(const char* filename, char *const argv[], char * const envp[]);

其中带有l后缀的说明是把参数表按照list格式传入,参数的最后一个必须是一个"(char *) 0";带有v后缀的说明是把参数当做一个字符串指针数组传入argv;
如果后缀带有e说明需要传入environ字符串数组;如果后缀是p说明会从PATH环境变量中搜寻对应的filename作为可执行程序运行。

system函数

#include <stdlib.h>

int system(const char *cmdstring);

system可以用来执行一个命令字符串,它的实现是依赖于fork,exec和waitpid函数,并且exec执行的是一个shell命令,这个函数是为了简化我们在C程序中调用一个shell命令的操作。比如:

system("data > time.txt")

会在time.txt中输出当前的日期时间。
返回值:

1. 如果是fork或者waitpid失败,返回-1
2. 如果exec执行失败,返回127
3. 如果执行成功,该函数的返回值为shell的终止状态,一般为0

关于此函数有两点需要注意:(1)调用system的进程会阻塞等待system命令执行完成后才会继续(2)system返回值表示的是shell是否正确返回,而并不表示命令本身是否正确执行。

进程优先级

#include <unistd.h>

int nice(int adj);

设置进程的nice值,Linux中adj参数范围为0到39, 值越低优先级越高。

#include <sys/resource.h>

int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int value);

which:PRIO_PROCESS/PRIO_PGRP/PRIO_USER可选择。
who:为0表示按照which来选择范围,如果范围是一组进程,get返回最高的优先级
value:这里的范围和内核中的nice值一致[-20–19]

父子进程的同步

子进程创建以后可能会发生一些竞争,比如同时访问同一个资源,此时必须要有一个先后顺序那么可以使用同步机制来完成。同步可以采用如下方式实现:

  1. 可以使用信号来做同步
  2. 可以使用管道来做同步
    关于同步的课题在其他博客详细介绍。

示例

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <string.h>

#define pr_debug(fmt,...)   do{ printf("[%ld] DEBUG: "fmt,(long)getpid(),##__VA_ARGS__); fflush(stdout); }while(0)
#define pr_info(fmt,...)    do{ printf("[%ld] INFO:  "fmt,(long)getpid(),##__VA_ARGS__); fflush(stdout); }while(0)
#define pr_err(fmt,...)     do{ printf("[%ld] ERROR: "fmt,(long)getpid(),##__VA_ARGS__);fflush(stdout); }while(0)
#define err_exit(fmt,...)   do{ printf("[%ld] ERROR: "fmt,(long)getpid(),##__VA_ARGS__); exit(1); }while(0)

int main(int argc, char *argv[])
{
	pid_t pid;
	int len, i, status;
	char **buf;

	if (argc == 1)
		err_exit("Usage: need at least one argument for this program.\n");

	len = (argc - 1) + 3; //(char *) 0 at last
	buf = (char **) malloc(sizeof(char *) * len);
	if (!buf)
		err_exit("malloc error\n");

	/*
	 * fill buf for exec argv
	 */
	buf[0] = "bash";
	buf[1] = "-c";
	buf[len-1] = (char *) 0;
	for (i = 1; i < argc; i++) {
		buf[i+1] = argv[i];
	}

	/*
	 * print buf for debug
	 */
	for (i = 0; i < len; i++)
		pr_info("argv[%d]=%s\n", i, buf[i]);

	if ((pid = fork()) < 0) {
		err_exit("fork error, %s\n", strerror(errno));
	} else if (pid == 0) {
	/* child process */
		execv("/bin/bash", buf);
		_exit(127);
	} else {
	/* pid > 0 : parent process */
		while (waitpid(pid, &status, 0) < 0) {
			if (errno != EINTR) {
				pr_err("waitpid error, %s\n", strerror(errno));
				status = -1;
			}

		}
	}
	free(buf);
	return status;
}

执行结果:

$ ./command time
[25496] INFO:  argv[0]=bash
[25496] INFO:  argv[1]=-c
[25496] INFO:  argv[2]=time
[25496] INFO:  argv[3]=(null)

real	0m0.000s
user	0m0.000s
sys	0m0.000s

猜你喜欢

转载自blog.csdn.net/rikeyone/article/details/88846846