歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> Gnu/Linux系統C編程之

Gnu/Linux系統C編程之

日期:2017/3/3 12:23:46   编辑:Linux技術

這裡主要介紹/proc偽文件系統及uname()函數來獲取系統或進程的一些信息。

/proc文件系統介紹在早期的UNIX發行版中,並不能很容易的分析內核的一些屬性,並且很難回答以下問題:

系統有多少進程正在運行,並且誰擁有這些進程?

一個進程都打開了哪些文件?

哪些文件目前被鎖住了,並且哪些進程擁有這些文件鎖?

系統有哪些套接字正在使用?

一些早期的UNIX發行版解決該問題是允許有權限的程序進入內核的內存空間的數據結構中。這樣的解決辦法有諸多不便。然而,最蛋疼的是它需要程序員了解的內核中的數據結構,並且這些數據結構在不同的內核版本中會略有不同,需要程序員根據實際所依賴的內核進行代碼的重寫。

為了提供便捷的訪問內核信息,很多現代的UNIX發行版提供了/proc虛擬文件系統。該文件系統駐留在/proc目錄下,該目錄下有很多文件以暴露內核信息,方便進程來從中讀取信息,並在某種情形下還可以使用系統的I/O調用來修改這些信息。說/proc文件系統是虛的,主要是因為它下面的文件及子目錄並不占用硬盤空間。相反是內核在進程訪問這些信息的時候創建的。

獲取某個進程的一些信息對於每一個系統中進程來說,內核提供了一個在/proc中相應的目錄,該目錄一般是進程的PID。在/proc/PID目錄中有一些文件及子目錄等,這些文件及目錄都包含了該進程的相關信息。在Gun/Linux系統中,我們最熟悉的1號進程就是init進程,與其相關的進程信息在/proc/1目錄下。

在/proc/1目錄下,有一個名為status的文件,status文件包含了關於該進程的一些詳盡信息,接下來我們可以看看裡面是什麼東東,

[root@lavenliu ~]# cat /proc/1/status 
Name:	init
State:	S (sleeping)
Tgid:	1
Pid:	1
PPid:	0
TracerPid:	0
Uid:	0	0	0	0
Gid:	0	0	0	0
Utrace:	0
FDSize:	64
Groups:	
VmPeak:	   19296 kB
VmSize:	   19232 kB
VmLck:	       0 kB
VmHWM:	    1588 kB
VmRSS:	    1588 kB
VmData:	     200 kB
VmStk:	      88 kB
VmExe:	     140 kB
VmLib:	    2348 kB
VmPTE:	      56 kB
VmSwap:	       0 kB
Threads:	1
SigQ:	0/3878
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000000000000
SigIgn:	0000000000001000
SigCgt:	00000001a0016623
CapInh:	0000000000000000
CapPrm:	ffffffffffffffff
CapEff:	fffffffffffffeff
CapBnd:	ffffffffffffffff
Cpus_allowed:	3
Cpus_allowed_list:	0-1
Mems_allowed:	00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:	0
voluntary_ctxt_switches:	577
nonvoluntary_ctxt_switches:	69
下面列出/proc/PID目錄下的一些其他文件,如下:

文件說明cmdline以'\0'結尾的命令行參數cwd指向當前工作目錄的軟連接environ環境變量列表,形如:NAME=value形式exe執行進程可執行文件的軟連接fd該目錄包含進程已打開的文件描述符,軟鏈接至打開的文件maps內存映射mem進程的虛擬內存mounts進程使用的掛載點root軟鏈接至根目錄status進程的一些信息(如:進程ID、內存使用情況、信號等)task該目錄下是進程的線程目錄,每個線程一個子目錄/proc/PID/fd目錄介紹

/proc/PID/fd目錄中包含了該PID進程打開的每一個文件描述符的軟鏈接。每一個軟鏈接的名字都與該進程打開的文件描述符是一致的。舉例來說,/proc/1990/1這個軟鏈接指向該1990進程的標准輸出。

