进程环境

main启动

当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序将此启动例程指定为程序的起始地址,接着启动例程从内核中取出命令行参数和环境变量值,然后执行main函数。

进程终止

使进程终止的方式有8种,其中5种为正常终止,3种为异常终止:

终止类型

说明

正常终止

(1)   从main返回

(2)   调用exit

(3)   调用_exit或_Exit

(4)   最后一个线程从启动例程返回

(5)   在最后一个线程中调用pthread_exit

异常终止

(1)   调用abort

(2)   接收到一个信号

(3)   最后一个线程对取消请求做出响应

相关函数:

#include <stdlib.h>

void exit(int status);

void _Exit(int status);

#include <unistd.h>

void _exit(int status);

说明:

在main中,return(0)与exit(0)是等价的。

exit函数总是执行一个标准I/O库的清理关闭工作,即对所有打开的流调用fclose函数。

#include <stdlib.h>

int atexit(void (*func)(void));

返回值:成功,0;失败,-1

说明:

atexit函数用于向进程注册函数,最多可以注册32个。这些函数将由exit调用。我们将这些函数称为终止处理函数。如果同一个函数登记了多次,则也会被调用多次。

exit调用顺序与这些函数的注册顺序刚好相反

# atexit函数的使用
[root@benxintuzi IO]# cat atexit.c
#include <stdio.h>
#include <stdlib.h> static void my_exit1(void);
static void my_exit2(void); int main(void)
{
if(atexit(my_exit1) != )
printf("can't register my_exit1\n");
if(atexit(my_exit1) != )
printf("can't register my_exit1\n");
if(atexit(my_exit2) != )
printf("can't register my_exit2\n"); printf("main is done\n");
return ;
} static void my_exit1(void)
{
printf("first exit handler\n");
} static void my_exit2(void)
{
printf("second exit handler\n");
} [root@benxintuzi IO]# ./atexit
main is done
second exit handler
first exit handler
first exit handler

环境变量

每个程序都有一张环境表,是一个字符指针数组,每个指针包含一个以null结束的字符串的地址。全局变量extern char** environ指向该环境表的地址:

如下函数用于操作环境变量的值:

#include <stdlib.h>

char* getenv(const char* name);

返回值:成功,返回与name关联的value;失败,返回NULL

int setenv(const char* name, const char* value, int rewrite);

int unsetenv(const char* name);

int putenv(char* str);

返回值:成功,0;失败,-1

说明:

putenv将name=value的字符串放到环境表中。如果name已经存在,则先删除其原来的定义。

关于参数rewrite:如果rewrite为0,则保留原有的name定义;rewrite非0,则覆盖原有的name定义。

unsetenv用于删除指定的name定义。

通常用getenv和putenv来访问特定的环境变量,而非使用environ变量,但是如果要查看整个环境,则仍然需要使用environ,如下:

#include <stdio.h>

int main(void)
{
extern char** environ; while(*environ != NULL)
{
printf("%s\n", *environ);
environ++;
} return ;
} [root@benxintuzi IO]# ./environ
HOSTNAME=benxintuzi
SELINUX_ROLE_REQUESTED=
SHELL=/bin/bash
TERM=vt100
HISTSIZE=
HADOOP_HOME=/bigdata/hadoop-2.6.
SSH_CLIENT=192.168.8.1
SELINUX_USE_CURRENT_RANGE=
QTDIR=/usr/lib/qt-3.3
QTINC=/usr/lib/qt-3.3/include
SSH_TTY=/dev/pts/
USER=benxintuzi
...

程序的存储布局

C程序由以下几部分组成:

正文段:存放代码的地方,通常是可共享的,即该部分内容是可再入的,因此一般只需一份副本,并且设置为只读。

初始化数据段:包含程序中需要明确指定初值的变量,比如:int maxcount = 99;

未初始化数据段(bss段):该段的数据不指定初值也可,因为内核在程序开始执行前,会将此段中的数据初始化为0或者NULL。该部分有时也被称为常量区或全局区。

栈区:存放临时变量,函数地址或者环境上下文信息等。

堆区:动态存储分配,位于bss段和栈区之间。

一个典型的存储空间示意图如下:

