1. 进程的概念
Linux操作系统是面向多用户的,在同一时间可以有许多用户向操作系统发出各种命令。在现代操作系统中,都有程序和进程的概念。
通俗的讲:程序是一个包含可以执行代码的文件,是一个静态的文件;而进程是一个开始执行但还没有结束的程序实例,就是可执行文件的具体实现。
一个程序可能有许多进程,而每一个进程又可以有许多的子进程,依次循环下去,而产生子孙进程。
当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存,设备等等)然后进行一系列的复杂操作,使程序变成进程以供系统调用。在系统里面只有进程没有程序,为了区分各个不同的进程,系统给每一个进程分配了一个 ID(就象我们的身份证)以便识别。
为了充分的利用资源,系统还对进程区分了不同的状态:新建、运行、阻塞、就绪和完成一共五个状态。
新建:表示进程正在被创建。
运行:进程正在运行。
阻塞:进程在等待某个事件发生。
就绪:系统在等待CPU来执行命令。
完成:进程已经结束了系统正在回收资源。
2. 进程的标志
进程的ID(也叫PID),通过系统调用getpid可以得到进程的ID,而getppid可以得到父进程(创建调用该函数进程的进程)的ID。
#include <unistd>;
pid_t getpid(void);//获得进程ID
pid_t getppid(void);//获得父进程ID
进程 - >程序 - > 用户
进程是为程序服务的,而程序为用户服务,系统为了找到该进程的用户名,还为进程和用户建立联系。这个用户称为进程的所有者。相应的每一用户也有一个用户ID。通过系统调用getuid可以得到进程所有者的ID。由于进程要用到一些资源。而Linux对系统资源是进行保护的,为了获取一定资源进程还有一个有效用户ID,这个ID和系统的资源使用有关,涉及到进程的权限。通过系统调用geteuid得到进程的有效用户ID。
#include <unistd>;
#include <sys/types.h>;
uid_t getuid(void);//获得用户ID
uid_t geteuid(void);//获得有效用户ID
gid_t getgid(void);//获得组ID
git_t getegid(void);//获得有效组ID
有时候我们还对用户的其他信息感兴趣,调用getpwuid来得到。
struct passwd {
char *pw_name; /* 登录名称 */
char *pw_passwd; /* 登录口令 */
uid_t pw_uid; /* 用户 ID */
gid_t pw_gid; /* 用户组 ID */
char *pw_gecos; /* 用户的真名 */
char *pw_dir; /* 用户的目录 */
char *pw_shell; /* 用户的 SHELL */
};
#include <pwd.h>;
#include <sys/types.h>;
struct passwd *getpwuid(uid_t uid);
实例:
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc,char ** argv){
/*定 义数据 */
pid_t my_pid,parent_pid;
uid_t my_uid,my_euid;
gid_t my_gid,my_egid;
struct passwd *my_info;
/*调用 系统函数*/
my_pid=getpid();
parent_pid=getppid();
my_uid=getuid();
my_euid=geteuid();
my_gid=getgid();
my_egid=getegid();
my_info=getpwuid(my_uid);
/*输出 */
printf("Process ID: %ld\n",my_pid);
printf("Parent ID: %ld\n",parent_pid);
printf("User ID: %ld\n",my_uid);
printf("Effective User ID: %ld\n",my_euid);
printf("Group ID: %ld\n",my_gid);
printf("Effective Group ID: %ld\n",my_egid);
if(my_info){
printf("My Login Name: %s\n" ,my_info->pw_name);
printf("My Password : %s\n" ,my_info->pw_passwd);
printf("My User ID : %ld\n",my_info->pw_uid);
printf("My Group ID : %ld\n",my_info->pw_gid);
printf("My Real Name: %s\n" ,my_info->pw_gecos);
}
printf("My Home Dir : %s\n", my_info->pw_dir);
printf("My Work Shell: %s\n", my_info->pw_shell);
}
3. 进程的创建
创建一个进程的系统调用很简单,通过系统调用fork函数。
#include <unistd.h>;
pid_t fork();
当一个进程调用了fork以后,系统会自动创建一个子进程,这个子进程和父进程不同的地方只有他的进程ID和父进程ID,其他的都是一样的,就像父进程克隆自己一样。当然创建两个一模一样的进程是没有意义的。为了区分父进程和子进程,必须跟踪fork的返回值。当fork调用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork返回值就是根据有重要作用。对于父进程fork返回子进程ID,而对于fork子进程返回0。
根据这个来区分父子进程。Linux 是一个多用户操作系统,在同一时间会有许多的用户在争夺系统的资源。有时进程为了早一点完成任务就创建子进程来争夺资源。一旦子进程被创建,父子进程一起从fork 处继续执行,相互竞争系统的资源。有时候我们希望子进程继续执行,而父进程阻塞直到子进程完成任务。这个时候我们可以调用 wait 或者 waitpid 系统调用.
#include <sys/types.h>;
#include <sys/wait.h>;
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid,int *stat_loc,int options);
wait系统调用会使父进程阻塞直到一个子进程结束或者父进程接受到了一个信号,如果没有父进程没有子进程或者其他的子进程已经结束了wait会立即返回。成功时,wait将返回子进程的ID,否则返回-1,并设置全局变量errno。stat_loc是子进程的退出状态。子进程调用exit,_exit或者是return来设置这个值,为了得到这个值Linux定义了几个宏来测试这个返回值。
WIFEXITED(status):判断子进程退出值是非 0.
WEXITSTATUS(status):判断子进程的退出值(当子进程退出时非 0).
WIFSIGNALED(status):子进程由于有没有获得的信号而退出.
WTERMSIG(status):子进程没有获得的信号号(在 WIFSIGNALED 为真时才有意义).
waitpid等待指定的子进程直到子进程返回。如果 pid 为正值则等待指定的进程(pid)。如果为 0 则等待任何一个组 ID 和调用者的组 ID 相同的进程,为-1 时等同于 wait 调用,小于-1 时等待任何一个组 ID 等于 pid 绝对值的。stat_loc 和 wait 的意义一样,options 可以决定父进程的状态 。
optios可以取两个值 :
WNOHANG:父进程立即返回当没有子进程存在时 。
WUNTACHED:当子进程结束时 waitpid 返回,但是子进程的退出状态不可得到。
父进程创建子进程后,子进程一般要执行不同的程序.为了调用系统程序,我们可以使用系统调用exec族调用。exec族调用有着5个函数:
#include <unistd.h>;
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const char *arg,...);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]):
/*exec 族调用可以执行给定程序.关于 exec 族调用的详细解说可以参考系统手册(man execl). */
实例:
#include <unistd.h>;
#include <sys/types.h>;
#include <sys/wait.h>;
#include <stdio.h>;
#include <errno.h>;
#include <math.h>;
void main(void)
{
pid_t child;
int status;
printf("描述怎样获得子进程的status\n");
if((child=fork())==-1)
{
printf("Fork Error : %s\n",strerror(errno));
exit(1);
}
else if(child==0) //判断自己是不是子进程
{
int i;
printf("I am the child: %ld\n",getpid());
for(i=0;i<1000000;i++) sin(i);
i=5;
printf("I exit with %d\n",i);
exit(i);
}
while(((child=wait(&status))==-1)&(errno==EINTR));
/*等待一个子进程结束*/
if(child==-1)/*子进程没有结束wait()返回-1,否则返回子PID*/
printf("Wait Error: %s\n",strerror(errno));
/*strerror 函数会返回一个指定的错误号的错误信息的字符串.*/
else if(!status)/*子进程的退出状态为0*/
printf("Child %ld terminated normally return status is zero\n",child);
else if(WIFEXITED(status))/*利用宏判断子进程退出状态非0*/
printf("Child %ld terminated normally return status is %d\n",child,WEXITSTATUS(status));
else if(WIFSIGNALED(status))/*利用宏判断子进程没有获得信号*/
printf("Child %ld terminated due to signal %d znot caught\n",child,WTERMSIG(status));
}
4. 守护进程的创建
由于 Linux 是多任务操作系统,我们就是不编写代码也可以把一个程序放到后台去执行的。我们只要在命令后面加上&符号 SHELL 就会把我们的程序放到后台去运行的.。这里我们”开发”一个后台检查邮件的程序。这个程序每个一个指定的时间回去检查我们的邮箱,如果发现我们有邮件了,会不断的报警。后面有这个函数的加强版本加强版本后台进程的创建思想: 首先父进程创建一个子进程,然后子进程杀死父进程。 信号处理所有的工作由子进程来处理。
#include <unistd.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <stdio.h>;
#include <errno.h>;
#include <fcntl.h>;
#include <signal.h>;
/* Linux 的默任个人的邮箱地址是 /var/spool/mail/用户的登录名 */
#define MAIL "/var/spool/mail/hoyt"
/* 睡眠 10 秒钟 */
#define SLEEP_TIME 10
main(void)
{
pid_t child;
if((child=fork())==-1)/*创建进程错误*/
{
printf("Fork Error: %s\n",strerror(errno));
exit(1);
}
else if(child>0) while(1);/*如果自己不是子进程则等待被杀死*/
if(kill(getppid(),SIGTERM)==-1)/*kill父进程错误*/
{
printf("Kill Parent Error: %s\n",strerror(errno));
exit(1);
}
{/*自己是子进程*/
int mailfd;
long i=0;
while(1)
{
printf("我是子进程,我在监听邮箱:%ld\r\n",i++);
if((mailfd=open(MAIL,O_RDONLY))!=-1)
{
fprintf(stderr,"%s","\007");
close(mailfd);
}
if(i==9999)i=0;
sleep(SLEEP_TIME);
}
}
}
可以在默认的路径下创建你的邮箱文件,然后测试一下这个程序。当然这个程序还有很多地方要改善的。