任何一個進程都很方便地訪問它自己的/proc/PID目錄通過使用/proc/self軟鏈接。

線程:/proc/PID/task目錄Linux內核2.4版本增加了線程組的概念以符合POSIX標准的線程模型。由於線程組裡的線程的某些線程屬性不同,所以Linux2.4內核版本在/proc/PID目錄中增加了task子目錄。對於進程的每個線程都對應一個目錄,每個目錄都是以線程ID命名的,形如/proc/PID/task/TID形式,TID為進程的一個線程。比如我們看一下PID為1287的進程的線程,它有如下的線程在運行,這些目錄名稱就是1287進程的線程ID(可以使用gettid()來獲取線程的TID)

ls /proc/1287/task
1287  1297  1343  1528  1530  1680  1695  1776  1778  1863
1296  1342  1527  1529  1679  1694  1775  1777  1862
在每一個/proc/PID/task/TID子目錄下也是一系列的文件和目錄,與/proc/PID目錄下的文件看起來很像。由於線程間共享了很多屬性,這些目錄下的文件所包含的信息是一樣的。既然這些線程間都共享大量的相同屬性,究竟是什麼然它們得以區別於其他線程呢?在/proc/PID/task/TID/status文件中包含的信息可以區分於該線程組中的其他線程,這些信息有State,Pid,SigPnd,SigBlk,CapInh,CapPrm,CapEff和CapBnd等信息來區分於其他線程。如,
[root@mydevops task]# cat 1287/status 
Name:	salt-master
State:	S (sleeping)
Tgid:	1287
Pid:	1287
PPid:	1282
TracerPid:	0
Uid:	0	0	0	0
Gid:	0	0	0	0
Utrace:	0
FDSize:	128
Groups:	
VmPeak:	 1140892 kB
VmSize:	 1075356 kB
VmLck:	       0 kB
VmHWM:	   26800 kB
VmRSS:	   26800 kB
VmData:	  797808 kB
VmStk:	     260 kB
VmExe:	       4 kB
VmLib:	   11504 kB
VmPTE:	     520 kB
VmSwap:	       0 kB
Threads:	19
SigQ:	0/3878
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	0000000000000000
SigIgn:	0000000001001000
SigCgt:	0000000180000a02
CapInh:	0000000000000000
CapPrm:	ffffffffffffffff
CapEff:	ffffffffffffffff
CapBnd:	ffffffffffffffff
Cpus_allowed:	3
Cpus_allowed_list:	0-1
Mems_allowed:	00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:	0
voluntary_ctxt_switches:	437
nonvoluntary_ctxt_switches:	256
[root@mydevops task]# cat 1297/status 
Name:	salt-master
State:	S (sleeping)
Tgid:	1287
Pid:	1297
PPid:	1282
TracerPid:	0
Uid:	0	0	0	0
Gid:	0	0	0	0
Utrace:	0
FDSize:	128
Groups:	
VmPeak:	 1140892 kB
VmSize:	 1075356 kB
VmLck:	       0 kB
VmHWM:	   26800 kB
VmRSS:	   26800 kB
VmData:	  797808 kB
VmStk:	     260 kB
VmExe:	       4 kB
VmLib:	   11504 kB
VmPTE:	     520 kB
VmSwap:	       0 kB
Threads:	19
SigQ:	0/3878
SigPnd:	0000000000000000
ShdPnd:	0000000000000000
SigBlk:	fffffffe7ffbfeff
SigIgn:	0000000001001000
SigCgt:	0000000180000a02
CapInh:	0000000000000000
CapPrm:	ffffffffffffffff
CapEff:	ffffffffffffffff
CapBnd:	ffffffffffffffff
Cpus_allowed:	3
Cpus_allowed_list:	0-1
Mems_allowed:	00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:	0
voluntary_ctxt_switches:	49
nonvoluntary_ctxt_switches:	7