Linux中的size程序用户报告一个程序所占用的存储空间信息:

[root@benxintuzi IO]# size passwd01 memstr

text    data     bss     dec     hex   filename

1360    264      8       1632    660    passwd01

2157    284      8       2449    991    memstr

动态存储分配函数如下:

#include <stdlib.h>

void* malloc(size_t size);

void* calloc(size_t nobj, size_t size);

void* realloc(void* ptr, size_t newsize);

void free(void* ptr);

说明:

malloc分配指定字节数的存储区,初始值不确定。

calloc分配指定长度的存储区,初始值为0。

realloc增加或者减少存储区的长度,新增的区域内初始值不确定。

资源限制

每个进程都有一组资源限制,可以使用如下函数查询和更改:

#include <sys/resource.h>

int getrlimit(int resource, struct rlimit* rlptr);

int setrlimit(int resource, const struct rlimit* rlptr);

返回值:成功,0;失败,-1

struct rlimit

{

rlim_t rlim_cur;  /* soft limit: current limit */

rlim_t rlim_max;  /* hard limit: maximum value for rlim_cur */

}

说明:

任何一个进程都可将一个软限制值更改为小于等于其硬限制值。

任何一个进程都可降低其硬限制值,但它不能小于软限制值,而且这种降低对于普通用户是不可逆的。

只有超级用户进程可以提高硬限制值。

resource参数取值如下:

RLIMIT_AS:进程总的可用存储空间的最大长度。

RLIMIT_CORE:core文件的最大字节。若为0,则阻止创建core文件。

RLIMIT_CPU:CPU时间的最大量值(秒)。若超过此软限制,则向该进程发送SIGXCPU信号。

RLIMIT_DATA:数据段总的最大字节长度,包含:初始化数据段、bss段、堆区。

RLIMIT_FSIZE:可以创建文件的最大字节长度。若超过此软限制,则向该进程发送SIGXFSZ信号。

RLIMIT_MEMLOCK:进程使用mlock能够锁定的在存储空间中的最大字节长度。

RLIMIT_MSGQUEUE:进程为消息队列可分配的最大存储字节数。

RLIMIT_NICE:nice值影响进程的调度优先级。

RLIMIT_NOFILE:进程可打开的最大文件数量。更改此限制将影响sysconf函数在参数_SC_OPEN_MAX中的返回值。

RLIMIT_NPROC:每个用户ID可拥有的最大子进程数。更改此限制将影响sysconf函数在参数_SC_CHILD_MAX中的返回值。

RLIMIT_RSS:最大驻留内存集字节长度。

RLIMIT_SIGPENDING:进程可排队的信号最大数量。

RLIMIT_STACK:栈的最大字节长度。

RLIMIT_SWAP:用户可消耗的交换空间的最大字节数。

如下程序打印当前系统支持的所有资源限制情况:

[root@benxintuzi IO]# cat reslimit.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h> #define doit(name) pr_limits(#name, name) static void pr_limits(char*, int); int main(void)
{
#ifdef RLIMIT_AS
doit(RLIMIT_AS);
#endif doit(RLIMIT_CORE);
doit(RLIMIT_CPU);
doit(RLIMIT_DATA);
doit(RLIMIT_FSIZE); #ifdef RLIMIT_MEMLOCK
doit(RLIMIT_MEMLOCK);
#endif #ifdef RLIMIT_MSGQUEUE
doit(RLIMIT_MSGQUEUE);
#endif #ifdef RLIMIT_NICE
doit(RLIMIT_NICE);
#endif doit(RLIMIT_NOFILE); #ifdef RLIMIT_NPROC
doit(RLIMIT_NPROC);
#endif #ifdef RLIMIT_NPTS
doit(RLIMIT_NPTS);
#endif #ifdef RLIMIT_RSS
doit(RLIMIT_RSS);
#endif #ifdef RLIMIT_SBSIZE
doit(RLIMIT_SBSIZE);
#endif #ifdef RLIMIT_SIGPENDING
doit(RLIMIT_SIGPENDING);
#endif doit(RLIMIT_STACK); #ifdef RLIMIT_SWAP
doit(RLIMIT_SWAP);
#endif #ifdef RLIMIT_VMEM
doit(RLIMIT_VMEM);
#endif exit();
} static void pr_limits(char* name, int resource)
{
struct rlimit limit;
unsigned long long lim; if (getrlimit(resource, &limit) < )
printf("getrlimit error for %s\n", name);
printf("%-14s ", name);
if (limit.rlim_cur == RLIM_INFINITY) { # 常量RLIM_INFINITY表示无限制
printf("(infinite) ");
} else {
lim = limit.rlim_cur;
printf("%10lld ", lim);
}
if (limit.rlim_max == RLIM_INFINITY) {
printf("(infinite)");
} else {
lim = limit.rlim_max;
printf("%10lld", lim);
}
putchar((int)'\n');
} [root@benxintuzi IO]# ./reslimit
RLIMIT_AS (infinite) (infinite)
RLIMIT_CORE (infinite)
RLIMIT_CPU (infinite) (infinite)
RLIMIT_DATA (infinite) (infinite)
RLIMIT_FSIZE (infinite) (infinite)
RLIMIT_MEMLOCK
RLIMIT_MSGQUEUE
RLIMIT_NICE
RLIMIT_NOFILE
RLIMIT_NPROC
RLIMIT_RSS (infinite) (infinite)
RLIMIT_SIGPENDING
RLIMIT_STACK (infinite)

