进程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。分为两种情况:
- 如果当前进程具有超级用户权限,那么可以设置任意的用户id,并且进程的实际id和有效id,以及保存的设置用户id都会跟着改变。
- 如果当前进程不具有超级用户权限,这两个函数设置的是有效id,并且只能uid只能选择实际id和保存的设置用户id中的一个。
- 其他情况下会返回-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有一些区别:
- wait会阻塞父进程等待子进程的结束,而waitpid有一个选项可以非阻塞运行。
- wait调用后会等待第一个终止的子进程,而waitpid可以指定要等待的子进程pid,因此它等待的并不一定是第一个终止的子进程。
- 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]
父子进程的同步
子进程创建以后可能会发生一些竞争,比如同时访问同一个资源,此时必须要有一个先后顺序那么可以使用同步机制来完成。同步可以采用如下方式实现:
- 可以使用信号来做同步
- 可以使用管道来做同步
关于同步的课题在其他博客详细介绍。
示例
#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