/proc目錄下的系統信息在/proc目錄下有一些文件及目錄用來提供操作系統級別的信息。這些文件及目錄如下表所示:

目錄提供的信息/proc一些系統信息/proc/net網絡及套接字的狀態信息/proc/sys/fs與文件系統相關的設置/proc/sys/kernel與內核相關的一些設置/proc/sys/net網絡與套接字相關的設置/proc/sys/vm內存管理相關的設置/proc/sysvipcSystemV IPC相關的信息訪問/proc下面的文件

對於/proc目錄下面的很多文件,我們可以通過shell腳本來輕松地訪問(也可以使用Python與Perl等腳本語言來訪問之),如

[root@mydevops ~]# cat /proc/sys/kernel/pid_max 
32768
[root@mydevops ~]# echo 100000 > /proc/sys/kernel/pid_max 
[root@mydevops ~]# cat /proc/sys/kernel/pid_max 
100000
[root@mydevops ~]#
/proc下的文件同樣可以被程序通過使用一般的I/O系統調用來訪問。程序使用I/O系統調用的方式訪問時有如下的限制:/proc下的一些文件是只讀的;那就是說它們的存在只是顯示內核的信息,並不能用來修改內核的信息。這些限制同樣適用於/proc/PID目錄下的文件。

/proc下的一些文件對文件的所有者是可以只讀的(或者是一個已授權的進程)。例如,/proc/PID目錄下的所有文件通常是擁有該進程的用戶所擁有,並且這些文件(如/proc/PID/environ)通常是授權只讀權限給文件的所有者。

除了/proc/PID子目錄下的文件,/proc下的大部分文件的擁有者是root用戶,通常這些文件有的是只可以通過root用戶來修改的。

訪問/proc/PID目錄下的文件/proc/PID目錄下的文件是經常變動的。當一個進程啟動時,相應的在/proc目錄下會創建以該進程ID的目錄;又當該進程終止時,該/proc/PID目錄就會消失。

接下來,我們寫一個簡單的程序,來更新/proc/sys/kernel/pid_max文件,該程序需要從命令行中讀取一個參數,並更新原有的數值;如果不提供參數,則獲取原先的數值。程序如下:

# cat mypid_max.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

#define MAX_LINE 100

int main(int argc, char *argv[])
{
    int     fd;
	char    line[MAX_LINE];
	ssize_t n;

	fd = open("/proc/sys/kernel/pid_max", (argc > 1) ? O_RDWR : O_RDONLY);
	if (fd == -1) {
		perror("open");
		exit(1);
	}
	
	n = read(fd, line, MAX_LINE);
	if (n == -1) {
		perror("read");
		exit(1);
	}

	if (argc > 1) {
		printf("Old value: ");
	}
	printf("%.*s", (int) n, line);

	if (argc > 1) {
		if (lseek(fd, 0, SEEK_SET) == -1) {
			perror("lseek");
			exit(1);
		}

		if (write(fd, argv[1], strlen(argv[1])) != strlen(argv[1])) {
			perror("write() failed");
			exit(1);
		}

		system("echo /proc/sys/kernel/pid_max now contains "
			   "`cat /proc/sys/kernel/pid_max`");
	}

    exit(EXIT_SUCCESS);
}
編譯並運行:

gcc -o mypid_max mypid_max.c
[root@mydevops sysinfo]# ./mypid_max 
100000
[root@mydevops sysinfo]# ./mypid_max 1024
Old value: 100000
/proc/sys/kernel/pid_max now contains 1024
[root@mydevops sysinfo]# ./mypid_max 
1024
系統鑒別:uname()

uname()系統調用通常會返回一些系統相關的信息。使用的結構體為utsbuf。函數原型為:

#include <sys/utsname.h>
int uname(struct utsname *utsbuf);
                                        Returns 0 on success, or –1 on error