进程控制

进程标识

每个进程都用一个非负整型ID唯一标识,但是该进程ID是可复用的,只不过大多数Unix系统采用延迟复用算法,即如果一个进程终止后,必须经过某个固定的间隔才可将该进程ID用于表示其他进程,这样做可以防止将新进程误认为是前一个已经终止的进程。

特殊进程:

进程ID

说明

0

用于标识调用进程(交换进程swapper),其并不执行任何磁盘上的程序,因此也被称为系统进程。

1

用于标识init进程,在自举结束时由内核调用,位于/sbin/init,用于在自举后启动一个Unix系统。init通常读取与系统有关的初始化文件(/etc/rc*或/etc/inittab、/etc/init.d),将系统引导到一个状态。init进程不会终止,但是其并不是系统进程,而是一个普通的用户进程,只不过是以超级用户特权运行。

其他ID标识操作函数:

#include <unistd.h>

pid_t getpid(void);

pid_t getppid(void);

uid_t getuid(void);

uid_t geteuid(void);

gid_t getgid(void);

gid_t getegid(void);

说明:

getpid返回进程ID,getppid返回父进程ID,getuid返回实际用户ID,geteuid返回有效用户ID,getgid返回实际组ID,getegid返回有效组ID。

进程创建

函数

说明

#include <unistd.h>

pid_t fork(void);

(1)   fork函数用于子进程的创建,调用一次,返回两次。父进程返回子进程ID,而子进程返回0。

(2)   子进程是父进程的副本,获取父进程的数据、堆、栈等的副本(不共享)。父子进程共享正文段。

(3)   由于在fork之后经常伴随着exec,所有现在很多fork实现并不执行数据、堆和栈的完全副本,而是采用写时复制(Copy-On-Write, COW)技术,即这些区域先由父子进程共享,内核将他们的权限设为只读,如果其中任何一个需要修改时,内核只为需要修改的区域部分制作一个副本。

(4)   在fork之后先执行父进程还是子进程是不确定的,取决于内核使用的调度算法。

[root@benxintuzi process]# cat fork01.c
#include <unistd.h>
#include <stdio.h> int globvar = ; /* external variable in initialized data */
char buf[] = "a write to stdout\n"; int main(void)
{
int var; /* automatic variable on the stack */
pid_t pid; var = ;
if (write(STDOUT_FILENO, buf, sizeof(buf)-) != sizeof(buf)-)
printf("write error\n"); printf("before fork\n"); /* we don't flush stdout */ if ((pid = fork()) < )
{
printf("fork error\n");
}
else if (pid == )
{ /* child */
globvar++; /* modify variables */
var++;
}
else
{
sleep(2); /* parent */
} printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); return ;
} [root@benxintuzi process]# ./fork01
a write to stdout
before fork
pid = , glob = , var =
pid = , glob = , var =

fork失败的两个主要原因:

