1.进程的概念
Linux 操作系统是面向多用户的.在同一时间可以有许多用户向操作系统发出各种命令。
程序是一个包含可以执行代码的文件,是一个静态的文件.而进程是一个开始执行但是还没有结束的程序的实例.就是可执行文件的具体实现.
一个程序可能有许多进程,而每一个进程又可以有许多子进程.
当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存,设备等等)然后进行一系列的复杂操作,使程序变成进程以供系统调用.在系统里面只有进程没有程序,为了区分各个不同的进程,系统给每一个进程分配了一个 ID(就象我们的
身份证)以便识别.
为了充分的利用资源,系统还对进程区分了不同的状态.将进程分为新建,运行,阻塞,就绪和完成五个状态.
新建表示进程正在被创建,
运行是进程正在运行,
阻塞是进程正在等待某一个事件发生,
就绪是表示系统正在等待 CPU 来执行命令,
完成表示进程已经结束了系统正在回收资源.
2.进程的标志
#include <unistd>;
pid_t getpid(void); 得到进程的 ID
pid_t getppid(void); 得到父进程的 ID
系统为了找到进程的用户名,还为进程和用户建立联系.这个用户称为进程的所有者.相应的每一个用户也有一个用户 ID.
uid_t getuid(void); 得到进程的所有者的 ID
uid_t geteuid(void); 得到进程的有效用户 ID
gid_t getgid(void);
gid_t getegid(void); 组 ID 和有效组ID
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) 用户的其他信息
(pid_t实为int)
示例:
#include <sys/types.h>
#include<unistd.h>
#include <stdio.h>
#include<pwd.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);
}
}
运行:
[yu@localhost ~]$ vi getpid.c [yu@localhost ~]$ gcc -o getpid getpid.c [yu@localhost ~]$ ./getpid Process ID: 5924 Parent ID: 3574 User ID: 1000 Effective User ID: 1000 Group ID: 1000 Effective Group ID: 1000 My Login Name: yu My Password : x My User ID : 1000 My Group ID : 1000 My Real Name: yu My Home Dir : /home/yu My Work Shell: /bin/bash
3.进程的创建
#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:判断子进程退出值是非 0
WEXITSTATUS:判断子进程的退出值(当子进程退出时非 0).
WIFSIGNALED:子进程由于有没有获得的信号而退出.
WTERMSIG:子进程没有获得的信号号(在 WIFSIGNALED 为真时才有意义).
pid_t waitpid(pid_t pid,int *stat_loc,int options);
waitpid 等待指定的子进程直到子进程返回.
参数说明:
如果 pid 为正值则等待指定的进程(pid).
如果为 0 则等待任何一个组 ID 和调用者的组 ID 相同的进程.
为-1 时等同于 wait 调用(任意ID);小于-1 时等待任何一个组 ID 等于 pid 绝对值的进程.
stat_loc 和 wait 的意义一样.
options 可以决定父进程的状态 .可以取两个值 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函数族可以实现在一个进程中启动另一个程序的功能。系统内核会使用新的程序替换原有进程中使用的程序。
开始新程序的执行。新进程和原有的进程号相同。调用exec函数后,代码段中的内容被新进程的代码替换,接着更新堆栈段和数据段。
实例:
#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("This will demostrate how to get child 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)
printf("Wait Error: %s\n",strerror(errno));
else if(!status)
printf("Child %ld terminated normally return status is zero\n",child);
else if(WIFEXITED(status))
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));
}
运行:
[yu@localhost ~]$ ./creatpid This will demostrate how to get child status I am the child: 6603 I exit with 5 Child 6603 terminated normally return status is 5
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
int 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)
{
printf("Kill Parent Error: %s\n",strerror(errno));
exit(1);
}
{
int mailfd;
while(1)
{
if((mailfd=open(MAIL,O_RDONLY))!=-1)
{
fprintf(stderr,"%s","\007");
close(mailfd);
}
sleep(SLEEP_TIME);
}
}
}
通过《Linux操作系统下C语言编程入门》(原 文: Hoyt 等,二次制作: AureoLEO)学习整理
浙公网安备 33010602011771号