utsbuf參數是一個指向utsname結構體的指針,它的定義如下:
#define _UTSNAME_LENGTH 65
struct utsname {
    char sysname[_UTSNAME_LENGTH]; /* Implementation name */
    char nodename[_UTSNAME_LENGTH]; /* Node name on network */
    char release[_UTSNAME_LENGTH]; /* Implementation release level */
    char version[_UTSNAME_LENGTH]; /* Release version level */
    char machine[_UTSNAME_LENGTH]; /* Hardware on which system
				      is running */
#ifdef _GNU_SOURCE /* Following is Linux-specific */
    char domainname[_UTSNAME_LENGTH]; /* NIS domain name of host */
#endif
};

在CentOS6.5 64位系統上,可以在/proc/sys/kernel目錄中找到這些相關的信息。可以通過訪問/proc/sys/kernel/hostname,/proc/sys/kernel/osrelease,/proc/sys/kernel/version這三個文件來獲取這些信息。這些信息都是只讀的,我們可以看看這幾個文件的內容:

[root@mydevops sysinfo]# cat /proc/sys/kernel/hostname 
mydevops
[root@mydevops sysinfo]# cat /proc/sys/kernel/osrelease 
2.6.32-573.18.1.el6.x86_64
[root@mydevops sysinfo]# cat /proc/sys/kernel/version 
#1 SMP Tue Feb 9 22:46:17 UTC 2016
另外一個文件/proc/version包含了上面三個文件的所有信息,並且還有一些關於內核編譯相關的信息(諸如:是誰進行的內核編譯工作,使用的是什麼機器進行編譯的,使用的GCC版本是多少等附加信息)。我們可以看看/proc/version文件的內容:

[root@mydevops sysinfo]# cat /proc/version 
Linux version 2.6.32-573.18.1.el6.x86_64 ([email protected]) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC) ) #1 SMP Tue Feb 9 22:46:17 UTC 2016
utsname結構體中sysname,release,version與machine字段由內核自動設置。接下來看看如何使用uname()函數的使用,代碼如下:

[root@mydevops sysinfo]# cat my_uname.c 
#ifdef __linux__
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>

int main(int argc, char *argv[])
{
    struct utsname uts;

    if (uname(&uts) == -1) {
        perror("uname");
        exit(1);
    }

    printf("Node name:   %s\n", uts.nodename);
    printf("System name: %s\n", uts.sysname);
    printf("Release:     %s\n", uts.release);
    printf("Version      %s\n", uts.version);
    printf("Machine:     %s\n", uts.machine);
#ifdef _GNU_SOURCE
    printf("Domain name: %s\n", uts.domainname);
#endif
    
    return 0;
}
編譯並運行該程序,如下:

[root@mydevops sysinfo]# gcc -o my_uname my_uname.c 
[root@mydevops sysinfo]# ./my_uname 
Node name:   mydevops
System name: Linux
Release:     2.6.32-573.18.1.el6.x86_64
Version      #1 SMP Tue Feb 9 22:46:17 UTC 2016
Machine:     x86_64
Domain name: (none)
nodename字段是使用sethostname()系統調用作為其返回值的。domainname字段是使用setdomainname()系統調用作為其返回值的,它是該主機的NIS(Network Information Services)域名。我們可以使用hostname及domainname進行設置我們的主機名及NIS域名,不過我們很少在程序中使用sethostname()及setdomainname()系統調用。一般地,hostname及NIS domain name是在系統啟動時由啟動腳本進行設置的。

想要查看更多的關於proc相關的信息,我們查看man page的第5章節的proc幫助信息,如下即可:

man 5 proc
練習:

獲取系統用戶的進程PID及進程名稱。該程序需要一個命令行參數,該參數是/etc/passwd中的用戶名(如root,mysql等)。

cat my_procfs_user_exe.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <ctype.h>
#include <limits.h>
#include <sys/types.h>
#include <pwd.h>
#include <errno.h>