(1)   系统中进程数量太多了。

(2)   该实际用户ID所拥有的进程数超过了系统限制(由CHILD_MAX指定)。

vfork

vfork函数用于创建一个新进程来执行一个新程序。vfork保证子进程先运行,在其调用exec或exit之后父进程才可能被调用执行,所以如果在调用这两个函数之前,子进程依赖于父进程的进一步动作,则会导致死锁。

将上述程序用vfork重写,由于vfork可以保证在调用exec或者exit之前,父进程不会执行,因此不用在父进程中调用sleep休眠了:

[root@benxintuzi process]# cat vfork.c
#include <unistd.h>
#include <stdio.h> int globvar = ; /* external variable in initialized data */
char buf[] = "a write to stdout\n"; int main(void)
{
int var; /* automatic variable on the stack */
pid_t pid; var = ;
if (write(STDOUT_FILENO, buf, sizeof(buf)-) != sizeof(buf)-)
printf("write error\n"); printf("before vfork\n"); /* we don't flush stdout */ if ((pid = vfork()) < )
{
printf("fork error\n");
}
else if (pid == )
{ /* child */
globvar++; /* modify variables */
var++;
exit(0); /* child terminates */
}
/* parent continues here */
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); return ;
} [root@benxintuzi process]# gcc vf./vfork
a write to stdout
before vfork
pid = , glob = , var =

进程终止

进程有5种正常终止方式以及3种异常终止方式:

5种正常终止方式:

(1)   在main函数内执行return语句,等效于调用exit。

(2)   调用exit函数。exit操作包括调用各种终止处理函数(exit handler)。

(3)   调用_exit或_Exit函数。_Exit的存在是为进程提供一种无需运行终止处理函数或者信号处理函数而终止的方式。_exit函数是由exit函数调用的,用于处理Unix系统特定的细节。

(4)   进程的最后一个线程在其启动例程中执行return语句。但是该线程的返回值不用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。

(5)   进程的最后一个线程调用pthread_exit函数。

3种异常终止方式:

(1)   调用abort,其产生SIGABRT信号。

(2)   进程接收到某些信号时。

(3)   最后一个线程对“取消”请求做出响应。

不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应进程关闭所有打开的文件描述符,释放其所占用的存储器等。

获取进程终止状态

当一个进程正常或异常终止时,内核就会向其父进程发送SIGCHLD信号,进而父进程调用wait或waitpid获取其子进程的终止状态。调用wait时可能有如下情况发生:

(1)   如果当前进程没有任何子进程,则立即出错返回。

(2)   如果所有子进程都在运行,则阻塞。

(3)   如果其中一个子进程已经终止,则取得该子进程的终止状态后立即返回。

#include <sys/wait.h>

pid_t wait(int* statloc);

pid_t waitpid(pid_t pid, int* statloc, int options);

返回值:成功,返回进程ID;失败,返回0或-1

说明:

进程的终止状态存放于statloc指向的存储空间中。

<sys/wait.h>中定义了4个宏,用来获取进程的终止状态:

WIFEXITED(status)标识正常终止;

WIFSIGNALED(status)标识异常终止;

WIFSTOPPED(status)标识当前暂停子进程;

WIFCONTINUED(status)标识暂停后继续执行的子进程;

关于参数pid:

pid == -1: 等待任一子进程,此时waitpid与wait等效。

pid > 0: 等待与pid相等的子进程。

pid == 0: 等待组ID与调用进程组ID相等的任一子进程。

pid < -1: 等待组ID等于pid绝对值的任一子进程。

waitpid与wait的比较:

(1)   waitpid可以等待一个特定的进程,而wait只能返回任一终止子进程的状态。

(2)   waitpid提供了一个wait的非阻塞版本。

(3)   waitpid通过WUNTRACED/WCONTINUED选项支持作业控制。

[root@benxintuzi process]# cat wait01.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h> void pr_exit(int status); int main(void)
{
pid_t pid;
int status; if ((pid = fork()) < )
printf("fork error\n");
else if (pid == ) /* child */
exit(); if (wait(&status) != pid) /* wait for child */
printf("wait error\n");
pr_exit(status); /* and print its status */ if ((pid = fork()) < )
printf("fork error\n");
else if (pid == ) /* child */
abort(); /* generates SIGABRT */ if (wait(&status) != pid) /* wait for child */
printf("wait error\n");
pr_exit(status); /* and print its status */ if ((pid = fork()) < )
printf("fork error\n");
else if (pid == ) /* child */
status /= ; /* divide by 0 generates SIGFPE */ if (wait(&status) != pid) /* wait for child */
printf("wait error\n");
pr_exit(status); /* and print its status */ exit();
} void pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d\n", WTERMSIG(status));
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n", WSTOPSIG(status));
} [root@benxintuzi process]# ./wait01
normal termination, exit status =
abnormal termination, signal number =
abnormal termination, signal number =

另一个取得进程终止状态的函数是waitid,与waitpid相似,但waitid允许一个进程指定要等待的子进程。它使用两个参数表示要等待的子进程所属的类型。

#include <sys/wait.h>

int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options);

返回值:成功,返回0;失败,返回-1

说明:

idtype参数可以如下:

P_PID:等待一个特定进程,id中包含要等待子进程的进程ID。

P_PGID:等待一个特定进程组的任一子进程,id中包含要等待子进程的进程组ID。

P_ALL:等待任一子进程,此时忽略id。

options参数如下(按位或):

WCONTINUED等待一个进程,其曾被停止,然后又继续运行,但其状态尚未报告。

WEXITED:等待已退出的进程。

WNOHANG:如果没有可用的子进程退出状态,立即返回而非阻塞。

WNOWAIT不破坏子进程退出状态,该子进程退出状态可由后续的wait/waitpid/waitid取得。

WSTOPPED等待一个进程,它已经停止,但其状态尚未报告。

注:

必须指定WCONTINUED、WNOWAIT、WSTOPPED三者之一。

infop指向一个siginfo结构,包含了造成子进程状态改变有关信号的详细信息。

函数exec

当进程调用exec函数时,exec就用磁盘上的一个新程序替换掉当前进程的正文段、数据段、堆区、栈区,然后开始执行新程序的main函数。一般调用fork后立即会调用exec,表示创建新进程后,立即在该进程中执行新程序。有7种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[]);

int fexecve(int fd, char* const argv[], char* const envp[]);

返回值:成功,不返回;失败,返回-1

说明:

关于filename,如果filename中包含/,则将其视为路径名;否则就在PATH指定的目录中搜索可执行文件。

如果execlp和execvp找到了一个可执行文件,但是该文件不是由链接器产生的可执行文件,则就将其看作一个shell脚本,试着调用/bin/sh,并将该filename作为shell的输入。

函数名中的l表示列表list,v表示向量vector,e表示环境。因此execl/execlp/execle要求将新程序的每个命令行参数都作为一个独立的参数传递,而execv/execvp/execve/fexecve将其按数组方式传递。execle/execve/fexecve要求传递一个环境表指针,其他几个exec函数则使用全局变量environ为新程序复制现有的环境。

首先,查看PATH变量,发现里面有HOME=/root一项,那么将以下可执行文件echoall放到/root目录下,echoall可执行文件源程序如下所示,用于打印当前进程的环境表:
#include <stdio.h> int main(int argc, char *argv[])
{
int i;
char **ptr;
extern char **environ; for (i = ; i < argc; i++) /* echo all command-line args */
printf("argv[%d]: %s\n", i, argv[i]); for (ptr = environ; *ptr != ; ptr++) /* and all env strings */
printf("%s\n", *ptr); return ;
} 然后使用fork创建新进程,使用exec函数运行echoall程序:
[root@benxintuzi process]# cat exec01.c

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h> char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL }; int main(void)
{
pid_t pid; if ((pid = fork()) < ) {
printf("fork error\n");
} else if (pid == ) { /* specify pathname, specify environment */
if (execle("/root/echoall", "echoall", "myarg1",
"MY ARG2", (char *), env_init) < )
printf("execle error\n");
} if (waitpid(pid, NULL, ) < )
printf("wait error\n"); if ((pid = fork()) < ) {
printf("fork error\n");
} else if (pid == ) { /* specify filename, inherit environment */
if (execlp("/root/echoall", "echoall", "only 1 arg", (char *)) < )
printf("execlp error\n");
} return ;
} [root@benxintuzi process]# ./exec01
argv[]: echoall
argv[]: myarg1
argv[]: MY ARG2
USER=unknown
PATH=/tmp
[root@benxintuzi process]# argv[]: echoall
argv[]: only arg
...
HOME=/root
_=./exec01