#define MAX_LINE 1000
typedef enum {FALSE, TRUE} Boolean;

char *username_from_id(uid_t uid)
{
	struct passwd *pwd;

	pwd = getpwuid(uid);
	return (pwd == NULL) ? NULL : pwd->pw_name;
}

uid_t userid_from_name(const char *name)
{
	struct passwd  *pwd;
	uid_t           u;
	char           *endptr;

	if (name == NULL || *name == '\0') {
		return -1;
	}

	u = strtol(name, &endptr, 10);
	if (*endptr == '\0') {
		return u;
	}

	pwd = getpwnam(name);
	if (pwd == NULL) {
		return -1;
	}

	return pwd->pw_uid;
}

int main(int argc, char *argv[])
{
	DIR            *dirp;
	struct dirent  *dp;
	char            path[PATH_MAX];
	char            line[MAX_LINE], cmd[MAX_LINE];
	FILE           *fp;
	char           *p;
	uid_t           uid, checked_uid;
	Boolean         got_name, got_uid;
	

	if (argc < 2 || strcmp(argv[1], "--help") == 0) {
		printf("%s <username>\n", argv[0]);
		exit(1);
	}

	checked_uid = userid_from_name(argv[1]);
	if (checked_uid == -1) {
		printf("Bad username: %s\n", argv[1]);
		exit(1);
	}

	dirp = opendir("/proc");
	if (dirp == NULL) {
		perror("opendir");
		exit(1);
	}

	/* scan entries unser /proc directory */
	for ( ;; ) {
		errno = 0;
		dp = readdir(dirp);
		if (dp == NULL) {
			if (errno != 0) {
				perror("readdir");
				exit(1);
			} else {
				break;
			}
		}

		/* since we are looking for /proc/PID directories, skip
		   entries that are not directories, or don't begin with a
		   digit*/
		if (dp->d_type != DT_DIR || !isdigit((unsigned char) dp->d_name[0])) {
			continue;
		}

		snprintf(path, PATH_MAX, "/proc/%s/status", dp->d_name);
		fp = fopen(path, "r");
		if (fp == NULL) {
			continue;
		}

		got_name = FALSE;
		got_uid = FALSE;
		while (!got_name || !got_uid) {
			if (fgets(line, MAX_LINE, fp) == NULL) {
				break;
			}

			/* The "Name:" line contains the name of the command that
			   this process is running */
			if (strncmp(line, "Name:", 5) == 0) {
				for (p = line + 5; *p != '\0' && isspace((unsigned char) *p); ) {
					p++;
				}
				strncpy(cmd, p, MAX_LINE - 1);
				cmd[MAX_LINE -1] = '\0';        /* Ensure null-terminated */

				got_name = TRUE;
			}

			/* The "Uid:" line contains the real, effective, saved set-,
               and file-system user IDs */

			if (strncmp(line, "Uid:", 4) == 0) {
				uid = strtol(line + 4, NULL, 10);
				got_uid = TRUE;
			}
		}
		fclose(fp);

		/* If we found a username and a UID, and the UID matches,
           then display the PID and command name */

		if (got_name && got_uid && uid == checked_uid)
			printf("%5s %s", dp->d_name, cmd);
	}

    return 0;
}
編譯並運行,
[root@mydevops sysinfo]# ./my_procfs_user_exe mysql
 1208 mysqld
[root@mydevops sysinfo]# ./my_procfs_user_exe nginx
[root@mydevops sysinfo]# ./my_procfs_user_exe daemon
[root@mydevops sysinfo]# ./my_procfs_user_exe apache
 3907 httpd
 3908 httpd
 3909 httpd
 3910 httpd
 3911 httpd
 3912 httpd
 3913 httpd
 3914 httpd
 3915 httpd

本文出自 “固態U盤” 博客,請務必保留此出處http://lavenliu.blog.51cto.com/5060944/1783110

Copyright © Linux教程網 All Rights Reserved