更改用户ID和组ID

Unix系统中,访问控制是基于用户ID和组ID的,当程序需要访问当前并不允许访问的资源时,就需要增加特权,更换自己的用户ID和组ID,使之具有合适的特权或访问权限。

可以用setuid函数设置实际用户ID和有效用户ID,用setgid设置实际组ID和有效组ID。关于内核维护的3个用户ID,有如下说明:

(1)   只有超级用户进程才可以更改实际用户ID。通常,实际用户ID是在登录时,由login程序设置的,而login是一个超级用户进程,当它调用setuid时,设置所有3个用户ID。

(2)   仅当文件中设置了保存用户ID位时,exec函数才会设置有效用户ID。如果保存用户ID位没有设置,则exec函数不会改变有效用户ID,而是维持其现有值不变。任何时候都可以调用setuid将有效用户ID设置为实际用户ID或者保存用户ID。

(3)   保存用户ID是由exec函数复制有效用户ID而得到的。如果设置了文件的保存用户ID位,则在exec根据文件的用户ID设置了进程的有效用户ID后,这个副本就被保存起来了。

如下函数用户交换实际ID和有效ID的值:

#include <unistd.h>

int setreuid(uid_t ruid, uid_t euid);

int setregid(gid_t rgid, gid_t egid);

返回值:成功,返回0;失败,返回-1

说明:

一个非特权用户可将其有效用户ID设置为实际用户ID或者保存用户ID,一个特权用户则可将有效用户ID设置为uid,设置不同用户ID的函数图示如下:

解释器文件

解释器文件的起始行形式是:

#! pathname [optional-argument]

在!和pathname之间的空格是可选的,最常见的解释器文件开始行为:

#! /bin/sh

pathname通常是绝对路径名。对解释器文件的处理是由内核作为exec系统调用的一部分来完成的。内核使调用exec函数的进程实际执行的并不是解释器文件本身,而是解释器文件中第一行pathname指定的文件,如下所示:

[root@benxintuzi process]# cat /root/echoall.c
#include <stdio.h> int main(int argc, char *argv[])
{
int i;
char **ptr;
extern char **environ; for (i = ; i < argc; i++) /* echo all command-line args */
printf("argv[%d]: %s\n", i, argv[i]); for (ptr = environ; *ptr != ; ptr++) /* and all env strings */
printf("%s\n", *ptr); return ;
} [root@benxintuzi process]# cat /root/testinterp
#! /root/echoall echoarg1 echoarg2 [root@benxintuzi process]# cat interp.c
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h> int main(void)
{
pid_t pid; if ((pid = fork()) < )
printf("fork error\n");
else if (pid == )
if (execl("/root/echoall", "/root/testinterp", "myarg1", "myarg2", (char*)) < )
printf("execl error\n");
if (waitpid(pid, NULL, ) < )
printf("waitpid error\n"); return ;
} [root@benxintuzi process]# ./interp
argv[]: /root/testinterp
argv[]: myarg1
argv[]: myarg2
HOSTNAME=benxintuzi
SELINUX_ROLE_REQUESTED=
SHELL=/bin/bash
TERM=vt100
HISTSIZE=
HADOOP_HOME=/bigdata/hadoop-2.6.
SSH_CLIENT=192.168.8.1
SELINUX_USE_CURRENT_RANGE=
QTDIR=/usr/lib/qt-3.3
QTINC=/usr/lib/qt-3.3/include
SSH_TTY=/dev/pts/
USER=benxintuzi

进程调度

调度策略和调度优先级是由内核控制的,进程可以通过调整nice值降低进程的调度优先级。nice越小,优先级越高。进程可以通过nice函数获取或更改其nice值,该操作只影响进程自己的nice值,不影响其他进程:

#include <unistd.h>

int nice(int lchr);

返回值:成功,返回新的nice值NZERO;失败,返回-1

说明:

nice值的范围一般为0~(2*NAERO) – 1。

新的nice值new_nice = old_nice + lchr,如果超出nice值的范围,自动降到最大、最小合法值。

#include <sys/resource.h>

int getpriority(int which, id_t who);

返回值:成功,返回-NZERO~NZERO – 1之间的nice值;失败,返回-1

说明:

getpriority函数不仅可以获取进程的nice值,还可以获取一组相关进程的nice值。

which参数如下:

PRIO_PROCESS:表示进程。

PRIO_PGRP:表示进程组。

PRIO_USER:表示用户ID。

who参数选择一个或者多个进程,如果为0,表示选择一个。

#include <sys/resource.h>

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

返回值:成功,返回0;失败,返回-1

进程时间

#include <sys/times.h>

clock_t times(struct tms* buf);

返回值:成功,返回时间;失败,返回-1

说明:

times函数填充tms结构体:

struct tms

{

clock_t    tms_utime; /* user CPU time */

clock_t tms_stime;   /* system CPU time */

clock_t tms_cutime;  /* user CPU time, terminated children */

clock_t tms_cstime;  /* system CPU time, terminated children */

};

sysconf(_SC_CLK_TCK)返回每秒时钟的滴答数。

如下函数将命令行参数作为程序运行,并且统计执行每个程序的时间:

[root@benxintuzi process]# cat times.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/times.h> static void pr_times(clock_t, struct tms *, struct tms *);
static void do_cmd(char *); int main(int argc, char *argv[])
{
int i; setbuf(stdout, NULL);
for (i = ; i < argc; i++)
do_cmd(argv[i]); /* once for each command-line arg */
return ;
} static void do_cmd(char *cmd) /* execute and time the "cmd" */
{
struct tms tmsstart, tmsend;
clock_t start, end;
int status; printf("\ncommand: %s\n", cmd); if ((start = times(&tmsstart)) == -) /* starting values */
printf("times error\n"); if ((status = system(cmd)) < ) /* execute command */
printf("system() error\n"); if ((end = times(&tmsend)) == -) /* ending values */
printf("times error\n"); pr_times(end-start, &tmsstart, &tmsend);
printf("status = %d\n", status);
} static void pr_times(clock_t real, struct tms *tmsstart, struct tms *tmsend)
{
static long clktck = ; if (clktck == ) /* fetch clock ticks per second first time */
if ((clktck = sysconf(_SC_CLK_TCK)) < )
printf("sysconf error\n"); printf(" real: %7.2f\n", real / (double) clktck);
printf(" user: %7.2f\n",
(tmsend->tms_utime - tmsstart->tms_utime) / (double) clktck);
printf(" sys: %7.2f\n",
(tmsend->tms_stime - tmsstart->tms_stime) / (double) clktck);
printf(" child user: %7.2f\n",
(tmsend->tms_cutime - tmsstart->tms_cutime) / (double) clktck);
printf(" child sys: %7.2f\n",
(tmsend->tms_cstime - tmsstart->tms_cstime) / (double) clktck);
} [root@benxintuzi process]# ./times "sleep 5" "date" "man bash > /dev/null" command: sleep
real: 5.03
user: 0.00
sys: 0.00
child user: 0.00
child sys: 0.00
status = command: date
Sat Aug :: PDT
real: 0.01
user: 0.00
sys: 0.00
child user: 0.00
child sys: 0.01
status = command: man bash > /dev/null
real: 0.69
user: 0.00
sys: 0.00
child user: 0.19
child sys: 0.49
status =
[root@benxintuzi process]#

Linux 进程(一):环境及其控制的更多相关文章

  1. linux进程管理之信号控制

    使用信号控制进程 ====================================================================================kill,ki ...

  2. linux进程及进程控制

    Linux进程控制   程序是一组可执行的静态指令集,而进程(process)是一个执行中的程序实例.利用分时技术,在Linux操作系统上同时可以运行多个进程.分时技术的基本原理是把CPU的运行时间划 ...

  3. Linux进程控制(三)

    1. 进程间打开文件的继承 1.1. 用fork继承打开的文件 fork以后的子进程自动继承了父进程的打开的文件,继承以后,父进程关闭打开的文件不会对子进程造成影响. 示例: #include < ...

  4. 【转载】linux进程及进程控制

    Linux进程控制   程序是一组可执行的静态指令集,而进程(process)是一个执行中的程序实例.利用分时技术,在Linux操作系统上同时可以运行多个进程.分时技术的基本原理是把CPU的运行时间划 ...

  5. Linux进程控制(一)

    1. Linux进程概述 进程是一个程序一次执行的过程,它和程序有本质区别.程序是静态的,它是一些保存在磁盘上的指令的有序集合:而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建.调度 ...

  6. UNIX环境高级编程——Linux进程地址空间和虚拟内存

    一.虚拟内存 分段机制:即分成代码段,数据段,堆栈段.每个内存段都与一个特权级相关联,即0~3,0具有最高特权级(内核),3则是最低特权级(用户),每当程序试图访问(权限又分为可读.可写和可执行)一个 ...

  7. Linux进程-命令行参数和环境列表

    命令行参数 在C中,main函数有很多的变种,比如 main(), int main(), int main(int argc, char *argv[]), int main(int argc, c ...

  8. Linux 进程

    Linux 进程 在用户空间,进程是由进程标识符(PID)表示的.从用户的角度来看,一个 PID 是一个数字值,可惟一标识一个进程.一个 PID 在进程的整个生命期间不会更改,但 PID 可以在进程销 ...

  9. Linux里设置环境变量的方法(export PATH)

    1.动态库路径的设置 Linux下调用动态库和windows不一样.linux 可执行程序是靠配置文件去读取路径的,因此有些时候需要设置路径 具体操作如下 export LD_LIBRARY_PATH ...

随机推荐

  1. asp.net中如何绑定combox下拉框数据(调用存储过程)

    #region 绑定类型(商品类型.仓库名称) public void DataType_Bind(int _peoid) { DataTable dt_goodsname = new DataTab ...

  2. ASP.NET MVC 开源项目学习之ProDinner (二)

    下面我们来看第二层:Data   这一层相对来说是对Core层的具体实现了. 从命名可以看出来,这和数据库相关. 1.Db.cs CodeFirst模式的本地数据库类,继承了DbContext. pr ...

  3. java(POI):基于模版的Excel导出功能,局部列写保护总结

    需求描述: 1.导出的Excel中部分列包含有下拉列表,并没有尝试过用代码实现这种功能,个人感觉比较棘手,故采用了模版的形式,直接导出数据到已经创建好的Excel模版中 2.Excel的第一列需要写保 ...

  4. .Net性能优化时应该关注的数据

    解决性能问题的时候,我往往会让客户添加下面一些计数器进行性能收集. Process object下的所有计数器: Processor object下的所有计数器: System object下的所有计 ...

  5. javaSE第二十六天

    第二十六天    414 1:网络编程(理解)    414 (1)网络编程:用Java语言实现计算机间数据的信息传递和资源共享    414 (2)网络编程模型    414 (3)网络编程的三要素 ...

  6. .NET Web开发总结(三)

    第五章 ASP.NET 页面语法 本章详细讲解.NET页面的语法结构     一般情况下  一个ASP.NET页面要包括页面编译指令 HTML页面框架及Web窗体     服务器端控件  服务器端代码 ...

  7. toggleClass

    toggleClass 用来给匹配元素切换类 语法 参考 http://www.w3schools.com/jquery/html_toggleclass.asp $(selector).toggle ...

  8. MapReduce框架Partitioner分区方法

    前言:对于二次排序相信大家也是似懂非懂,我也是一样,对其中的很多方法都不理解诶,所有只有暂时放在一边,当你接触到其他的函数,你知道的越多时你对二次排序的理解也就更深入了,同时建议大家对wordcoun ...

  9. ajax翻页效果模仿yii框架

    ajax翻页效果,模仿yii框架. 复制代码代码如下: <!DOCTYPE html>  <html>  <head>  <title>ajax分页_w ...

  10. leetcode刷题笔记

    (1)Best Time to Buy and Sell Stock Total Accepted: 10430 Total Submissions: 33800My Submissions Say ...